学习自http://blog.csdn.net/lmj623565791/article/details/40020137
原博主用继承自RelativeLayout的布局实现,我用专门实现网格布局的GridLayout重新实现。另外用二维数组来存储每一个子View,逻辑上更清晰。
先上效果图
其中的每一个格子是一个自定义的View
public class Game2048Item extends View {
private Paint paint;
private int number;
private Rect textRect;
private String numberValue;
public Game2048Item(Context context) {
this(context, null);
}
public Game2048Item(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Game2048Item(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
}
public void setNumber(int number) {
this.number = number;
numberValue = "" + number;
paint.setTextSize(50);
textRect = new Rect();
paint.getTextBounds(numberValue, 0, numberValue.length(), textRect);
invalidate();
}
public int getNumber() {
return number;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String backColor;
switch (number) {
case 0:
backColor = "#CCC0B3";
break;
case 2:
backColor = "#EEE4DA";
break;
case 4:
backColor = "#EDE0C8";
break;
case 8:
backColor = "#F2B179";
break;
case 16:
backColor = "#F49563";
break;
case 32:
backColor = "#F5794D";
break;
case 64:
backColor = "#F55D37";
break;
case 128:
backColor = "#EEE863";
break;
case 256:
backColor = "#EDB04D";
break;
case 512:
backColor = "#ECB04D";
break;
case 1024:
backColor = "#EB9437";
break;
case 2048:
backColor = "#EA7821";
break;
default:
backColor = "#EA7821";
break;
}
/*每个方块的背景*/
paint.setColor(Color.parseColor(backColor));
paint.setStyle(Paint.Style.FILL);
canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),paint);
/*每个方块上的数字*/
if (number != 0) {
paint.setColor(Color.BLACK);
float x = (getMeasuredWidth()-textRect.width())/2;
float y = getMeasuredHeight()/2 + textRect.width()/2;
canvas.drawText(numberValue, x, y, paint);
}
}
}
public class Game2048GridLayout extends GridLayout {
private int childWidth;
private int padding;
private int childRow = 4;
private int margin = 10;
private boolean isLayout = false;
private Game2048Item[][] game2048Items = null;
private GestureDetector gestureDetector;
private boolean isMoveHappen = false;
private boolean isMergeHappen = false;
private boolean isFirst = true;
private int score;
private OnGame2048Listener onGame2048Listener;
/*枚举手势操作类型*/
private enum Action {
UP, DOWN, LEFT, RIGHT
}
public Game2048GridLayout(Context context) {
this(context, null);
}
public Game2048GridLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Game2048GridLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
padding = Math.min(getPaddingBottom(), getPaddingTop());
gestureDetector = new GestureDetector(context, new MyGestureListener());
score = 0;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/*布局的实际长宽一致,为两者中的最小值*/
int length = Math.min(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
childWidth = (length - 2 * padding - (childRow - 1) * margin) / childRow;
setMeasuredDimension(length, length);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
/*避免多次加载*/
if (!isLayout) {
if (game2048Items == null) {
game2048Items = new Game2048Item[childRow][childRow];
}
for (int i = 0; i < childRow; i++) {
for (int j = 0; j < childRow; j++) {
Game2048Item child = new Game2048Item(getContext());
game2048Items[i][j] = child;
Spec row = GridLayout.spec(i);
Spec column = GridLayout.spec(j);
GridLayout.LayoutParams lp = new LayoutParams(row, column);
/*每个子View的宽高*/
lp.width = childWidth;
lp.height = childWidth;
if ((j + 1) != childRow) {
lp.rightMargin = margin;
}
if (i > 0) {
lp.topMargin = margin;
}
lp.setGravity(Gravity.FILL);
addView(child, lp);
}
}
/*生成、显示数字*/
generateNum();
}
isLayout = true;
}
private void generateNum() {
if (isGameOver()) {
onGame2048Listener.onGameOver();
return;
}
/*初始化,随机产生四个非零子View*/
if (isFirst) {
for (int i = 0; i < 4; i++) {
int randomRow = new Random().nextInt(childRow);
int randomCol = new Random().nextInt(childRow);
Game2048Item item = game2048Items[randomRow][randomCol];
while (item.getNumber() != 0) {
randomRow = new Random().nextInt(childRow);
randomCol = new Random().nextInt(childRow);
item = game2048Items[randomRow][randomCol];
}
item.setNumber(Math.random() > 0.75 ? 4 : 2);
Animation scaleAnimation = new ScaleAnimation(0, 1, 0, 1,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(200);
item.startAnimation(scaleAnimation);
isMoveHappen = isMergeHappen = false;
}
isFirst = false;
}
if (!isFull()) {
if (isMoveHappen || isMergeHappen) {
int randomRow = new Random().nextInt(childRow);
int randomCol = new Random().nextInt(childRow);
Game2048Item item = game2048Items[randomRow][randomCol];
while (item.getNumber() != 0) {
randomRow = new Random().nextInt(childRow);
randomCol = new Random().nextInt(childRow);
item = game2048Items[randomRow][randomCol];
}
item.setNumber(Math.random() > 0.75 ? 4 : 2);
Animation scaleAnimation = new ScaleAnimation(0, 1, 0, 1,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(200);
item.startAnimation(scaleAnimation);
isMoveHappen = isMergeHappen = false;
}
}
}
/*触摸事件交由手势监听器处理*/
@Override
public boolean onTouchEvent(MotionEvent event) {
gestureDetector.onTouchEvent(event);
return true;
}
private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
final int MIN_DISTANCE = 50;
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
float x = e2.getX() - e1.getX();
float y = e2.getY() - e1.getY();
float absX = Math.abs(x);
float absY = Math.abs(y);
if (x > MIN_DISTANCE && absX > absY) {
action(Action.RIGHT);
}
if (x < -MIN_DISTANCE && absX > absY) {
action(Action.LEFT);
}
if (y > MIN_DISTANCE && absY > absX) {
action(Action.DOWN);
}
if (y < -MIN_DISTANCE && absY > absX) {
action(Action.UP);
}
return true;
}
}
private void action(Action action) {
for (int i = 0; i < childRow; i++) {
/*用来存储行或列的临时列表*/
List<Game2048Item> rowList = new ArrayList<>();
for (int j = 0; j < childRow; j++) {
int rowIndex = getRowIndexByAction(action, i, j);
int colIndex = getColIndexByAction(action, i, j);
Game2048Item item = game2048Items[rowIndex][colIndex];
/*将非零的子项存入临时列表*/
if (item.getNumber() != 0) {
rowList.add(item);
}
}
for (int j = 0; j < rowList.size(); j++) {
int rowIndex = getRowIndexByAction(action, i, j);
int colIndex = getColIndexByAction(action, i, j);
Game2048Item item = game2048Items[rowIndex][colIndex];
/*如果临时列表(除去数值为0的项)里的子项与移动前的此列或者行出现不同,说明移动发生*/
if (item.getNumber() != rowList.get(j).getNumber()) {
isMoveHappen = true;
}
}
/*在临时列表里合并相同数字*/
mergeItem(rowList);
for (int j = 0; j < childRow; j++) {
/*依次将临时列表里的数字填入本列/行,剩余直接填0*/
if (rowList.size() > j) {
switch (action) {
case LEFT :
game2048Items[i][j].setNumber(rowList.get(j).getNumber());
break;
case RIGHT:
game2048Items[i][childRow - 1 - j].setNumber(rowList.get(j).getNumber());
break;
case UP:
game2048Items[j][i].setNumber(rowList.get(j).getNumber());
break;
case DOWN:
game2048Items[childRow - 1 - j][i].setNumber(rowList.get(j).getNumber());
break;
}
} else {
switch (action) {
case LEFT :
game2048Items[i][j].setNumber(0);
break;
case RIGHT:
game2048Items[i][childRow - 1 - j].setNumber(0);
break;
case UP:
game2048Items[j][i].setNumber(0);
break;
case DOWN:
game2048Items[childRow - 1 - j][i].setNumber(0);
break;
}
}
}
}
generateNum();
}
/*根据手势方向确定如何取得子项。例如向上滑动,则从列方向从上到下取得每一个子项在game2048Item里的对应坐标*/
private int getRowIndexByAction(Action action, int i, int j) {
int rowIndex = -1;
switch (action) {
case LEFT:
case RIGHT:
rowIndex = i;
break;
case UP:
rowIndex = j;
break;
case DOWN:
rowIndex = childRow - 1 - j;
break;
}
return rowIndex;
}
private int getColIndexByAction(Action action, int i, int j) {
int colIndex = -1;
switch (action) {
case LEFT:
colIndex = j;
break;
case RIGHT:
colIndex = childRow - 1 - j;
break;
case UP:
case DOWN:
colIndex = i;
break;
}
return colIndex;
}
private void mergeItem(List<Game2048Item> rowList) {
if (rowList.size() < 2) {
return;
}
for (int i = 0; i < rowList.size() - 1; i++) {
Game2048Item item1 = rowList.get(i);
Game2048Item item2 = rowList.get(i + 1);
if (item1.getNumber() == item2.getNumber()) {
isMergeHappen = true;
score += item1.getNumber() * 2;
item1.setNumber(item1.getNumber() * 2);
onGame2048Listener.onScoreChange(score);
for (int j = i + 1; j < rowList.size() - 1; j++) {
/*将后一项的数字依次向前移*/
rowList.get(j).setNumber(rowList.get(j + 1).getNumber());
}
/*最后一项一定设置为0*/
rowList.get(rowList.size() - 1).setNumber(0);
return;
}
}
}
private boolean isGameOver() {
//*如果格子没有被非零数字填满*//*
if (!isFull()) {
return false;
}
//*如果填满了,检验是否有相邻子项具有相同数字*//*
for (int i = 0; i < childRow; i++) {
for (int j = 0; j < childRow; j++) {
Game2048Item item = game2048Items[i][j];
// 如果不是最后一列,则与右边项相比
if ((j + 1) != childRow) {
Game2048Item itemRight = game2048Items[i][j + 1];
if (item.getNumber() == itemRight.getNumber())
return false;
}
// 如果不是最后一行,则与下边项相比
if ((i + 1) != childRow) {
Log.e("TAG", "DOWN");
Game2048Item itemBottom = game2048Items[i + 1][j];
if (item.getNumber() == itemBottom.getNumber())
return false;
}
// 如果不是第一列,则与左边项相比
if (j != 0) {
Log.e("TAG", "LEFT");
Game2048Item itemLeft = game2048Items[i][j - 1];
if (itemLeft.getNumber() == item.getNumber())
return false;
}
// 如果不是第一行,则与上边项相比
if (i != 0) {
Log.e("TAG", "UP");
Game2048Item itemTop = game2048Items[i - 1][j];
if (item.getNumber() == itemTop.getNumber())
return false;
}
}
}
return true;
}
public boolean isFull() {
for (int i = 0; i < childRow; i++) {
for (int j = 0; j < childRow; j++) {
Game2048Item game2048Item = game2048Items[i][j];
if (game2048Item.getNumber() == 0)
return false;
}
}
return true;
}
/*对外接口,由调用方决定具体逻辑*/
public interface OnGame2048Listener {
void onScoreChange(int score);
void onGameOver();
}
public void setOnGame2048Listener(OnGame2048Listener onGame2048Listener) {
this.onGame2048Listener = onGame2048Listener;
}
public void reStart() {
for (int i = 0; i < childRow; i++) {
for (int j = 0; j < childRow; j++) {
Game2048Item item = game2048Items[i][j];
item.setNumber(0);
}
}
score = 0;
onGame2048Listener.onScoreChange(0);
isFirst = true;
generateNum();
}
}
其中提供了一个借口用于在activity中调用,让主程序决定要实现的操作
public class MainActivity extends AppCompatActivity implements Game2048Layout.OnGame2048Listener{
private TextView scoreView;
Game2048Layout game2048Layout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
game2048Layout = (Game2048Layout) findViewById(R.id.game);
game2048Layout.setOnGame2048Listener(this);
scoreView = (TextView) findViewById(R.id.score);
}
@Override
public void onScoreChange(int score) {
scoreView.setText("Score : "+score);
}
@Override
public void onGameOver() {
new AlertDialog.Builder(this).setTitle("game Over")
.setMessage("you have got "+ scoreView.getText()).setCancelable(false)
.setPositiveButton("Restart", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
game2048Layout.reStart();
}
}).setNegativeButton("Exit", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).show();
}
}