Android手游 “2048” 源码解析

转载请写明出处:http://blog.csdn.net/big_heart_c

下面所解析的源码是来自极客学院“Android 2048 ”中的源码,读者可以从 https://github.com/plter/Android2048GameLesson 中下载,作者是 ime 。

源码主要由3个 java 方法组成:MainActivity、Card、GameView。MainActivity就不用说了,Card方法是实现每一个小方格的形状、颜色等等,GameView方法主要是处理用户滑动时间、控制游戏界面等等。其实都挺简单的,关键是要耐心的去读,每次读源码,看别人活用书本上的东西,都感觉 unbelievable !自己自然能从中学习不少,这更是书本上学不来的。

首先解析 "Card.java" 文件:

package com.jikexueyuan.game2048;

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

public class Card extends FrameLayout {
	private TextView label;
	public Card(Context context) {
		super(context);
				
		label = new TextView(getContext());
		label.setTextSize(32);
		label.setBackgroundColor(0x33ffffff);	//设置方格背景
		label.setGravity(Gravity.CENTER);	//使数字居于方格正中
		
		LayoutParams lp = new LayoutParams(-1, -1);	//使方格自适应屏幕
		lp.setMargins(10, 10, 0, 0);		//使方格左,上的间距均为10像素
		addView(label, lp);
		
		setNum(0);
	}
	
	
	private int num = 0;
	
	public int getNum() {
		return num;
	}
	
	public void setNum(int num) {	//定义一个在方格中显示数字的方法供 “GameView.java” 调用
		this.num = num;
		
		if (num<=0) {
			label.setText("");
		}else{
			label.setText(num+"");
		}
	}
	
	public boolean equals(Card o) {		//定义一个比较方格的数字是否相同的方法供 “GameView.java” 调用
		return getNum()==o.getNum();
	}
}


接下来是"GameView.java"文件:

package com.jikexueyuan.game2048;

import java.util.ArrayList;
import java.util.List;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.GridLayout;

public class GameView extends GridLayout {
	private Card[][]	cardsMap	= new Card[4][4];		//用来存储没有数字显示的方格,当其没有数字时,使其值为 0
	private List<Point>	emptyPoints	= new ArrayList<Point>();	// 使用emptyPoints数组来存储没有数字的方格
																
	public GameView(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
		
		initGameView();
	}
	
	public GameView(Context context) {
		super(context);
		
		initGameView();
	}
	
	public GameView(Context context, AttributeSet attrs) {
		super(context, attrs);
		
		initGameView();
	}
	
	private void initGameView() {
		setColumnCount(4); // 定义列数,即 4*4 的格子
		setBackgroundColor(0xffbbada0);
		
		setOnTouchListener(new View.OnTouchListener() {
			
			private float	startX,startY,offsetX,offsetY;	// startX,startY表示用户滑动起始位置,offsetX,offsetY则表示末尾的位置
					
			@ Override
			public boolean onTouch(View v, MotionEvent event) {
				
				switch (event.getAction()) {
					case MotionEvent.ACTION_DOWN:
						startX = event.getX();
						startY = event.getY();
						break;
					case MotionEvent.ACTION_UP:
						offsetX = event.getX() - startX;
						offsetY = event.getY() - startY;
						
						if (Math.abs(offsetX) > Math // 说明用户是水平方向滑动的
								.abs(offsetY)) {
							if (offsetX < -5) { // 设置为“-5”是为了容错,因为数据存储精度原因
								swipeLeft();
							}
							else if (offsetX > 5) {
								swipeRight();
							}
						}
						else { // 说明用户是竖直方向滑动的
							if (offsetY < -5) {
								swipeUp();
							}
							else if (offsetY > 5) {
								swipeDown();
							}
						}
						
						break;
				}
				return true; // 原先是return false,在这里得改成 return true,原因请参照本人转载的一篇博文  http://blog.csdn.net/big_heart_c/article/details/39678439
			}
		});
	}
	
	@ Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) { // 跟随不同手机的屏幕大小,设置相应每个方格的大小
		super.onSizeChanged(w, h, oldw, oldh);
		
		int cardWidth = (Math.min(w, h) - 10) / 4;
		
		addCards(cardWidth, cardWidth);		//向  GameView 界面添加方格
		
		startGame();
	}
	
	private void addCards(int cardWidth, int cardHeight) {
		
		Card c;
		
		for (int y = 0; y < 4; y++) {
			for (int x = 0; x < 4; x++) {
				c = new Card(getContext());
				c.setNum(0);  // 先设置每个格子都是没有数字,即值均为 0
				addView(c, cardWidth, cardHeight);
				cardsMap[x][y] = c;
			}
		}
	}
	
	private void startGame() {
		
		MainActivity.getMainActivity().clearScore();
		
		for (int y = 0; y < 4; y++) {
			for (int x = 0; x < 4; x++) {
				cardsMap[x][y].setNum(0);
			}
		}
		
		addRandomNum(); // 开始时先有两个方格有数字
		addRandomNum();
	}
	
	private void addRandomNum() { // 随机生成下一个数字出现的位置
	
		emptyPoints.clear(); // 先清空游戏界面
		
		for (int y = 0; y < 4; y++) {
			for (int x = 0; x < 4; x++) {
				if (cardsMap[x][y].getNum() <= 0) { // 遍历cardsMap中所有元素,如果其值为
													// 0,即不需要显示数字的话,则加入emptyPoints中
					emptyPoints.add(new Point(x, y));
				}
			}
		}
		
		Point p = emptyPoints // 随机生成下一个格子的位置,即只需要从emptyPoints中随机挑一个就OK了
				.remove((int) (Math.random() * emptyPoints
						.size()));
		// 使下一个格子出现 4 和2的概率比为 1 9(实际游戏中出现4的概率比较小,这里我们假设它为0.1)
		cardsMap[p.x][p.y].setNum(Math.random() > 0.1 ? 2 : 4);
	}
	
	private void swipeLeft() { // 处理用户向左滑动的事件
	
		boolean merge = false;
		
		for (int y = 0; y < 4; y++) {
			for (int x = 0; x < 4; x++) {
				
				for (int x1 = x + 1; x1 < 4; x1++) {	// 每一个方格都比较一下自己右边的数,看能否结合
					if (cardsMap[x1][y].getNum() > 0) {
						
						if (cardsMap[x][y].getNum() <= 0) {
							cardsMap[x][y]
									.setNum(cardsMap[x1][y]
											.getNum());
							cardsMap[x1][y].setNum(0);
							
							x--;
							
							merge = true;
						}
						else if (cardsMap[x][y]
								.equals(cardsMap[x1][y])) {
							cardsMap[x][y].setNum(cardsMap[x][y]
									.getNum() * 2);
							cardsMap[x1][y].setNum(0);
							
							MainActivity.getMainActivity()
									.addScore(
											cardsMap[x][y]
													.getNum());
							merge = true;
						}
						
						break;
					}
				}
			}
		}
		
		if (merge) {
			addRandomNum();
			checkComplete();	//每次移动都判断游戏是否结束
		}
	}
	
	private void swipeRight() { // 处理用户向右滑动的事件
	
		boolean merge = false;  //判断此次操作是否有合并
		
		for (int y = 0; y < 4; y++) {
			for (int x = 3; x >= 0; x--) {
				
				for (int x1 = x - 1; x1 >= 0; x1--) {
					if (cardsMap[x1][y].getNum() > 0) {
						
						if (cardsMap[x][y].getNum() <= 0) {
							cardsMap[x][y]
									.setNum(cardsMap[x1][y]
											.getNum());
							cardsMap[x1][y].setNum(0);
							
							x++;
							merge = true;
						}
						else if (cardsMap[x][y]
								.equals(cardsMap[x1][y])) {
							cardsMap[x][y].setNum(cardsMap[x][y]
									.getNum() * 2);
							cardsMap[x1][y].setNum(0);
							MainActivity.getMainActivity()
									.addScore(
											cardsMap[x][y]
													.getNum());
							merge = true;
						}
						
						break;
					}
				}
			}
		}
		
		if (merge) {       //如果有合并,则再随机添加一个有数字的格子,并检查游戏是否结束
			addRandomNum();
			checkComplete();
		}
	}
	
	private void swipeUp() { // 处理用户向上滑动的事件
	
		boolean merge = false;
		
		for (int x = 0; x < 4; x++) {
			for (int y = 0; y < 4; y++) {
				
				for (int y1 = y + 1; y1 < 4; y1++) {
					if (cardsMap[x][y1].getNum() > 0) {
						
						if (cardsMap[x][y].getNum() <= 0) {
							cardsMap[x][y]
									.setNum(cardsMap[x][y1]
											.getNum());
							cardsMap[x][y1].setNum(0);
							
							y--;
							
							merge = true;
						}
						else if (cardsMap[x][y]
								.equals(cardsMap[x][y1])) {
							cardsMap[x][y].setNum(cardsMap[x][y]
									.getNum() * 2);
							cardsMap[x][y1].setNum(0);
							MainActivity.getMainActivity()
									.addScore(
											cardsMap[x][y]
													.getNum());
							merge = true;
						}
						
						break;
						
					}
				}
			}
		}
		
		if (merge) {
			addRandomNum();
			checkComplete();
		}
	}
	
	private void swipeDown() { // 处理用户向下滑动的事件
	
		boolean merge = false;
		
		for (int x = 0; x < 4; x++) {
			for (int y = 3; y >= 0; y--) {
				
				for (int y1 = y - 1; y1 >= 0; y1--) {
					if (cardsMap[x][y1].getNum() > 0) {
						
						if (cardsMap[x][y].getNum() <= 0) {
							cardsMap[x][y]
									.setNum(cardsMap[x][y1]
											.getNum());
							cardsMap[x][y1].setNum(0);
							
							y++;
							merge = true;
						}
						else if (cardsMap[x][y]
								.equals(cardsMap[x][y1])) {
							cardsMap[x][y].setNum(cardsMap[x][y]
									.getNum() * 2);
							cardsMap[x][y1].setNum(0);
							MainActivity.getMainActivity()
									.addScore(
											cardsMap[x][y]
													.getNum());
							merge = true;
						}
						
						break;
					}
				}
			}
		}
		
		if (merge) {
			addRandomNum();
			checkComplete();
		}
	}
	
	private void checkComplete() { // 判断游戏是否结束
	
		boolean complete = true;
		
		ALL:
		// 定义一个ALL标签,是下面双重循环中的break能跳出该双重循环
		for (int y = 0; y < 4; y++) {
			for (int x = 0; x < 4; x++) {
				if (cardsMap[x][y].getNum() == 0 // 如果还有空格子 或
													// 任意一个格子的上下左右可以合并,即存在相同的数的时候,则游戏继续
						|| (x > 0 && cardsMap[x][y]
								.equals(cardsMap[x - 1][y]))
						|| (x < 3 && cardsMap[x][y]
								.equals(cardsMap[x + 1][y]))
						|| (y > 0 && cardsMap[x][y]
								.equals(cardsMap[x][y - 1]))
						|| (y < 3 && cardsMap[x][y]
								.equals(cardsMap[x][y + 1]))) {
					
					complete = false;
					break ALL; // 跳出该双重循环
				}
			}
		}
		
		if (complete) {		//游戏结束的时候跳出一个dialog
			new AlertDialog.Builder(getContext())
					.setTitle("你好")
					.setMessage("游戏结束")
					.setPositiveButton(
							"重来",
							new DialogInterface.OnClickListener() {
								
								@ Override
								public void onClick(
										DialogInterface dialog,
										int which) {
									startGame();
								}
							}).show();
		}
		
	}
}
然后是"MainActivity.java":

package com.jikexueyuan.game2048;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.widget.TextView;

public class MainActivity extends Activity {
	private int score = 0;
	private TextView tvScore;	//用于显示分数
	
	private static MainActivity mainActivity = null;
	public MainActivity() {
		mainActivity = this;
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		tvScore = (TextView) findViewById(R.id.tvScore);
	}


	@Override
	public boolean onCreateOptionsMenu(Menu menu) {

		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}
	
	public void clearScore(){	//分数清零,用于游戏开始或重新开始时
		score = 0;
		showScore();
	}
	
	public void showScore(){
		tvScore.setText(score+"");
	}
	
	public void addScore(int s){
		score+=s;
		showScore();
	}
	/*使其他方法能调用 MainActivity中的方法,如  GameView 方法中
	 * MainActivity.getMainActivity()
			.addScore(
			cardsMap[x][y]
					.getNum());*/
	public static MainActivity getMainActivity() {
		return mainActivity;
	}

}
最后是"activity_mian.xml",这个布局比较简单,没什么好说的,仅贴出来供不想下载源码的读者查看:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.jikexueyuan.game2048.MainActivity"
    tools:ignore="MergeRootFrame" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/score" />

        <TextView
            android:id="@+id/tvScore"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <com.jikexueyuan.game2048.GameView
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/gameView" >
    </com.jikexueyuan.game2048.GameView>

</LinearLayout>

以上便是本人对该源码的浅见了,由于学习Android不是很长时间,可能会有些许错误的地方,还望大家指出!如果大家有不明白的也可提问,本人一定解答。另外,若还是看不明白,可参看另一位博主关于改源码的解析http://www.cnblogs.com/ilfmonday/p/2048android.html,或去下载极客学院视频观看。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值