2048游戏
- 项目简介
- Config
- Constants
- GameActivity
- GameItem
- GameView
- MainActivity
- ScreenUtils
- 布局
- activity_game.xml
- activity_main.xml
项目简介
选择难度,生成随机数字,通关上下左右滑动,合并相同的数字,直到达到目标数字即可通关游戏,选择界面如下
游戏界面如下
项目结构如下
Config
public class Config extends Application {
private static SharedPreferences mSp;
@Override
public void onCreate() {
super.onCreate();
mSp = getSharedPreferences(Constants.SP_GAME, MODE_PRIVATE);
}
public int getTargetScore() {
return mSp.getInt(Constants.KEY_Target_Score, Score.Score2048.getValue());
}
public void setTargetScore(int targetScore) {
SharedPreferences.Editor editor = mSp.edit();
editor.putInt(Constants.KEY_Target_Score, targetScore);
editor.apply();
}
public int getGameLines() {
return mSp.getInt(Constants.KEY_GAME_LINES, GameLines.GameLines4.getValue());
}
public void setGameLines(int gameLines) {
SharedPreferences.Editor editor = mSp.edit();
editor.putInt(Constants.KEY_GAME_LINES, gameLines);
editor.apply();
}
public int getHighestScore() {
return mSp.getInt(Constants.KEY_HIGHEST_SCORE, 0);
}
public void setHighestScore(int highestScore) {
SharedPreferences.Editor editor = mSp.edit();
editor.putInt(Constants.KEY_HIGHEST_SCORE, highestScore);
editor.apply();
}
}
Constants
public class Constants {
public static final String PARA_GAME_LINES = "para_game_lines";
public static final String PARA_TARGET_SCORE = "para_target_score";
public static final String SP_GAME = "sp_game";
public static final String KEY_HIGHEST_SCORE = "key_highest_score";
public static final String KEY_GAME_LINES = "key_game_lines";
public static final String KEY_Target_Score = "key_target_score";
public static final GameLines DEFAULT_GAME_LINES = GameLines.GameLines4;
public static final Score DEFAULT_SCORE = Score.Score2048;
public enum GameLines {
GameLines4(4), GameLines5(5);
private int value;
GameLines(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public enum Score {
Score2048(2048), Score4049(4096);
private int value;
Score(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public enum STATE {
FAILED, NORMAL, SUCCESS
}
}
GameActivity
public class GameActivity extends AppCompatActivity implements View.OnClickListener, GameView.GameCallBack {
private TextView mCurrentScore;
private TextView mHighestScore;
private TextView mTargetScore;
private Button mRevert;
private Button mRestart;
private GameView mGame;
private Config mConfig;
private boolean isRevert = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
initView();
initData();
}
private void initView() {
mCurrentScore = findViewById(R.id.current_score);
mHighestScore = findViewById(R.id.highest_score);
mTargetScore = findViewById(R.id.target_score);
mRevert = findViewById(R.id.revert);
mRestart = findViewById(R.id.restart);
mGame = findViewById(R.id.game);
}
private void initData() {
mConfig = new Config();
mRevert.setOnClickListener(this);
mRestart.setOnClickListener(this);
mGame.setOnGameCallBack(this);
mCurrentScore.setText("当前分数: " + 0);
mHighestScore.setText("最高分数: " + mConfig.getHighestScore());
mTargetScore.setText("目标分数: " + mConfig.getTargetScore());
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.revert:
if (isRevert) {
mGame.revertGame();
isRevert = false;
} else {
Toast.makeText(this, "只能撤销一次", Toast.LENGTH_SHORT).show();
}
break;
case R.id.restart:
mGame.restartGame();
break;
}
}
@Override
public void onScoreChange(int currentScore) {
mCurrentScore.setText("当前分数: " + currentScore);
mHighestScore.setText("最高分数: " + mConfig.getHighestScore());
}
@Override
public void onCheckGameFinish(Constants.STATE state) {
switch (state) {
case SUCCESS:
showFinishDialog("成功通关");
break;
case FAILED:
showFinishDialog("挑战失败");
break;
case NORMAL:
break;
}
}
@Override
public void onOpenRevert() {
isRevert = true;
}
private void showFinishDialog(String msg) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(msg);
builder.setCancelable(false);
builder.setNegativeButton("再来一次", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mGame.restartGame();
dialog.dismiss();
}
});
builder.setPositiveButton("退出游戏", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
finish();
}
});
builder.show();
}
}
GameItem
public class GameItem extends FrameLayout {
private int mNum;
private TextView mNumCard;
private LayoutParams mParams;
private Config mConfig;
public GameItem(@NonNull Context context) {
this(context, null);
}
public GameItem(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public GameItem(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public GameItem(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initCardItem();
}
private void initCardItem() {
mConfig = new Config();
setBackgroundColor(Color.GRAY);
mNumCard = new TextView(getContext());
switch (mConfig.getGameLines()) {
case 4:
mNumCard.setTextSize(35);
break;
case 5:
mNumCard.setTextSize(25);
break;
default:
mNumCard.setTextSize(20);
break;
}
TextPaint tp = mNumCard.getPaint();
tp.setFakeBoldText(true);
mNumCard.setGravity(Gravity.CENTER);
mParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mParams.setMargins(5, 5, 5, 5);
addView(mNumCard, mParams);
}
public void setNum(int num) {
this.mNum = num;
if (num == 0) {
mNumCard.setText("");
} else {
mNumCard.setText(String.valueOf(num));
}
switch (num) {
case 0:
mNumCard.setBackgroundColor(0x00000000);
break;
case 2:
mNumCard.setBackgroundColor(0xffeee5db);
break;
case 4:
mNumCard.setBackgroundColor(0xffeee0ca);
break;
case 8:
mNumCard.setBackgroundColor(0xfff2c17a);
break;
case 16:
mNumCard.setBackgroundColor(0xfff59667);
break;
case 32:
mNumCard.setBackgroundColor(0xfff38c6f);
break;
case 64:
mNumCard.setBackgroundColor(0xfff66e3c);
break;
case 128:
mNumCard.setBackgroundColor(0xffedcf74);
break;
case 256:
mNumCard.setBackgroundColor(0xffedcc64);
break;
case 512:
mNumCard.setBackgroundColor(0xffedc854);
break;
case 1024:
mNumCard.setBackgroundColor(0xffedc54f);
break;
case 2048:
mNumCard.setBackgroundColor(0xffedc32e);
break;
default:
mNumCard.setBackgroundColor(0xff3c4a34);
break;
}
}
public int getNum() {
return mNum;
}
}
GameView
public class GameView extends GridLayout implements View.OnTouchListener {
private static final String TAG = "GameView";
private int mHistoryScore;
private int mCurrentScore;
private int mGameLines;
private GameItem[][] mGameMatrix;
private int[][] mGameMatrixHistory;
private ArrayList<Point> mBlankItem;
private int mStartX;
private int mStartY;
private int mEndX;
private int mEndY;
private int mTargetScore;
private Config mConfig;
private GameCallBack mCallBack;
private int mHistoryHighestScore;
public GameView(Context context) {
this(context, null);
}
public GameView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GameView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public GameView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initConfig();
initView();
initData();
initGame();
}
private void initConfig() {
mConfig = new Config();
mGameLines = mConfig.getGameLines();
mTargetScore = mConfig.getTargetScore();
Log.d(TAG, "initConfig: mGameLines = " + mGameLines);
Log.d(TAG, "initConfig: mTargetScore = " + mTargetScore);
}
private void initView() {
removeAllViews();
setColumnCount(mGameLines);
setRowCount(mGameLines);
setOnTouchListener(this);
}
private void initData() {
mGameMatrix = new GameItem[mGameLines][mGameLines];
mGameMatrixHistory = new int[mGameLines][mGameLines];
mBlankItem = new ArrayList<Point>();
}
private void initGame() {
GameItem item;
for (int i = 0; i < mGameLines; i++) {
for (int j = 0; j < mGameLines; j++) {
item = new GameItem(getContext());
item.setNum(0);
addView(item, ScreenUtils.getScreenWidthPixels(getContext()) / mGameLines, ScreenUtils.getScreenWidthPixels(getContext()) / mGameLines);
mGameMatrix[i][j] = item;
mBlankItem.add(new Point(i, j));
}
}
addRandomNum();
addRandomNum();
updateScore();
}
private void addRandomNum() {
getBlanks();
if (mBlankItem.size() > 0) {
int randomNum = (int) (Math.random() * mBlankItem.size());
Point randomPoint = mBlankItem.get(randomNum);
mGameMatrix[randomPoint.x][randomPoint.y].setNum(Math.random() > 0.2d ? 2 : 4);
animCreate(mGameMatrix[randomPoint.x][randomPoint.y]);
}
}
private void addSuperNum(int superNum) {
getBlanks();
if (mBlankItem.size() > 0) {
int randomNum = (int) (Math.random() * mBlankItem.size());
Point randomPoint = mBlankItem.get(randomNum);
mGameMatrix[randomPoint.x][randomPoint.y].setNum(superNum);
animCreate(mGameMatrix[randomPoint.x][randomPoint.y]);
}
}
private void animCreate(GameItem item) {
ScaleAnimation sa = new ScaleAnimation(0.1f, 1, 0.1f, 1,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
sa.setDuration(100);
item.startAnimation(sa);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG, "onTouch: ");
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartX = (int) event.getX();
mStartY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
mEndX = (int) event.getX();
mEndY = (int) event.getY();
Log.d(TAG, "onTouch: start = (" + mStartX + "," + mStartY + ")");
Log.d(TAG, "onTouch: end = (" + mEndX + "," + mEndY + ")");
judgeDirection(mEndX - mStartX, mEndY - mStartY);
if (isMoved()) {
updateScore();
}
break;
default:
break;
}
return true;
}
private void updateScore() {
for (GameItem[] gameMatrix : mGameMatrix) {
for (GameItem matrix : gameMatrix) {
if (matrix.getNum() > mCurrentScore) {
mCurrentScore = matrix.getNum();
}
}
}
if (mCurrentScore > mConfig.getHighestScore()) {
mConfig.setHighestScore(mCurrentScore);
}
if (mCallBack != null) {
mCallBack.onOpenRevert();
mCallBack.onScoreChange(mCurrentScore);
mCallBack.onCheckGameFinish(checkCompleted());
}
}
private void judgeDirection(int offsetX, int offsetY) {
Log.d(TAG, "judgeDirection: offsetX = " + offsetX);
Log.d(TAG, "judgeDirection: offsetY = " + offsetY);
int density = ScreenUtils.getScreenMetrics(getContext());
int slideDis = 5 * density;
int maxDis = ScreenUtils.getScreenWidthPixels(getContext()) - 50;
Log.d(TAG, "judgeDirection: slideDis = " + slideDis);
Log.d(TAG, "judgeDirection: maxDis = " + maxDis);
boolean flagNormal = (Math.abs(offsetX) > slideDis || Math.abs(offsetY) > slideDis)
&& Math.abs(offsetX) < maxDis
&& Math.abs(offsetY) < maxDis;
boolean flagSuper = Math.abs(offsetX) > maxDis || Math.abs(offsetY) > maxDis;
Log.d(TAG, "judgeDirection: flagNormal = " + flagNormal);
Log.d(TAG, "judgeDirection: flagSuper = " + flagSuper);
if (flagNormal || flagSuper) {
saveHistoryMatrix();
}
if (flagNormal && !flagSuper) {
if (Math.abs(offsetX) > Math.abs(offsetY)) {
if (offsetX > slideDis) {
swipeRight();
} else {
swipeLeft();
}
} else {
if (offsetY > slideDis) {
swipeDown();
} else {
swipeUp();
}
}
addRandomNum();
} else if (flagSuper) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
EditText et = new EditText(getContext());
builder.setTitle("Back Door")
.setView(et)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (!TextUtils.isEmpty(et.getText())) {
addSuperNum(Integer.parseInt(et.getText().toString()));
if (mCallBack != null) {
mCallBack.onCheckGameFinish(checkCompleted());
}
}
}
})
.setNegativeButton("ByeBye", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).create().show();
}
}
private void swipeLeft() {
for (int i = 0; i < mGameLines; i++) {
int baseNum = -1;
List<Integer> tempList = new ArrayList<>();
for (int j = 0; j < mGameLines; j++) {
int currentNum = mGameMatrix[i][j].getNum();
if (currentNum != 0) {
if (baseNum == -1) { //选取第一个非0数字作为基准
baseNum = currentNum;
continue;
}
if (baseNum == currentNum) { //如果当前数字等于前一个数字,则合并
tempList.add(baseNum * 2);
baseNum = -1;
} else { //如果当前数字不等于前一个数字,则基准数字后移
tempList.add(baseNum);
baseNum = currentNum;
}
}
}
if (baseNum != -1) {
tempList.add(baseNum);
}
for (int j = 0; j < mGameLines; j++) {
if (j < tempList.size()) {
mGameMatrix[i][j].setNum(tempList.get(j));
} else {
mGameMatrix[i][j].setNum(0);
}
}
}
}
private void swipeRight() {
for (int i = 0; i < mGameLines; i++) {
int baseNum = -1;
List<Integer> tempList = new ArrayList<>();
for (int j = mGameLines - 1; j >= 0; j--) {
int currentNum = mGameMatrix[i][j].getNum();
if (currentNum != 0) {
if (baseNum == -1) { //选取第一个非0数字作为基准
baseNum = currentNum;
continue;
}
if (baseNum == currentNum) { //如果当前数字等于前一个数字,则合并
tempList.add(baseNum * 2);
baseNum = -1;
} else { //如果当前数字不等于前一个数字,则基准数字后移
tempList.add(baseNum);
baseNum = currentNum;
}
}
}
if (baseNum != -1) {
tempList.add(baseNum);
}
for (int j = 0; j < mGameLines; j++) {
if (j < tempList.size()) {
mGameMatrix[i][mGameLines - 1 - j].setNum(tempList.get(j));
} else {
mGameMatrix[i][mGameLines - 1 - j].setNum(0);
}
}
}
}
private void swipeDown() {
for (int i = 0; i < mGameLines; i++) {
int baseNum = -1;
List<Integer> tempList = new ArrayList<>();
for (int j = mGameLines - 1; j >= 0; j--) {
int currentNum = mGameMatrix[j][i].getNum();
if (currentNum != 0) {
if (baseNum == -1) { //选取第一个非0数字作为基准
baseNum = currentNum;
continue;
}
if (baseNum == currentNum) { //如果当前数字等于前一个数字,则合并
tempList.add(baseNum * 2);
baseNum = -1;
} else { //如果当前数字不等于前一个数字,则基准数字后移
tempList.add(baseNum);
baseNum = currentNum;
}
}
}
if (baseNum != -1) {
tempList.add(baseNum);
}
for (int j = 0; j < mGameLines; j++) {
if (j < tempList.size()) {
mGameMatrix[mGameLines - 1 - j][i].setNum(tempList.get(j));
} else {
mGameMatrix[mGameLines - 1 - j][i].setNum(0);
}
}
}
}
private void swipeUp() {
for (int i = 0; i < mGameLines; i++) {
int baseNum = -1;
List<Integer> tempList = new ArrayList<>();
for (int j = 0; j < mGameLines; j++) {
int currentNum = mGameMatrix[j][i].getNum();
if (currentNum != 0) {
if (baseNum == -1) { //选取第一个非0数字作为基准
baseNum = currentNum;
continue;
}
if (baseNum == currentNum) { //如果当前数字等于前一个数字,则合并
tempList.add(baseNum * 2);
baseNum = -1;
} else { //如果当前数字不等于前一个数字,则基准数字后移
tempList.add(baseNum);
baseNum = currentNum;
}
}
}
if (baseNum != -1) {
tempList.add(baseNum);
}
for (int j = 0; j < mGameLines; j++) {
if (j < tempList.size()) {
mGameMatrix[j][i].setNum(tempList.get(j));
} else {
mGameMatrix[j][i].setNum(0);
}
}
}
}
private void saveHistoryMatrix() {
mHistoryScore = mCurrentScore;
mHistoryHighestScore = mConfig.getHighestScore();
for (int i = 0; i < mGameLines; i++) {
for (int j = 0; j < mGameLines; j++) {
mGameMatrixHistory[i][j] = mGameMatrix[i][j].getNum();
}
}
}
private boolean isMoved() {
for (int i = 0; i < mGameLines; i++) {
for (int j = 0; j < mGameLines; j++) {
if (mGameMatrixHistory[i][j] != mGameMatrix[i][j].getNum()) {
return true;
}
}
}
return false;
}
public void revertGame() {
int sum = 0;
for (int[] element : mGameMatrixHistory) {
for (int i : element) {
sum += i;
}
}
if (sum != 0) {
mCurrentScore = mHistoryScore;
mConfig.setHighestScore(mHistoryHighestScore);
if (mCallBack != null) {
mCallBack.onScoreChange(mCurrentScore);
}
for (int i = 0; i < mGameLines; i++) {
for (int j = 0; j < mGameLines; j++) {
mGameMatrix[i][j].setNum(mGameMatrixHistory[i][j]);
}
}
}
}
public void restartGame() {
removeAllViews();
mCurrentScore = 0;
mHistoryScore = 0;
for (int i = 0; i < mGameMatrixHistory.length; i++) {
for (int j = 0; j < mGameMatrixHistory.length; j++) {
mGameMatrixHistory[i][j] = 0;
}
}
mBlankItem.clear();
initGame();
}
private void getBlanks() {
mBlankItem.clear();
for (int i = 0; i < mGameLines; i++) {
for (int j = 0; j < mGameLines; j++) {
if (mGameMatrix[i][j].getNum() == 0) {
mBlankItem.add(new Point(i, j));
}
}
}
}
private STATE checkCompleted() {
for (int i = 0; i < mGameLines; i++) {
for (int j = 0; j < mGameLines; j++) {
if (mGameMatrix[i][j].getNum() == mTargetScore) {
return STATE.SUCCESS;
}
}
}
getBlanks();
if (mBlankItem.size() == 0) {
for (int i = 0; i < mGameLines; i++) {
for (int j = 0; j < mGameLines; j++) {
if (j < mGameLines - 1) {
if (mGameMatrix[i][j].getNum() == mGameMatrix[i][j + 1].getNum()) {
return STATE.NORMAL;
}
}
if (i < mGameLines - 1) {
if (mGameMatrix[i][j].getNum() == mGameMatrix[i + 1][j].getNum()) {
return STATE.NORMAL;
}
}
}
}
return STATE.FAILED;
}
return STATE.NORMAL;
}
public interface GameCallBack {
void onScoreChange(int currentScore);
void onCheckGameFinish(STATE state);
void onOpenRevert();
}
public void setOnGameCallBack(GameCallBack callBack) {
mCallBack = callBack;
}
}
MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView mRegionSelector;
private TextView mTargetScoreSelector;
private TextView mBack;
private TextView mStartGame;
private GameLines mGameLines = Constants.DEFAULT_GAME_LINES;
private Score mTargetScore = Constants.DEFAULT_SCORE;
private Config mConfig;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
initListener();
}
private void initView() {
mRegionSelector = findViewById(R.id.game_lines_selector);
mTargetScoreSelector = findViewById(R.id.target_score_selector);
mBack = findViewById(R.id.back);
mStartGame = findViewById(R.id.startGame);
}
private void initData() {
mConfig = new Config();
mRegionSelector.setText(mGameLines.getValue() + " × " + mGameLines.getValue());
mTargetScoreSelector.setText(String.valueOf(mTargetScore.getValue()));
}
private void initListener() {
mRegionSelector.setOnClickListener(this);
mTargetScoreSelector.setOnClickListener(this);
mBack.setOnClickListener(this);
mStartGame.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.game_lines_selector:
if (mGameLines == GameLines.GameLines4) {
mGameLines = GameLines.GameLines5;
} else if (mGameLines == GameLines.GameLines5) {
mGameLines = GameLines.GameLines4;
}
mRegionSelector.setText(mGameLines.getValue() + " × " + mGameLines.getValue());
break;
case R.id.target_score_selector:
if (mTargetScore == Score.Score2048) {
mTargetScore = Score.Score4049;
} else if (mTargetScore == Score.Score4049) {
mTargetScore = Score.Score2048;
}
mTargetScoreSelector.setText(String.valueOf(mTargetScore.getValue()));
break;
case R.id.back:
finish();
break;
case R.id.startGame:
//GameActivity.startGameActivity(this, mGameLines.getValue(), mTargetScore.getValue());
mConfig.setGameLines(mGameLines.getValue());
mConfig.setTargetScore(mTargetScore.getValue());
mConfig.setHighestScore(0);
startActivity(new Intent(this, GameActivity.class));
break;
}
}
}
ScreenUtils
public class ScreenUtils {
public static DisplayMetrics getDisplayMetrics(Context context) {
DisplayMetrics metrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
display.getMetrics(metrics);
return metrics;
}
public static int getScreenMetrics(Context context) {
return (int) getDisplayMetrics(context).density;
}
public static int getScreenWidthPixels(Context context) {
return getDisplayMetrics(context).widthPixels;
}
public static int getScreenHeightPixels(Context context) {
return getDisplayMetrics(context).heightPixels;
}
}
布局
activity_game.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<TextView
android:id="@+id/current_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#33000000"
android:padding="10dp"
android:text="当前分数"
android:textSize="15sp" />
<TextView
android:id="@+id/highest_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#33000000"
android:padding="10dp"
android:text="最高分数"
android:textSize="15sp" />
<TextView
android:id="@+id/target_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#33000000"
android:padding="10dp"
android:text="目标分数"
android:textSize="15sp" />
</LinearLayout>
<com.demo.demo0.GameView
android:id="@+id/game"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/ll_btns"
android:layout_below="@id/ll_title" />
<LinearLayout
android:id="@+id/ll_btns"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center">
<Button
android:id="@+id/revert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="30dp"
android:background="#33000000"
android:text="撤销"
android:textSize="30sp" />
<Button
android:id="@+id/restart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="30dp"
android:background="#33000000"
android:text="重来"
android:textSize="30sp" />
</LinearLayout>
</RelativeLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<TextView
android:id="@+id/current_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#33000000"
android:padding="10dp"
android:text="当前分数"
android:textSize="15sp" />
<TextView
android:id="@+id/highest_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#33000000"
android:padding="10dp"
android:text="最高分数"
android:textSize="15sp" />
<TextView
android:id="@+id/target_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#33000000"
android:padding="10dp"
android:text="目标分数"
android:textSize="15sp" />
</LinearLayout>
<com.demo.demo0.GameView
android:id="@+id/game"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/ll_btns"
android:layout_below="@id/ll_title" />
<LinearLayout
android:id="@+id/ll_btns"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center">
<Button
android:id="@+id/revert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="30dp"
android:background="#33000000"
android:text="撤销"
android:textSize="30sp" />
<Button
android:id="@+id/restart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="30dp"
android:background="#33000000"
android:text="重来"
android:textSize="30sp" />
</LinearLayout>
</RelativeLayout>