安卓小游戏 2048 新手练手项目 完整代码(含注释)

看了极客的安卓2048的开发教程,大概了解了一下思路,然后自己就开始写了。后来发现这个设计思路不是太好,不方便加移动动画,就只加了创建卡片和合并的动画,不过用来练手还可以。

游戏截图如下:
在这里插入图片描述
如果想拷贝到本地运行的话,注意修改和包名相关的地方
或者在创建工程的时候按照以下命名:

项目名: Game2048
包名: pers.hurric.game2048

AndroidManifest.xml

仅需设置screenOrientation

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="pers.hurric.game2048">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    
    <TextView
        android:id="@+id/textScore"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/score"
        android:textColor="@color/colorPrimary"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toTopOf="@+id/guideline4"
        app:layout_constraintEnd_toStartOf="@+id/guideline2"
        app:layout_constraintStart_toStartOf="@+id/guideline6"
        app:layout_constraintTop_toTopOf="@+id/guideline3" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />

    <Button
        android:id="@+id/buttonReplay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/button_replay"
        android:textColor="@android:color/holo_blue_light"
        app:layout_constraintBottom_toTopOf="@+id/guideline4"
        app:layout_constraintEnd_toStartOf="@+id/guideline7"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline3" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.12" />

    <TextView
        android:id="@+id/textHighestScore"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/highest_score"
        android:textColor="@android:color/holo_purple"
        android:textSize="18sp"
        android:textStyle="italic"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintEnd_toStartOf="@+id/guideline7"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="parent" />

    <pers.hurric.game2048.GameView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@+id/guideline5"
        app:layout_constraintEnd_toStartOf="@+id/guideline7"
        app:layout_constraintStart_toStartOf="@+id/guideline6"
        app:layout_constraintTop_toTopOf="@+id/guideline4" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.26" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.9" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.05109489" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.95" />
</androidx.constraintlayout.widget.ConstraintLayout>

string.xml

<resources>
    <string name="app_name">Game2048</string>
    <string name="score">Score : 0</string>
    <string name="highest_score">Highest Score : 0</string>
    <string name="button_replay">Restart</string>
</resources>

color.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
    <color name="gameViewBackgroundColor">#D2B48C</color>
    <color name="backgroundColor0">#DEB887</color>
    <color name="textColor0">#00000000</color>
    <color name="textColor2">#333300</color>
    <color name="textColor4">#333300</color>
    <color name="textColorCommon">#FFFAFA</color>
    <color name="backgroundColor2">#FDF5E6</color>
    <color name="backgroundColor4">#FFE4B5</color>
    <color name="backgroundColor8">#FFE4C4</color>
    <color name="backgroundColor16">#FFDAB9</color>
    <color name="backgroundColor32">#FF7F50</color>
    <color name="backgroundColor64">#FF4500</color>
    <color name="backgroundColor128">#FF8C00</color>
    <color name="backgroundColor256">#FFD700</color>
    <color name="backgroundColor512">#9ACD32</color>
    <color name="backgroundColor1024">#483D8B</color>
    <color name="backgroundColor2048">#4B0082</color>
    <color name="backgroundColorBiggerThan2048">#000000</color>
</resources>

MainActivity.java

package pers.hurric.game2048;

import androidx.appcompat.app.AppCompatActivity;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private final String FILE_NAME = "MyData";
    private final String HIGHEST_SCORE = "highest_score";

    private TextView textScore;
    private TextView textHighestScore;
    private Button buttonReplay;

    private int score = 0;
    private int highestScore = 0;

    public static MainActivity mainActivity;

    public MainActivity(){
        mainActivity = this;
    }

    public void addScore(int score) {
        this.score += score;
        textScore.setText("Score : " + this.score);
        //更新最高分
        updateHighestScore(this.score);
    }

    private void updateHighestScore(int score){
        if(score > highestScore){
            highestScore = score;
            textHighestScore.setText("HighestScore : " + score);
			//存储最高分
            SharedPreferences shp = getSharedPreferences(FILE_NAME, MODE_PRIVATE);
            SharedPreferences.Editor editor = shp.edit();

            editor.putInt(HIGHEST_SCORE, highestScore);
            editor.apply();
        }
    }

    public void clearScore(){
        score = 0;
        textScore.setText("Score : " + 0);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textScore = findViewById(R.id.textScore);
        textHighestScore = findViewById(R.id.textHighestScore);
        buttonReplay = findViewById(R.id.buttonReplay);

		//读取最高分
        SharedPreferences shp = getSharedPreferences(FILE_NAME, MODE_PRIVATE);
        highestScore = shp.getInt(HIGHEST_SCORE, 0);
        textHighestScore.setText("HighestScore : " + highestScore);

        buttonReplay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                GameView.gameView.replayGame();
            }
        });
    }

    private boolean isExit = false;

    class ExitHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == 0){
                isExit = false;//修改状态为退出
            }
        }

    };

    ExitHandler mHandler = new ExitHandler();

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if(keyCode == KeyEvent.KEYCODE_BACK){
            if(!isExit){
                isExit = true;
                Toast.makeText(this, "再按一次退出游戏", Toast.LENGTH_SHORT).show();
                //延迟更改状态信息
                mHandler.sendEmptyMessageDelayed(0, 2000);
            }
            else{
                finish();
            }
        }
        return false;
    }
}

GameView.java

自定义游戏面板

package pers.hurric.game2048;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.widget.GridLayout;
import android.widget.Toast;

import java.util.Random;

public class GameView extends GridLayout {

    public static GameView gameView;

    private Card[][] cards = new Card[4][4];

    public GameView(Context context) {
        super(context);
        gameView = this;
        initGame();
    }

    public GameView(Context context, AttributeSet attrs) {
        super(context, attrs);
        gameView = this;
        initGame();
    }

    public GameView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        gameView = this;
        initGame();
    }

    public void initGame(){
        this.setBackgroundColor(getResources().getColor(R.color.gameViewBackgroundColor));
        setColumnCount(4);

        int cardWidth = GetCardWidth();
        addCards(cardWidth, cardWidth);

        randomCreateCard(2);

        setListener();
    }

    public void replayGame(){
        MainActivity.mainActivity.clearScore();
        for(int i = 0; i < 4; ++i){
            for(int j = 0; j < 4; ++j){
                cards[i][j].setNum(0);
            }
        }
        randomCreateCard(2);
    }

	/*
	 * 监听Touch事件
	 */
    private void setListener(){
        setOnTouchListener(new OnTouchListener() {
            private float staX,  staY, endX, endY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch(event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        staX = event.getX();
                        staY = event.getY();
                        break;

                    case MotionEvent.ACTION_UP:
                        endX = event.getX();
                        endY = event.getY();

                        boolean swiped = false;//记录是否有效滑动了

                        //水平移动更多
                        if(Math.abs(endX - staX) > Math.abs(endY - staY)){
                            if(endX - staX > 10){
                                if(swipeRight()){
                                    swiped = true;
                                }
                            }
                            else if(endX - staX < -10){
                                if(swipeLeft()){
                                    swiped = true;
                                }
                            }
                        }
                        else{
                            if(endY - staY < -10){
                                if(swipeUp()){
                                    swiped = true;
                                }
                            }
                            else if(endY - staY > 10){
                                if(swipeDown()){
                                    swiped = true;
                                }
                            }
                        }
                        //滑动后创建新块,并检查当前状态是否能滑动
                        if(swiped){
                            randomCreateCard(1);
                            if(!canSwipe()){
                                gameOver();
                            }
                        }
                        break;
                }
                return true;
            }
        });
    }

	/*
	 * 返回该次滑动是否有效(有卡片移动或合并)
	 */
    private boolean swipeUp(){
        boolean flag = false;
        for(int j = 0; j < 4; ++j){
            int ind = 0;
            //从上往下依次处理
            for(int i = 1; i < 4; ++i){
            	//如果是存在数字的,往上遍历
                if(cards[i][j].getNum() != 0){
                    for(int ii = i - 1; ii >= ind; --ii){
                    	//如果这块是空的,将数字上移
                        if(cards[ii][j].getNum() == 0){
                            cards[ii][j].setNum(cards[i][j].getNum());
                            cards[i][j].setNum(0);
                            i--;//上移
                            flag = true;
                        }
                        //如果这块是相同的数,合并,合并的块不能一下合并两次,更新ind,不再遍历合并的块
                        else if(cards[ii][j].getNum() == cards[i][j].getNum()){
                            cards[ii][j].setNum((cards[i][j].getNum() * 2));
                            cards[i][j].setNum(0);
                            flag = true;
                            ind = ii + 1;//已经合过,该点不再合成
                            MainActivity.mainActivity.addScore(cards[ii][j].getNum() / 2);
                            //播放合并动画
                            playMergeAnimation(ii, j);
                            break;
                        }
                        //上面的块数字不同,退出循环
                        else break;
                    }
                }
            }
        }
        return flag;
    }

    private boolean swipeDown(){
        boolean flag = false;
        for(int j = 0; j < 4; ++j){
            int ind = 4;
            for(int i = 2; i >= 0; --i){
                if(cards[i][j].getNum() != 0){
                    for(int ii = i + 1; ii < ind; ++ii){
                        if(cards[ii][j].getNum() == 0){
                            cards[ii][j].setNum(cards[i][j].getNum());
                            cards[i][j].setNum(0);
                            flag = true;
                            i++;
                        }
                        else if(cards[ii][j].getNum() == cards[i][j].getNum()){
                            cards[ii][j].setNum((cards[i][j].getNum() * 2));
                            cards[i][j].setNum(0);
                            flag = true;
                            ind = ii;
                            MainActivity.mainActivity.addScore(cards[ii][j].getNum() / 2);
                            playMergeAnimation(ii, j);
                            break;
                        }
                        else break;
                    }
                }
            }
        }
        return flag;
    }

    private boolean swipeLeft(){
        boolean flag = false;
        for(int i = 0; i < 4; ++i){
            int ind = 0;
            for(int j = 1; j < 4; ++j){
                if(cards[i][j].getNum() != 0){
                    for(int jj = j - 1; jj >= ind; --jj){
                        if(cards[i][jj].getNum() == 0){
                            cards[i][jj].setNum(cards[i][j].getNum());
                            cards[i][j].setNum(0);
                            flag = true;
                            j--;
                        }
                        else if(cards[i][jj].getNum() == cards[i][j].getNum()){
                            cards[i][jj].setNum((cards[i][j].getNum() * 2));
                            cards[i][j].setNum(0);
                            flag = true;
                            ind = jj + 1;
                            MainActivity.mainActivity.addScore(cards[i][jj].getNum() / 2);
                            playMergeAnimation(i, jj);
                            break;
                        }
                        else break;
                    }
                }
            }
        }
        return flag;
    }

    private boolean swipeRight(){
        boolean flag = false;
        for(int i = 0; i < 4; ++i){
            int ind = 4;
            for(int j = 2; j >= 0; --j){
                if(cards[i][j].getNum() != 0){
                    for(int jj = j + 1; jj < ind; ++jj){
                        if(cards[i][jj].getNum() == 0){
                            cards[i][jj].setNum(cards[i][j].getNum());
                            cards[i][j].setNum(0);
                            flag = true;
                            j++;
                        }
                        else if(cards[i][jj].getNum() == cards[i][j].getNum()){
                            cards[i][jj].setNum((cards[i][j].getNum() * 2));
                            cards[i][j].setNum(0);
                            flag = true;
                            ind = jj;
                            MainActivity.mainActivity.addScore(cards[i][jj].getNum() / 2);
                            playMergeAnimation(i, jj);
                            break;
                        }
                        else break;
                    }
                }
            }
        }
        return flag;
    }

	/**
	  *如果存在空白块,或者相邻的数字相同的块,则可以继续滑动
	  */
    private boolean canSwipe(){
        for(int i = 0; i < 4; ++i){
            for(int j = 0; j < 4; ++j){
                if(cards[i][j].getNum() == 0){
                    return true;
                }
                else if(i != 3 && cards[i][j].getNum() == cards[i + 1][j].getNum()){
                    return true;
                }
                else if(j != 3 && cards[i][j].getNum() == cards[i][j + 1].getNum()){
                    return true;
                }
            }
        }
        return false;
    }

    private void addCards(int width, int height){
        Card c;
        for(int i = 0; i < 4; ++i){
            for(int j = 0; j < 4; ++j){
                c = new Card(getContext());
                addView(c, width, height);
                cards[i][j] = c;
            }
        }
    }

    private void gameOver(){
        Toast.makeText(getContext(), "游戏结束", Toast.LENGTH_SHORT).show();
    }

    private int GetCardWidth() {
    	//获取屏幕信息
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        //根据布局,GameView是占屏幕宽度的90%,除以4就是卡片边长
        return (int)((displayMetrics.widthPixels * 0.9f) / 4);
    }

	/*
	 * 递归随机,玄学复杂度,期望递归次数小于 16 次,偷了个懒
	 * 最好是把可用方块加入到一个列表中,然后在列表中随机
	 */
    private void randomCreateCard(int cnt){
        Random random = new Random();
        int r = random.nextInt(4);
        int c = random.nextInt(4);

		//该处已经存在数字,重新随机r, c
        if(cards[r][c].getNum() != 0){
            randomCreateCard(cnt);
            return;
        }

        int rand = random.nextInt(10);

        if(rand >= 2) rand = 2;
        else rand = 4;

        cards[r][c].setNum(rand);

		//播放创建动画
        playCreateAnimation(r, c);
		
        if(cnt >= 2){
            randomCreateCard(cnt - 1);
        }
    }

	/*
	 * 播放创建新块动画
	 */
    private void playCreateAnimation(int r, int c){
        AnimationSet animationSet = new AnimationSet(true);
		
		//旋转
        RotateAnimation anim = new RotateAnimation(0,360,RotateAnimation.RELATIVE_TO_SELF,0.5f, RotateAnimation.RELATIVE_TO_SELF,0.5f);
        anim.setDuration(250);
        anim.setRepeatCount(0);
        anim.setInterpolator(new LinearInterpolator());

		//缩放
        ScaleAnimation anim2 = new ScaleAnimation(0,1,0,1,
                Animation.RELATIVE_TO_SELF,0.5f,
                Animation.RELATIVE_TO_SELF,0.5f
        );
        anim2.setDuration(250);
        anim2.setRepeatCount(0);

        animationSet.addAnimation(anim);
        animationSet.addAnimation(anim2);

        cards[r][c].startAnimation(animationSet);
    }

	/*
	 * 播放合并动画
	 */
    private void playMergeAnimation(int r, int c){
        ScaleAnimation anim = new ScaleAnimation(1,1.2f,1,1.2f,
                Animation.RELATIVE_TO_SELF,0.5f,
                Animation.RELATIVE_TO_SELF,0.5f
        );
        anim.setDuration(150);
        anim.setRepeatCount(0);

        anim.setRepeatMode(Animation.REVERSE);

        cards[r][c].startAnimation(anim);
    }
}

Card.java

代表一个格子

package pers.hurric.game2048;

import android.content.Context;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;

import java.util.HashMap;
import java.util.Map;

public class Card extends FrameLayout {

    TextView textView;

    private int num;

    static Map<Integer, Integer> backgroundColorIdMap = new HashMap<>();
    static Map<Integer, Integer> textColorIdMap = new HashMap<>();

    static {
        textColorIdMap.put(0, R.color.textColor0);
        textColorIdMap.put(2, R.color.textColor2);
        textColorIdMap.put(4, R.color.textColor4);

        backgroundColorIdMap.put(0, R.color.backgroundColor0);
        backgroundColorIdMap.put(2, R.color.backgroundColor2);
        backgroundColorIdMap.put(4, R.color.backgroundColor4);
        backgroundColorIdMap.put(8, R.color.backgroundColor8);
        backgroundColorIdMap.put(16, R.color.backgroundColor16);
        backgroundColorIdMap.put(32, R.color.backgroundColor32);
        backgroundColorIdMap.put(64, R.color.backgroundColor64);
        backgroundColorIdMap.put(128, R.color.backgroundColor128);
        backgroundColorIdMap.put(256, R.color.backgroundColor256);
        backgroundColorIdMap.put(512, R.color.backgroundColor512);
        backgroundColorIdMap.put(1024, R.color.backgroundColor1024);
    }

    public Card(@NonNull Context context) {
        super(context);

        textView = new TextView(context);
        textView.setGravity(Gravity.CENTER);
        textView.setText(getNum() + "");
        textView.setTextSize(50);

        setNum(0);

        LayoutParams lp = new LayoutParams(-1, -1);
        lp.setMargins(10, 10, 10, 10);

        addView(textView, lp);
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
        textView.setText(num + "");
        //数字改变时,同时改变改变字体大小和颜色
        changeColor(num);
        changeSize(num);
    }

    private void changeSize(int num){
        if(num >= 1024){
            textView.setTextSize(25);
        }
        else if(num >= 128){
            textView.setTextSize(35);
        }
        else if(num >= 16){
            textView.setTextSize(42);
        }
        else{
            textView.setTextSize(50);
        }
    }

    private void changeColor(int num){
        if(num >= 8){
            textView.setTextColor(getResources().getColor(R.color.textColorCommon));
        }
        else{
            textView.setTextColor(getResources().getColor(textColorIdMap.get(num)));
        }
        if(num >= 2048){
            textView.setBackgroundColor(getResources().getColor(R.color.backgroundColorBiggerThan2048));
        }
        else{
            textView.setBackgroundColor(getResources().getColor(backgroundColorIdMap.get(num)));
        }
    }
}

  • 22
    点赞
  • 114
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值