为了将之前学过的ViewModel,Fragement,本地化,横竖屏设置等结合在一起,做一个安卓口算的app,可以用来给家里的小学生们做练习用,那么开始吧~
dependencies {
...
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0'
}
android {
...
defaultConfig {
...
dataBinding.enabled true
}
...
}
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();
}
}
...
(2) 答题界面的数据绑定——当前得分、出题数字与运算符:
...
...
...
(3) 挑战成功创造新纪录的界面——新纪录高分:
...
...
(4) 挑战失败没有创造新纪录显示本次得分:
...
...
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();
}
}
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();
}
}
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,寻找中文并选择,在多出来的文本框中填写与英文对应的中文,阿拉伯数字与运算符可以设置为唯一:
string.xml(zh-rCN)
口算测试
回答正确!请继续!
清除
确定
返回
得分:%d
取消
确定
最高记录:%d
请开始答题
挑战失败!
你的得分:%d
确定退出吗?
开始挑战
口算测试
挑战成功!!!
新纪录:%d
各个界面测试如下:
标题界面:
答题界面:
挑战成功界面:
挑战失败界面:
当前如果横屏的话:
界面全部被打乱,不是我们想要的结果,因此需要设置横屏布局(如果强制不允许横屏处理需要在AndroidManifest.xml配置文件中设置相对应的Activity就可以):
对每一个页面做单独的横板副本(根据需求做即可,此处不做代码的粘贴):
目前还有一个小bug的问题就是在横竖屏切换的时候,问答界面中的算式会因为重新构建界面而刷新界面,因此界面上的式子也会因此而变动。