安卓口算测试app

安卓口算测试app

  • 安卓口算测试app
    • 创建项目
      • 运行程序
      • 横屏处理
    • 结束

本文为学习类文档,通过学习B站up主longway777的视频,如有侵权,请联系博主进行删除

安卓口算测试app

为了将之前学过的ViewModel,Fragement,本地化,横竖屏设置等结合在一起,做一个安卓口算的app,可以用来给家里的小学生们做练习用,那么开始吧~

创建项目

  1. 创建Fragement分别显示标题,答题,输和赢的界面:安卓口算测试app_第1张图片
  2. 在资源中创建navigation文件并添加依赖,完成后将fragement导入创建逻辑:安卓口算测试app_第2张图片
    安卓口算测试app_第3张图片
  3. 将网上下载的图片存到Drawable中,在使用系统自带的矢量图时需要添加对应的依赖在此不做赘述,并在Gradle中添加需要dataBinding和ViewModelProvider依赖并sync同步:
dependencies {
	...
    implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0'
}

android {
...
    defaultConfig {
	...
        dataBinding.enabled true
    }
...
}

  1. 做界面:
    fragement_title.xml
    安卓口算测试app_第4张图片



    

    

    

    

    

fragement_question.xml:安卓口算测试app_第5张图片




    

    

    

    

    

    

    

    

    

    

    

    

    

    

    

    

    

    

fragment_lose.xml:安卓口算测试app_第6张图片




    

    

    

    

    

    

fragment_win.xml:安卓口算测试app_第7张图片




    

    

    

    

    

    
  1. 将fragement链接到activity中:安卓口算测试app_第8张图片
  2. ViewModel类中做数据的操作:
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.SavedStateHandle;

import java.util.Random;

@SuppressWarnings("ConstantConditions")
public class MyViewModel extends AndroidViewModel {
    private SavedStateHandle handle;
    private static String KEY_HIGE_SCORE = "key_high_score";
    private static String KEY_LEFT_NUMBER = "key_left_number";
    private static String KEY_RIGHT_NUMBER = "key_right_number";
    private static String KEY_OPERATOR = "key_operator";
    private static String KEY_ANSWER = "key_answer";
    private static String SAVE_SHP_DATA_MANE = "save_shp_data_name";
    private static String KEY_CURRENT_SCORE = "key_current_score";
    boolean win_flag = false;

    public MyViewModel(@NonNull Application application, SavedStateHandle handle) { //使用了savedstatehandle
        super(application);
        //管理的数据有:最高纪录存放之sharedpreferences,题目数字及运算符和答案,当前得分。
        if (!handle.contains(KEY_HIGE_SCORE)){
            SharedPreferences sharedPreferences = getApplication().getSharedPreferences(SAVE_SHP_DATA_MANE, Context.MODE_PRIVATE);
            handle.set(KEY_HIGE_SCORE,sharedPreferences.getInt(KEY_HIGE_SCORE,0));
            handle.set(KEY_LEFT_NUMBER,0);
            handle.set(KEY_RIGHT_NUMBER,0);
            handle.set(KEY_OPERATOR,"+");
            handle.set(KEY_ANSWER,0);
            handle.set(KEY_CURRENT_SCORE,0);
        }
        this.handle = handle;
    }
    MutableLiveDatagetLeftNumber(){
        return handle.getLiveData(KEY_LEFT_NUMBER);
    }
    MutableLiveDatagetRightNumber(){
        return handle.getLiveData(KEY_RIGHT_NUMBER);
    }
    MutableLiveDatagetOperator(){
        return handle.getLiveData(KEY_OPERATOR);
    }
    MutableLiveDatagetHignScore(){
        return handle.getLiveData(KEY_HIGE_SCORE);
    }
    MutableLiveDatagetCurrentScore(){
        return handle.getLiveData(KEY_CURRENT_SCORE);
    }
    MutableLiveDatagetAnswer(){
        return handle.getLiveData(KEY_ANSWER);
    }

    //创建题目
    void generator(){
        int LEVEL_NUMBER = 21;
        Random random = new Random();
        int x,y;
        x = random.nextInt(LEVEL_NUMBER) + 1; //出现的随机数为0到LEVEL_NUMBER-1;为防止负数出现,需要加1
        y = random.nextInt(LEVEL_NUMBER) + 1;
        if (x%2 == 0){  //对2取余为0,使得加减法各占一半
            getOperator().setValue("+");
            //做处理防止超出学生难度范围,做大数作为来当加法的结果
            if (x > y){  //x若为大数,x做结果,y做"+"前面的数,差值做“+”后面的数
                getAnswer().setValue(x);
                getLeftNumber().setValue(y);
                getRightNumber().setValue(x - y);
            }else {
                getAnswer().setValue(y);
                getLeftNumber().setValue(x);
                getRightNumber().setValue(y - x);
            }
        }else {
            getOperator().setValue("-");
            if (x > y){
                getAnswer().setValue(x - y);
                getLeftNumber().setValue(x);
                getRightNumber().setValue(y);
            }else {
                getAnswer().setValue(y - x);
                getLeftNumber().setValue(y);
                getRightNumber().setValue(x);
            }
        }
    }

    //保存
    void save() {
        SharedPreferences sharedPreferences = getApplication().getSharedPreferences(SAVE_SHP_DATA_MANE,Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putInt(KEY_HIGE_SCORE,getHignScore().getValue());
        editor.apply();
    }

    //根据答题情况区别对待
    void answerCorrect(){
        getCurrentScore().setValue(getCurrentScore().getValue() + 1);
        if (getCurrentScore().getValue() > getHignScore().getValue()){
            getHignScore().setValue(getCurrentScore().getValue());//刷新最高分
            win_flag = true;
        }
        generator();
    }
}
  1. 对界面数据绑定,转换databinding layout并绑定数据
    (1) 标题界面的数据绑定——右上角的最高纪录:



    
        
    
...

        
    

(2) 答题界面的数据绑定——当前得分、出题数字与运算符:




    
        
    
...
        
...

        

        

        
...
    

(3) 挑战成功创造新纪录的界面——新纪录高分:




    
        

    

    
...

        
...
    

(4) 挑战失败没有创造新纪录显示本次得分:




    
        
    

    
...

        
...
    

  1. 编写Fragement中的代码(每个Fragement中都需要写绑定):
    标题fragement:
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.SavedStateViewModelFactory;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.calculationtest.databinding.FragmentTitleBinding;
...
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, final ViewGroup container,
                             Bundle savedInstanceState) {
        MyViewModel myViewModel;
        myViewModel = new ViewModelProvider(requireActivity(), new SavedStateViewModelFactory(requireActivity()
                .getApplication(), this)).get(MyViewModel.class); //新版大长式子恶心人 TvT ~~~
        FragmentTitleBinding binding;
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_title,container,false);
        binding.setData(myViewModel);
        binding.setLifecycleOwner(requireActivity());
        binding.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                NavController controller = Navigation.findNavController(v);
                controller.navigate(R.id.action_titleFragment_to_questionFragment);
            }
        });
        return binding.getRoot();  //返回值为View类型
    }
}

答题界面:

import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.SavedStateViewModelFactory;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.calculationtest.databinding.FragmentQuestionBinding;
...
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        final MyViewModel myViewModel;
        myViewModel = new ViewModelProvider(requireActivity(), new SavedStateViewModelFactory(requireActivity()
                .getApplication(), this)).get(MyViewModel.class);
        myViewModel.generator();
        myViewModel.getCurrentScore().setValue(0);
        final FragmentQuestionBinding binding;
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_question,container,false);
        binding.setData(myViewModel);
        binding.setLifecycleOwner(requireActivity());
        //借用stringbuilder来处理用户输入的数据
        final StringBuilder builder = new StringBuilder();
        View.OnClickListener listener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switch (v.getId()){
                    case R.id.button0:
                        builder.append(0);
                        break;
                    case R.id.button1:
                        builder.append(1);
                        break;
                    case R.id.button2:
                        builder.append(2);
                        break;
                    case R.id.button3:
                        builder.append(3);
                        break;
                    case R.id.button4:
                        builder.append(4);
                        break;
                    case R.id.button5:
                        builder.append(5);
                        break;
                    case R.id.button6:
                        builder.append(6);
                        break;
                    case R.id.button7:
                        builder.append(7);
                        break;
                    case R.id.button8:
                        builder.append(8);
                        break;
                    case R.id.button9:
                        builder.append(9);
                        break;
                    case R.id.buttonClear:
                        builder.setLength(0);
                        //或者builder =  new StringBuilder();
                        break;
                }
            if (builder.length() == 0){
                binding.textView9.setText(R.string.input_indicator);
            }else {
                binding.textView9.setText(builder.toString());
            }
            }
        };

        binding.button0.setOnClickListener(listener);
        binding.button1.setOnClickListener(listener);
        binding.button2.setOnClickListener(listener);
        binding.button3.setOnClickListener(listener);
        binding.button4.setOnClickListener(listener);
        binding.button5.setOnClickListener(listener);
        binding.button6.setOnClickListener(listener);
        binding.button7.setOnClickListener(listener);
        binding.button8.setOnClickListener(listener);
        binding.button9.setOnClickListener(listener);
        binding.buttonClear.setOnClickListener(listener);

        binding.buttonSubmit.setOnClickListener(new View.OnClickListener() {
            @SuppressWarnings("ConstantConditions")
            @Override
            public void onClick(View v) {
              if (builder.length() == 0) {
                    builder.append("-1"); //当前加减法算式中不可能有负数答案,因此设为-1,来作为不输入直接确定的错误答案
                }
                if (Integer.valueOf(builder.toString()).intValue() == myViewModel.getAnswer().getValue()){ //转换为简单int型作比较,如果什么都不输入会返回空值,需要在此之前做拦截
                    myViewModel.answerCorrect();
                    builder.setLength(0);
                    binding.textView9.setText(R.string.answer_correct_message);
                    //builder.append(getString(R.string.answer_correct_message));
                }else {
                    NavController controller = Navigation.findNavController(v);
                    if (myViewModel.win_flag){
                        controller.navigate(R.id.action_questionFragment_to_winFragment);
                        myViewModel.win_flag = false;
                        myViewModel.save();
                    }else {
                        controller.navigate(R.id.action_questionFragment_to_loseFragment);
                    }
                }
            }
        });
        return binding.getRoot();
    }
}

创造新纪录界面:

import android.os.Bundle;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.SavedStateViewModelFactory;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.calculationtest.databinding.FragmentWinBinding;
...
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        MyViewModel myViewModel = new ViewModelProvider(requireActivity(),new SavedStateViewModelFactory(requireActivity()
                .getApplication(),this)).get(MyViewModel.class);
        FragmentWinBinding binding;
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_win, container, false);
        binding.setData(myViewModel);
        binding.setLifecycleOwner(requireActivity());

        binding.button11.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Navigation.findNavController(v).navigate(R.id.action_winFragment_to_titleFragment);
            }
        });
        return binding.getRoot();
    }
}

挑战失败当前得分界面:

import android.os.Bundle;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.SavedStateViewModelFactory;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.calculationtest.databinding.FragmentLoseBinding;
...
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        MyViewModel myViewModel = new ViewModelProvider(requireActivity(),new SavedStateViewModelFactory(requireActivity()
                .getApplication(),this)).get(MyViewModel.class);
        final FragmentLoseBinding binding;
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_lose, container, false);
        binding.setData(myViewModel);
        binding.setLifecycleOwner(requireActivity());

        binding.button10.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Navigation.findNavController(v).navigate(R.id.action_loseFragment_to_titleFragment);
            }
        });
        return binding.getRoot();
    }
}
  1. 完善ActionBar实现误操作提示——界面人性化返回:在MainActivity中填写代码
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.NavigationUI;
import android.content.DialogInterface;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    NavController controller;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //左上角的返回按键
        controller = Navigation.findNavController(this,R.id.fragment);
        NavigationUI.setupActionBarWithNavController(this,controller);
    }

    @Override //响应返回按键实现询问是否返回?
    public boolean onSupportNavigateUp() {
        if (controller.getCurrentDestination().getId() == R.id.questionFragment){  //答题界面实现询问返回
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle(getString(R.string.quit_dialog_title));
            builder.setPositiveButton(R.string.dialog_positive_message, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    controller.navigateUp();
                }
            });
            builder.setNegativeButton(R.string.dialog_negative_message, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {

                }
            });
            AlertDialog dialog = builder.create(); //创建对话框并显示
            dialog.show();
        }else if (controller.getCurrentDestination().getId() == R.id.titleFragment) {  //标题界面实现退出程序
            finish();
        }
        else {  //其他界面(输赢界面)实现跳转到标题界面
            controller.navigate(R.id.titleFragment);
        }
        return super.onSupportNavigateUp();
    }

    @Override  //设备自带返回按键做处理,继承上面的即可
    public void onBackPressed() {
        onSupportNavigateUp();
        //super.onBackPressed();
    }
}
  1. String.xml实现汉化:此处也附上当前使用的dimens
    dimens.xml:


    60sp
    40sp
    30sp
    20sp

strings.xml


    CalculationTest
    
    Hello blank fragment
    Calculation Test
    title_image
    Enter
    High Score: %d
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    C
    OK
    =
    \?
    Score:%d
    Your answer
    lose image
    win message
    You Lose!
    You Win!!!
    Your Score:%d
    New Record:%d
    Back
    Correct!Go on!
    Are you sure to quit?
    OK
    Cancle
    

在string页面点击右上角的open editor,寻找中文并选择,在多出来的文本框中填写与英文对应的中文,阿拉伯数字与运算符可以设置为唯一:
安卓口算测试app_第9张图片
安卓口算测试app_第10张图片
string.xml(zh-rCN)



    口算测试
    回答正确!请继续!
    清除
    确定
    返回
    得分:%d
    取消
    确定
    最高记录:%d
    请开始答题
    挑战失败!
    你的得分:%d
    确定退出吗?
    开始挑战
    口算测试
    挑战成功!!!
    新纪录:%d

  1. fragement名称更改:在导航图中的图形化界面中选中对应的fragement的label进行更改,也可以创建资源进行系统化管理



    
        
    
    
        
        
    
    
        
    
    
        
    

运行程序

各个界面测试如下:
标题界面:
安卓口算测试app_第11张图片
答题界面:
安卓口算测试app_第12张图片安卓口算测试app_第13张图片
安卓口算测试app_第14张图片
挑战成功界面:
安卓口算测试app_第15张图片
挑战失败界面:
安卓口算测试app_第16张图片

横屏处理

当前如果横屏的话:安卓口算测试app_第17张图片
界面全部被打乱,不是我们想要的结果,因此需要设置横屏布局(如果强制不允许横屏处理需要在AndroidManifest.xml配置文件中设置相对应的Activity就可以):


            
                

                
            
        
    

对每一个页面做单独的横板副本(根据需求做即可,此处不做代码的粘贴):
安卓口算测试app_第18张图片

结束

目前还有一个小bug的问题就是在横竖屏切换的时候,问答界面中的算式会因为重新构建界面而刷新界面,因此界面上的式子也会因此而变动。

你可能感兴趣的:(app,android,studio)