打造一个属于你自己的Android版2048

时间:2022-10-31 08:09:44

作为新手,写的博客一开始也没什么人看吧,看了一下浏览量最多还有留言的一篇是我思路写的最清晰和独特的一篇,看来用点心来写,对自己对大家都好,不能草草了事 。勤于学习,勤于总结归纳,勤于提高效率。在小项目中入手学习各种知识点可能效率更高,更能把知识点串联起来。  

今天写的博客是2048,说实话我是开始要写2048的时候才下一个来玩一下,之前是不懂的,玩了之后发现简单易入迷。下面直入主题,  

一开始,首先要熟悉2048这个游戏,最好写一下规则说明书,这里我就不列出来了。  

效果图展示:开始界面——>功能选择界面——>游戏界面

打造一个属于你自己的Android版2048打造一个属于你自己的Android版2048打造一个属于你自己的Android版2048


打造一个属于你自己的Android版2048


功能介绍:

开始界面:五秒之后进入功能选择界面

功能选择界面:根据不同的按钮来选择游戏的模式,可以选择不同模式,对应会有不同的图片进入游戏界面就开始播放背景音乐

游戏界面:1.记录分数;2.保留最高分;3.游戏区域符合游戏规则;4.背景音乐的播放开始暂停和停止;5.移动合并的声音 ; 6.游戏重来; 7.赢或者输跳出对话框

核心思想:(可以看完代码再来体会,或者体会完能有所启发后自己写)

做完这个2048后,给我最大的感觉就是把游戏界面当做一个自定义组件来实现,就像是系统自给的组件,比如按钮,按钮有自己的颜色大小外观,你点击按钮会有点击事件(切换图片显示下按效果),那么一个游戏界面也是这样的,有自己的颜色大小外观,和响应事件,那么这些内容就要我们自己来实现。

  其次就是2048的游戏逻辑,移动、合并、判断输赢,这些都要好好思考算法。经典地用到了二维数组来存储卡片位置,便于增删的链表来记录空位置。

再然后,如何打造个性化的2048,就是把数字变成自己喜欢的图片咯~数字逻辑转换为图像逻辑时,运用到了数组,算是比较笨的方法,但也是最容易想到的。

Last but not least,(莫名其妙想用到这个短语。。),传参问题,一是方式,activity之间的跳转用intent来传参;一般的用getxx,setxx来传参;自定义组件上下文context不用传构造函数里有。二是先后顺序,一开始我把获取参数放在GameView的构造函数里,这样GameView在初始化时已经执行了获取参数,参数来不及传过来的。

实现流程:

打造一个属于你自己的Android版2048

java代码:

</pre><pre name="code" class="java">/**
* @author Clark Xu
*
* 五秒之后跳转到功能选择界面
*
*/
public class WelcomeActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题栏
setContentView(R.layout.welcome);
skip();
}
// 5秒之后跳转到第二个页面
// time计时器
public void skip() {
final Intent it = new Intent(WelcomeActivity.this, ChooseActivity.class); // 你要转向的Activity
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
startActivity(it); // 执行
}
};
timer.schedule(task, 1000 * 5);
}
}
/** * @author Clark Xu *  * 功能选择界面:根据不同的按钮来选择游戏的模式,进入游戏界面就开始播放对应的背景音乐 * */public class ChooseActivity extends Activity {private int row = 4, column = 4;private int model = 1;private Button Btn3x3;private Button Btn4x4;private Button Btn5x5;private Button Btn6x6;private Button NormalBtn;private Button DigitBtn;private Button QilongzhuBtn;private Button HuoyinBtn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题栏setContentView(R.layout.choose);getView();setListeners();}public void getView() {Btn3x3 = (Button) findViewById(R.id.Btn3x3);Btn4x4 = (Button) findViewById(R.id.Btn4x4);Btn5x5 = (Button) findViewById(R.id.Btn5x5);Btn6x6 = (Button) findViewById(R.id.Btn6x6);NormalBtn = (Button) findViewById(R.id.NormalBtn);DigitBtn = (Button) findViewById(R.id.DigitBtn);QilongzhuBtn = (Button) findViewById(R.id.QilongzhuBtn);HuoyinBtn = (Button) findViewById(R.id.HuoyinBtn);}//多个按钮添加监听事件用这种格式简洁明了public void setListeners() {Button button[] = { Btn3x3, Btn4x4, Btn5x5, Btn6x6, NormalBtn,DigitBtn, QilongzhuBtn, HuoyinBtn };// 创一个事件处理类ButtonListener bl = new ButtonListener();for (int i = 0; i < 8; i++) {button[i].setOnClickListener(bl);}}class ButtonListener implements OnClickListener{//他们都要发生跳转,然后各自传不同的参数@Overridepublic void onClick(View v) {if(v instanceof Button){//发生跳转Intent intent =new Intent(ChooseActivity.this,MainActivity.class);//传不同参int btnId=v.getId();switch (btnId) {case R.id.Btn3x3:column = 3;row = 3;break;case R.id.Btn4x4:column = 4;row = 4;break;case R.id.Btn5x5:column = 5;row = 5;break;case R.id.Btn6x6:column = 6;row = 6;break;case R.id.NormalBtn:model = 1;break;case R.id.DigitBtn:model = 2;break;case R.id.QilongzhuBtn:model = 3;break;case R.id.HuoyinBtn:model = 4;break;default:break;}intent.putExtra("column", column);intent.putExtra("row", row);intent.putExtra("model", model);intent.putExtra("music", true);startActivity(intent);}}}}
/** * @author Clark Xu * 把自定义组件GameView添加到这上面, * 获取传过来的各种参数,执行不同的模式 * */public class MainActivity extends Activity {private int score = 0;private TextView tvScore;private TextView tvBestScore;private int column = 4;private int row = 4;private int model = 1;private MediaPlayer mp;private boolean music = false;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button resbtn = (Button) findViewById(R.id.resbtn);tvScore = (TextView) findViewById(R.id.scoreView2);tvBestScore = (TextView) findViewById(R.id.scoreView4);Intent intent = this.getIntent();row = intent.getIntExtra("row", 4);column = intent.getIntExtra("column", 4);model = intent.getIntExtra("model", 1);switch (model) {case 1:// 数字版mp = MediaPlayer.create(this, R.raw.infectious);break;case 2:// 数码版mp = MediaPlayer.create(this, R.raw.braveheart);break;case 3:// 七龙珠版mp = MediaPlayer.create(this, R.raw.qilongzhu);break;case 4:// 火影版mp = MediaPlayer.create(this, R.raw.huoyin);break;}try {mp.prepare();// 让mp对象准备} catch (Exception e) {e.printStackTrace();}// 一进来就播放音乐music = intent.getBooleanExtra("music", false);if (music && (null != mp)) {mp.start();mp.setLooping(true);}// 开始游戏的逻辑GameView.getGameView().initGameView();// 获取三个按钮Button butStart = (Button) this.findViewById(R.id.startBtn);Button butPause = (Button) this.findViewById(R.id.pauseBtn);Button butStop = (Button) this.findViewById(R.id.stopBtn);// 实例化事件处理类MusicListener ml = new MusicListener();// 给事件源添加动作监听方法,指定事件处理类对象mlbutStart.setOnClickListener(ml);butPause.setOnClickListener(ml);butStop.setOnClickListener(ml);resbtn.setOnClickListener(new OnClickListener() {public void onClick(View v) {// TODO Auto-generated method stubGameView.getGameView().startGame();clearScore();}});}class MusicListener implements OnClickListener {@Overridepublic void onClick(View v) {Button btn = (Button) v;String text = btn.getText().toString();if (text.equals("START")) {if (null != mp)mp.start();mp.setLooping(true);} else if (text.equals("PAUSE")) {if (null != mp)mp.pause();} else if (text.equals("STOP")) {// stop的话就完全结束了,还不如用pause,再调用seekTo时间调为0try {mp.pause();mp.seekTo(0);} catch (Exception e) {e.printStackTrace();}}}}public int getColumn() {return column;}public int getRow() {return row;}public int getModel() {return model;}@Overridepublic 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();int maxScore = Math.max(score, getBestScore());saveBestScore(maxScore);showBestScore(maxScore);}// 保存分数public void saveBestScore(int s) {Editor e = getPreferences(MODE_PRIVATE).edit();e.putInt("bestScore", s);e.commit();}public int getBestScore() {return getPreferences(MODE_PRIVATE).getInt("bestScore", 0);}public void showBestScore(int s) {tvBestScore.setText(s + "");}// 销毁Activity时执行的,重写此方法表示Activity的生命周期结束不要一直播放占用资源protected void onDestroy() {super.onDestroy();if (null != mp)try {mp.release();} catch (Exception e) {e.getStackTrace();}}}
/** * @author Clark Xu * 游戏自定义组件:设置大小颜色模式, * 根据响应实现游戏逻辑 *  继承表格布局更合适 */public class GameView extends GridLayout {private float startX, startY, endX, endY, offX, offY;private int row = 4, colunm = 4;// 行row对应y,列colunm对应x,默认开始都为4private CardView[][] cardsMap = new CardView[10][10];// 用一个二维数组来存private List<Point> emptyPoints = new ArrayList<Point>();// 链表方便增加删除private static GameView gview = null;private MainActivity my2048;private CardAnim ca = new CardAnim();private int cw;// 在xml中能够访问则要添加构造方法// 以防万一三个构造方法都要写:对应参分别为上下文,属性,样式public GameView(Context context) {super(context);gview = this;}public GameView(Context context, AttributeSet attrs) {super(context, attrs);gview = this;}public GameView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);gview = this;}public static GameView getGameView() {return gview;}// 由于手机可能不同,我们需要动态地获取卡片的宽高,所以要重写下面这个方法获取当前布局的宽高,// 为了让手机不会因倒过来改变宽高,要去mainifest里配置// 只会在手机里第一次运行的时候执行,之后不会改变protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);int cardWidth = (Math.min(w, h) - 10) / colunm;addCards(cardWidth, cardWidth);// 把参数传过去startGame();}public void startGame() {for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {cardsMap[x][y].setNum(0);}}my2048.addScore(0);my2048.clearScore();addRandomNum();addRandomNum();}private void addCards(int cardWidth, int cardHeigth) {CardView c;cw = cardWidth;for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {// 并不知道getContext是什么意思c = new CardView(getContext());// 先都初始画0号图片c.setNum(0);addView(c, cardWidth, cardHeigth);// 把所有的卡片都记录下来cardsMap[x][y] = c;}}}// 添加随机数的时候要先遍历private void addRandomNum() {emptyPoints.clear();for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {if (cardsMap[x][y].getNum() <= 0) {emptyPoints.add(new Point(x, y));// 把空位给emptypoints链表}}}// 随机把emptyPoints中的一个赋值,生成2的概率为9,4为1Point p = emptyPoints.remove((int) (Math.random() * emptyPoints.size()));// 2号图片和4号图片cardsMap[p.x][p.y].setNum(Math.random() > 0.1 ? 2 : 4);        //动画生成效果ca.createScaleTo1(cardsMap[p.x][p.y]);}// GameView实现的功能public void initGameView() {my2048 = (MainActivity) this.getContext();colunm = my2048.getColumn();row = my2048.getRow();setColumnCount(colunm);// 设置表格为4列setBackgroundColor(Color.BLUE);setOnTouchListener(new OnTouchListener() {public boolean onTouch(View v, MotionEvent event) {int action = event.getAction();// 获取触屏的动作switch (action) {// 按下获取起始点case MotionEvent.ACTION_DOWN:startX = event.getX();startY = event.getY();break;// 松开获取终止点,通过比较位移来判断滑动方向// 要处理一下滑动偏的,看offx和offy哪个绝对值大就按照哪个来case MotionEvent.ACTION_UP:endX = event.getX();endY = event.getY();offX = startX - endX;offY = startY - endY;if (Math.abs(offX) >= Math.abs(offY)) {if (offX >= 5)moveLeft();// System.out.println("左");else if (offX < -5)moveRight();// System.out.println("右");} else if (Math.abs(offX) <= Math.abs(offY)) {if (offY >= 5)moveUp();// System.out.println("上");else if (offY < -5)moveDown();// System.out.println("下");}break;}// !!!要改为true,否则ACTION_UP不会执行return true;}});}private void moveLeft() {boolean merge = false;for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {// 遍历当前位置的右边,如果有数字,如果当前位置没有数字,则合并到当前位置for (int x1 = x + 1; x1 < colunm; 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;MyPlayer.playTone(getContext(),MyPlayer.INDEX_SONG_MOVE);break;} else if (cardsMap[x][y].equals(cardsMap[x1][y])) {cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);cardsMap[x1][y].setNum(0);my2048.addScore(cardsMap[x][y].getNum());merge = true;MyPlayer.playTone(getContext(),MyPlayer.INDEX_SONG_CANCEL);break;}break;}}}}if (merge) {addRandomNum();checkComplete();}}private void moveRight() {boolean merge = false;for (int y = 0; y < row; y++) {for (int x = colunm - 1; 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;MyPlayer.playTone(getContext(),MyPlayer.INDEX_SONG_MOVE);break;} else if (cardsMap[x][y].equals(cardsMap[x1][y])) {cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);cardsMap[x1][y].setNum(0);my2048.addScore(cardsMap[x][y].getNum());merge = true;MyPlayer.playTone(getContext(),MyPlayer.INDEX_SONG_CANCEL);break;}break;}}}}if (merge) {addRandomNum();checkComplete();}}private void moveUp() {boolean merge = false;for (int x = 0; x < colunm; x++) {for (int y = 0; y < row; y++) {// 遍历当前位置的右边,如果有数字,如果当前位置没有数字,则合并到当前位置for (int y1 = y + 1; y1 < row; 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;MyPlayer.playTone(getContext(),MyPlayer.INDEX_SONG_MOVE);break;} else if (cardsMap[x][y].equals(cardsMap[x][y1])) {cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);cardsMap[x][y1].setNum(0);my2048.addScore(cardsMap[x][y].getNum());merge = true;MyPlayer.playTone(getContext(),MyPlayer.INDEX_SONG_CANCEL);break;}break;}}}}if (merge) {addRandomNum();checkComplete();}}private void moveDown() {boolean merge = false;for (int x = 0; x < colunm; x++) {for (int y = row - 1; 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;MyPlayer.playTone(getContext(),MyPlayer.INDEX_SONG_MOVE);break;} else if (cardsMap[x][y].equals(cardsMap[x][y1])) {cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);cardsMap[x][y1].setNum(0);my2048.addScore(cardsMap[x][y].getNum());merge = true;MyPlayer.playTone(getContext(),MyPlayer.INDEX_SONG_CANCEL);break;}break;}}}}if (merge) {addRandomNum();checkComplete();}}// 判断结束private void checkComplete() {boolean complete = true;for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {if (cardsMap[x][y].getNum() == 2048)new AlertDialog.Builder(getContext()).setTitle("你好").setMessage("游戏胜利").setPositiveButton("重来",new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {startGame();}}).show();}}ALL: for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; 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;// 如果出现这种情况,跳出双重循环,只写一个break只能跳出当前循环}}}if (complete) {new AlertDialog.Builder(getContext()).setTitle("你好").setMessage("游戏结束").setPositiveButton("重来",new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {startGame();}}).show();}}}
/** * @author Clark Xu * 卡片自定义组件,添加到GameView上 * */public class CardView extends FrameLayout {private int num = 0;// 只显示数字的话可以用Textview,要是用图片的话要用imviewprivate TextView label;private ImageView pic;private int[] picArray = new int[3000];private MainActivity my2048;private int model = 1;private LayoutParams lp;public CardView(Context context) {super(context);putPic();pic = new ImageView(getContext());lp = new LayoutParams(-1, -1);// -1,-1就是填充完父类容器的意思lp.setMargins(10, 10, 0, 0);// 用来设置边框很管用addView(pic, lp);// 把imageView加到CardView上setNum(0);}    //把数字逻辑实现的2048转化为图片逻辑,只需要把数字定位数组序数,数字对应图片,并保持一一对应关系public void putPic() {picArray[0] = R.drawable.num0;picArray[2] = R.drawable.pic2;picArray[4] = R.drawable.pic4;picArray[8] = R.drawable.pic8;picArray[16] = R.drawable.pic16;picArray[32] = R.drawable.pic32;picArray[64] = R.drawable.pic64;picArray[128] = R.drawable.pic128;picArray[256] = R.drawable.pic256;picArray[512] = R.drawable.pic512;picArray[1024] = R.drawable.pic1024;picArray[2048] = R.drawable.pic2048;// 数字版+7picArray[7] = R.drawable.num0;picArray[9] = R.drawable.num2;picArray[11] = R.drawable.num4;picArray[15] = R.drawable.num8;picArray[23] = R.drawable.num16;picArray[39] = R.drawable.num32;picArray[71] = R.drawable.num64;picArray[135] = R.drawable.num128;picArray[263] = R.drawable.num256;picArray[519] = R.drawable.num512;picArray[1031] = R.drawable.num1024;picArray[2055] = R.drawable.num2048;// 七龙珠版+10picArray[10] = R.drawable.num0;picArray[12] = R.drawable.wk1;picArray[14] = R.drawable.wk2;picArray[18] = R.drawable.wk3;picArray[26] = R.drawable.wk4;picArray[42] = R.drawable.wk5;picArray[74] = R.drawable.wk6;picArray[128] = R.drawable.wk7;picArray[266] = R.drawable.wk8;picArray[522] = R.drawable.wk9;picArray[1034] = R.drawable.wk10;picArray[2058] = R.drawable.wk11;// 其他版+17picArray[17] = R.drawable.num0;picArray[19] = R.drawable.hy1;picArray[21] = R.drawable.hy2;picArray[25] = R.drawable.hy3;picArray[33] = R.drawable.hy4;picArray[49] = R.drawable.hy5;picArray[81] = R.drawable.hy6;picArray[145] = R.drawable.hy7;picArray[273] = R.drawable.hy8;picArray[529] = R.drawable.hy9;picArray[1041] = R.drawable.hy10;picArray[2065] = R.drawable.hy11;}// 数字:数字相当于图片idpublic int getNum() {return num;}public void setNum(int num) {this.num = num;my2048 = (MainActivity) this.getContext();model = my2048.getModel();System.out.println(">>>>>>>>>>" + model);switch (model) {case 1:// 普通模式pic.setBackgroundResource(picArray[num + 7]);break;case 2:// 数码模式pic.setBackgroundResource(picArray[num]);break;case 3:// 七龙珠模式pic.setBackgroundResource(picArray[num + 10]);break;case 4:// 火影模式pic.setBackgroundResource(picArray[num + 17]);break;}}// 判断数字是否相同public boolean equals(CardView cv) {return getNum() == cv.getNum();}}
/** * @author Clark Xu * 音效播放类MyPlayer, * 因为是放在asset目录下读取方式有所不同 * */public class MyPlayer {public final static int INDEX_SONG_MOVE = 0;public final static int INDEX_SONG_CANCEL = 1;public final static int INDEX_SONG_BEGIN = 2;private final static String[] SONG_NAMES = { "cancel.mp3", "move.mp3","braveheart.mp3" };// 音效播放private static MediaPlayer[] mToneMediaPlayer = new MediaPlayer[SONG_NAMES.length];/* * 播放声音效果 cancel move */public static void playTone(Context context, int index) {// 加载声音AssetManager assetManager = context.getAssets();if (mToneMediaPlayer[index] == null) {mToneMediaPlayer[index] = new MediaPlayer();try {AssetFileDescriptor fileDescriptor = assetManager.openFd(SONG_NAMES[index]);mToneMediaPlayer[index].setDataSource(fileDescriptor.getFileDescriptor(),fileDescriptor.getStartOffset(),fileDescriptor.getLength());mToneMediaPlayer[index].prepare();} catch (Exception e) {// TODO: handle exceptione.printStackTrace();}}mToneMediaPlayer[index].start();}}
<?xml version="1.0" encoding="utf-8"?><!-- 主界面的布局方式 --><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_weight="0.65"        android:orientation="horizontal" >        <TextView            android:id="@+id/scoreView1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="@string/score"            android:textSize="22sp" />        <TextView            android:id="@+id/scoreView2"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textSize="22sp" />        <TextView            android:id="@+id/scoreView3"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="@string/bestscore"            android:textSize="22sp" />        <TextView            android:id="@+id/scoreView4"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textSize="22sp" />    </LinearLayout>    <!-- 把自定义组件的全路径写下来 -->    <com.lanjie.game2048.GameView        android:id="@+id/GameView"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="10" />      <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="horizontal" >        <Button            android:id="@+id/startBtn"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_weight="1"            android:text="@string/startBtn" />        <Button            android:id="@+id/pauseBtn"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_weight="1"            android:text="@string/pauseBtn" />        <Button            android:id="@+id/stopBtn"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_weight="1"            android:text="@string/stopBtn" />            </LinearLayout>    <Button        android:id="@+id/resbtn"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="@string/restart" /></LinearLayout>

这个2048是暑假就做了的,一直拖呀拖呀,有些事情能尽快完成就不要拖,事情这么多,一放下就不想做了,还是要培养雷厉风行的习惯性格,最近更的还是有点少,接下来有一堆任务要干,争取做完一个消化整理就放到博客上来,相信一个人的博客会体现出对技术的用心和热情,加油~