想看完整教程和成果展示关注哔哩哔哩up主: Alvin_Han233
写在前面:作者本人澳洲大学毕业,下半年再出去读研,回国隔离的时候太无聊就录了一个这个项目的demo,结果我没想到的是要代码的人还挺多。我也是百忙(我现在既工作又上学)之中抽出了点时间做的视频,写的文章。喜欢的话关注一波up (ღ( ´・ᴗ・` ))。这个app是在本人大四上学期的时候做的,隔得时间有点久,可能有些生疏。但up保证这个app的代码绝对无bug,能跑通!
1. 首先六个page,六个activity:
-MainActivity就是主页;
-GameActivity就进入了quiz的界面;
-SettingActivity是设置难中易的界面;
-TwitterActivity是点完post to 推t之后的界面;
-HighscoreActivity是高分页(这里我用了一点数据库SQL语言-- 我们的作业要求);
-ResultActivity是每次玩完的得分展示。
此外每次的得分都可以post到推t上,我们需要创建一个resource folder,把Dummy(此项目用的账号)的Token,key都放进这个文件中,才能用推*这个功能。这块代码我不会发的(别打我)没经过老师允许,不能发这个。
MainActivity:
在主页中,我们可以点击跳转到setting,play,highscore页,下面的instruction用了dialog实现的。
level的设置----在settingpage中用了一个intent,再把button点击的level放进intent中,key就叫"level",mainactivity就得到了level。
package com.example.educationalapp;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private String level;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bundle levelData = getIntent().getExtras();
if (levelData == null) {
return;
}
// get the level from setting page
level = levelData.getString("level", "Medium");
TextView currentLevel = findViewById(R.id.CurrentLevel);
currentLevel.setText(level); // update the text, display the level
}
// To enable the menu, in mainActivity add the following callback to inflate the menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
return super.onCreateOptionsMenu(menu);
}
// go to setting page
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.add_item) {
Intent intent = new Intent(this, SettingActivity.class);
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
// if clicking on play button, go to Game page
public void onClickPlay(View view) {
Intent intent = new Intent(MainActivity.this, GameActivity.class);
intent.putExtra("level", level);
setResult(RESULT_OK, intent);
startActivity(intent);
}
// go to high score page
public void onClickhs(View view) {
Intent intent = new Intent(this, HighScoreActivity.class);
startActivity(intent);
}
// show the instructions
public void instructions(View v) {
AlertDialog dialog = new AlertDialog.Builder(this).create();
dialog.setTitle("Instructions");
dialog.setMessage("Here is the instruction" +
"\n\n>1. You need click setting button to select difficulty of the quiz" +
"\n\n> 2. You have 50 seconds to finish the quiz. Once timeing up, the app will automatically display result page." +
"\n\n> 3. After the quiz, you can send to twitter or not." +
"\n\n> 4. Clicking high score button to show history high score.");
dialog.setButton(AlertDialog.BUTTON_POSITIVE, "Dismiss",
(dialog1, which) -> dialog1.dismiss());
dialog.show();
}
}
SettingActivity
setting activity 中用 level = button.getText().toString();来探测user按了哪个键,再把level放进intent里。
package com.example.educationalapp;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
public class SettingActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_setting);
Button medium_btn = findViewById(R.id.medium_btn);
ActionBar actionBar = getSupportActionBar();
assert actionBar != null;
// set the behaviour sa same as bottom back button
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setDisplayUseLogoEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
Intent intent = getIntent();
boolean default1 = intent.getBooleanExtra("default", false);
if (default1) { // the default level is medium
onClickLevel(medium_btn);
}
}
// using the bardware "Back" button to cancel adding a new item
@Override
public void onBackPressed() {
Toast.makeText(this, "Nothing pressed", Toast.LENGTH_SHORT).show();
super.onBackPressed();
}
// if clicking a level, jump to home page
public void onClickLevel(View view) {
Button button = (Button) view;
String level;
level = button.getText().toString();
Intent intent = new Intent(SettingActivity.this, MainActivity.class);
intent.putExtra("level", level);
setResult(RESULT_OK, intent);
startActivity(intent);
}
}
GameActivity
之后就是最复杂的GameActivity了
上代码!
package com.example.educationalapp;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.SystemClock;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.squareup.seismic.ShakeDetector;
import java.util.Locale;
public class GameActivity extends AppCompatActivity implements ShakeDetector.Listener {
private final Game game = new Game();
private static final long COUNTDOWN_IN_MILLIES = 50000; // Pre-defined time
private ColorStateList textColorDefaultCd;
private CountDownTimer countDownTimer;
private long timeLeftInMillis;
private TextView textViewCountDown;
private ImageView mQuizImage;
private String mAnswer;
private int mScore = 0;
private int mQuizNum = 1;
private int QuestionNum;
private int initialNum;
private TextView mQuizNumView;
private int index;
private final Questions mQuestions = new Questions(); // mQuestion is object; Question is class
private String[] mCorrectAnswer;
private String[] mImage;
// initialize 2-dimension string array
private final String[][] mChoice = {{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"},{"a", "a", "a"}};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
Bundle levelData = getIntent().getExtras();
if (levelData == null) {
return;
}
// randomize the question's sequence
int[] questionSequence = randomCommon(0,11,10);
// select difficulty based on clicked button
String level = levelData.getString("level", "Medium");
initialNum = game.mQuestionSelect(level);
// the current question number
QuestionNum = initialNum + questionSequence[index] - 1;
mQuizNumView = findViewById(R.id.quiznum_textview);
textViewCountDown = findViewById(R.id.text_view_countedown);
mQuizImage = findViewById(R.id.quiz_image);
// get string resource from XML
// get string from xml: mchoices, mAnswers, mImages
String[] mChoices_3items = getResources().getStringArray(R.array.mChoice);
mCorrectAnswer = getResources().getStringArray(R.array.mCorrectAnswer);
mImage = getResources().getStringArray(R.array.mImage);
// split mChoice into three choices by comma
// the pass one choice array into mChoice
for (int i = 0; i < 30; i ++) {
mChoice[i] = mChoices_3items[i].split(",");
}
textColorDefaultCd = textViewCountDown.getTextColors();
updateQuestion();
Button submit = findViewById(R.id.button_submit);
// once this activity is called, start counting down
timeLeftInMillis = COUNTDOWN_IN_MILLIES;
// shake detecter
// it will implement hearShake()
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
ShakeDetector shakeDetector = new ShakeDetector(this);
shakeDetector.start(sensorManager);
startCountDown(); // start count down once create this activity
submit.setOnClickListener(v -> { // Quiz logic
if (game.checkCorrect(mAnswer, QuestionNum,mCorrectAnswer)) { // call the method by object
displayToastCorrectAnswer();
mScore++;
} else {
displayToastWrongAnswer();
}
SystemClock.sleep(500);
int numQuestion = mQuestions.getLength(mImage) / 3 - 1; // this number is 9 always
// index % 10 is the index in every 10 question; recall that QuestionNum is 0-30
if (game.checkLast(numQuestion, index % 10)) { // if approach the last quetion (call the method by object)
pauseCountDown();
// jump into Result Activity
Intent intent_result = new Intent(GameActivity.this, ResultActivity.class);
intent_result.putExtra("totalQuestions", mQuestions.getLength(mImage) / 3);
intent_result.putExtra("finalScore", mScore);
startActivity(intent_result);
index = 0; // initialize all into zero
mQuizNum = 0;
mScore = 0;
} else {
// if not the last question index + 1
index++;
QuestionNum = initialNum + questionSequence[index] - 1;
mQuizNum++;
updateQuestion(); // if game reach at the last question, the app wont update display
Log.i("TestRandom", String.valueOf(QuestionNum)); // it is good to include in code review
}
});
// sensor programming
// learn from: https://developer.android.com/training/gestures/detector
mQuizImage.setOnTouchListener(new View.OnTouchListener() {
GestureDetector gestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public void onLongPress(MotionEvent e) {
pauseCountDown();
Intent intent_home = new Intent(GameActivity.this, MainActivity.class); // Long press to go to home page
startActivity(intent_home);
super.onLongPress(e);
}
});
@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
return false;
}
});
}
// generate random and unique integer array, range from 1-10 (minus 1 later)
public static int[] randomCommon(int min, int max, int n){
int[] result = new int[n];
int count = 0;
while(count < n) {
int num = (int) (Math.random() * (max - min)) + min; // generate random integer inside the range
boolean flag = true;
for (int j = 0; j < n; j++) { // if the generated integer is not inside the list, append it into list
if(num == result[j]){
flag = false;
break;
}
}
if(flag){
result[count] = num;
count++;
}
}
return result;
}
// implement shakeListener
// android studio is not sensitive, you have to shake it very very very strong to see the result
@Override
public void hearShake() {
Toast.makeText(getApplicationContext(), "The answer is " + mQuestions.getCoorectAnswer(mCorrectAnswer,QuestionNum), Toast.LENGTH_SHORT).show(); // shake to show the answer
}
// clock pause function
private void pauseCountDown() {
countDownTimer.cancel();
}
// startDown(method)
private void startCountDown() {
countDownTimer = new CountDownTimer(timeLeftInMillis, 1000) { // the first parameter is the limitation of countdown; the second parameter is the time interval
@Override
public void onTick(long millisUntilFinished) {
timeLeftInMillis = millisUntilFinished; // assign the boundary
updateCountDownText();
}
@Override
public void onFinish() { // if time up, jump to other activity and stop the clock
timeLeftInMillis = 0;
updateCountDownText();
Toast.makeText(GameActivity.this, "Times up", Toast.LENGTH_LONG).show();
// if times up jump into result page and while store the result
Intent intent_result = new Intent(GameActivity.this, ResultActivity.class);
intent_result.putExtra("totalQuestions", mQuestions.getLength(mImage) / 3);
intent_result.putExtra("finalScore", mScore);
startActivity(intent_result);
SystemClock.sleep(500);
}
}.start(); //start countdown immediately once creating it.
}
// this method is used to update count down timer
private void updateCountDownText() {
int minutes = (int) (timeLeftInMillis / 1000) / 60; // this is minutes
int seconds = (int) (timeLeftInMillis / 1000) % 60; // this is seconds
String timeFormatted = String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);
textViewCountDown.setText(timeFormatted); // update the text
if (timeLeftInMillis < 10000) { // if the time is less than 10 second, text turns red
textViewCountDown.setTextColor(Color.RED);
} else {
textViewCountDown.setTextColor(textColorDefaultCd);
}
}
// 2 methods below to show if answer is correct or not
private void displayToastCorrectAnswer() {
Toast.makeText(GameActivity.this, "Perfect!", Toast.LENGTH_SHORT).show();
}
private void displayToastWrongAnswer() {
Toast.makeText(GameActivity.this, "Oh nooo!", Toast.LENGTH_SHORT).show();
}
// method to update the display
private void updateQuestion() {
LinearLayout answer_layout = findViewById(R.id.answer_layout);
answer_layout.removeAllViews(); // before view the new question we need remove the old questions
mAnswer = "";
mQuizNumView.setText(mQuizNum + "/" + mQuestions.getLength(mImage) / 3); // display the number of current quiz
showRadioButtonAnswers(QuestionNum); // call the defined function to update the answers
showMainImage(); // display chinese character
ScrollView sv = findViewById(R.id.scrollView);
sv.smoothScrollTo(0, 0);
}
// method to show the image
private void showMainImage() {
String img = mQuestions.getImage(mImage,QuestionNum); // get the image names
mQuizImage.setImageResource(getResources().getIdentifier(img, "drawable", getPackageName())); // display the characters
}
// method to show radio button answers
private void showRadioButtonAnswers(int qnum) {
int choiceIndex;
final LinearLayout answerLayout = findViewById(R.id.answer_layout);
RadioGroup rg = new RadioGroup(this);
rg.setOrientation(RadioGroup.VERTICAL);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, // HEIGHT
LinearLayout.LayoutParams.WRAP_CONTENT // WIDTH
);
rg.setLayoutParams(lp);
rg.setPadding(0, 0, 0, 0); // to ensure the choices are in the center
final RadioButton[] rb1 = new RadioButton[3]; // create a RadioButton array, 3 value button
int[] choiceSequence = randomCommon(0,4,3); // randomize the question's sequence, generate a random choice sequence
for (int i = 0; i <= 2; i++) {
rb1[i] = new RadioButton(this);
choiceIndex = choiceSequence[i] - 1; // generate random choice index, select random choice from one string array
rb1[i].setText(mQuestions.getChoice(mChoice,qnum)[choiceIndex]); // set the three choices side radio button
rb1[i].setTextColor(Color.BLACK);
rb1[i].setTextSize(25);
rb1[i].setId(i);
rb1[i].setWidth(900);
rg.addView(rb1[i]); // add into Radio button view group
}
rg.setOnCheckedChangeListener((group, id) -> {
id = choiceSequence[id] - 1; // here is my very very smart move!!! specify the id into random consequence
mAnswer = mQuestions.getChoice(mChoice,QuestionNum)[id];
});
answerLayout.addView(rg); //the method make layout add to linear
}
// if hardware press the back button, stop the watch.
@Override
protected void onPause() {
super.onPause();
pauseCountDown();
}
}
这篇文章就这么多了,大家喜欢的话,三联一波,咱们下篇再见!