这篇文章通过自定义ViewGroup实现前段时间挺火的一个游戏——别踩白块。好了先看一下效果图:
好了,下面我说一下我的思路,大家都知道重写ViewGroup的步骤:onMesure(),onDraw( ),onLayout(),这里我只需要重写onDraw()和onTouchEvent();好了,我首先创建的一个矩形类继承RectF类,创建了误了静态常量和两个构造函数:
import android.graphics.RectF;
/**
* Author : luweicheng on 2017/4/30 0030 12:16
* E-mail :[email protected]
* GitHub : https://github.com/luweicheng24
* funcation: 矩形框类
*/
public class PiecesRectF extends RectF {
private int type;
public final static int BLAKE = 0;//黑块
public final static int WRITE = 1;//白块
public final static int BLUE = 2;//黑块按下时的显示蓝块
public final static int START = 3;//标记有开始的黑块
public final static int RED = 4;//按到白块,或有黑块漏按,游戏结束时的红块
public PiecesRectF(){
super();
type = Math.random() > 0.5 ? 0:1;//初始化时,给type随机一个白块或黑块
}
/**
* 创建最后一行的矩形
* @param isLast
*/
public PiecesRectF(Boolean isLast){
super();
}
public void setType(int type) {
this.type = type;
}
public int getType() {
return type;
}
}
该类对象就想到于每个矩形框,类型有五种类型,注释也已经写的很清楚了。下面我们就来继承ViewGroup来自定义我们的ViewGroup:继承之后我们先来对初始化五行四列的二维数组来存放矩形框,由于第五行的矩形框要作为开始按钮,所以单独创建了第五行,第五行只有一个类型的开始的矩形框,其他的都是类型为白色的矩形框:
/**
* 初始化5行4列的矩形框
*/
private void initRect() {
/**
* 创建4行4列的方块(每行1,2列最少有一个黑块,3,4列最少有一个黑块)
*/
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
recfs[i][j] = new PiecesRectF();
if (j == 1) {
if (recfs[i][j - 1].getType() == PiecesRectF.BLAKE || recfs[i][j - 1].getType() == PiecesRectF.START) {
recfs[i][j].setType(PiecesRectF.WRITE);
}
} else if (j == 3) {
if (recfs[i][j - 1].getType() == PiecesRectF.BLAKE || recfs[i][j - 1].getType() == PiecesRectF.START) {
recfs[i][j].setType(PiecesRectF.WRITE);
}
if (recfs[i][j - 2].getType() == PiecesRectF.WRITE && recfs[i][j - 3].getType() == PiecesRectF.WRITE) {
recfs[i][j].setType(PiecesRectF.BLAKE);
}
}
}
}//创建第五行的数据,只需要一个类型为开始的矩形,其他的都是白色
for (int i = 0; i < 4; i++) {
recfs[4][i]= new PiecesRectF(true);
if(i==1){
recfs[4][i].setType(PiecesRectF.START);
}else {
recfs[4][i].setType(PiecesRectF.WRITE);
}
}
}
初始化好了矩形框,接下来就是绘制矩形框绘制的时候由于要保持界面只呈现四行四列的黑白快,并且界面是从上往下滑动,所以我们初始化的时候将第一行的矩形框根据左右上下的设置将其基本是绘制成了一条线,因此用户看见的第一行矩形框其实就是第二行矩形框,下面是重写OnDraw方法根据二位数据里面矩形框的类型来绘制:
/**
* 绘制矩形框
*
* @param canvas
*/
private void drawRects(Canvas canvas) {
int w = getWidth() / 4;
int h = getHeight() / 4;
if (isGameOver) {
if(onBalckCheckListener !=null){
onBalckCheckListener.gameOver();
}
isGameOver = false;
}
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 4; j++) {
recfs[i][j].left = w * j;
recfs[i][j].right = w * (j + 1);
recfs[i][j].bottom = topRectHeight + i * h;
recfs[i][j].top = recfs[i][j].bottom - h;
paint.setStyle(Paint.Style.FILL);//设置画笔为填充整个矩形
if (recfs[i][j].getType() == PiecesRectF.WRITE) {
paint.setColor(Color.WHITE);
canvas.drawRect(recfs[i][j], paint);
} else if (recfs[i][j].getType() == PiecesRectF.BLAKE) {
paint.setColor(Color.BLACK);
canvas.drawRect(recfs[i][j], paint);
} else if (recfs[i][j].getType() == PiecesRectF.BLUE) {
paint.setColor(Color.BLUE);
canvas.drawRect(recfs[i][j], paint);
} else if (recfs[i][j].getType() == PiecesRectF.RED) {
paint.setColor(Color.RED);
canvas.drawRect(recfs[i][j], paint);
} else if (recfs[i][j].getType() == PiecesRectF.START) {
paint.setColor(Color.BLACK);
canvas.drawRect(recfs[i][j], paint);
//在绘制文字
paint.setColor(Color.parseColor("#ffffff"));
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(50);
String start = "开始";
Rect bounds = new Rect();
paint.getTextBounds(start, 0, start.length(), bounds);
float x = recfs[i][j].left / 2 + recfs[i][j].right / 2;
float y = recfs[i][j].top / 2 + recfs[i][j].bottom / 2 + bounds.bottom / 2 - bounds.top / 2;
canvas.drawText(start, x, y, paint);
}
/**
* 绘制边框
*/
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.parseColor("#cccccc"));
paint.setStrokeWidth(3);
canvas.drawRect(recfs[i][j], paint);
}
}
}
可以看到由于topRectHeight的初始化为0,所以绘制的第一行在界面看不见,所以我们可以根据不断增加该值之后的绘制实现向下滑动,绘制结果如下:
好了,绘制成功喽,下面就是对它每个矩形点击的监听处理:
@Override
public boolean onTouchEvent(MotionEvent event) {
int index = event.getActionIndex();
switch (event.getActionMasked()){
case ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:{
int id = event.getPointerId(index);
//判断点击的是那个矩形块
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 4; j++) {
PiecesRectF f = recfs[i][j];
if (event.getX() > f.left && event.getX() < f.right
&& event.getY() < f.bottom && event.getY() > f.top) {
selectRecfs.put(id, f);
if (f.getType() == PiecesRectF.BLAKE) {
f.setType(PiecesRectF.BLUE);
score++;
} else if (f.getType() == PiecesRectF.WRITE) {
f.setType(PiecesRectF.RED);
isGameOver = true;
postInvalidate();
} else if (f.getType() == PiecesRectF.START) {
f.setType(PiecesRectF.BLUE);
startThread();
}
if(onBalckCheckListener !=null){
onBalckCheckListener.score(score);
}
}
}
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP: {
int id = event.getPointerId(index);
PiecesRectF f = selectRecfs.get(id, null);//得到某个手指选中的方块
if (f != null && f.getType() == PiecesRectF.BLUE) {
//手指抬起后,将蓝色重新变红
f.setType(PiecesRectF.WRITE);
}
}
break;
}
return true;//事件终止传递
}
在事件处理中对矩形的类型进行判断处理,将点击的矩形保存在SparseArray数组中,如果点击的是开始,将开启一个线程对矩形数组和检查是否有黑框漏点:
/**
* 开启线程不断的对界面进行绘制
*/
private void startThread() {
new Thread(){
@Override
public void run() {
while (true) {
/**
* 判断是否含有黑块接触屏幕底端
*/
if(topRectHeight>0){
if(checkHasBlack()){
isGameOver = true;
postInvalidate();
return;
}
}
topRectHeight += getSpeed();//固定的矩形下滑速度
if(isGameOver){
return;
}
if (topRectHeight > getHeight() / 4) {//如果顶层的方块高度超出清零
topRectHeight = 0;
updateView();//更新矩形块
}
try {
sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
postInvalidate();
}
}
}.start();
}
/**
* 检查最后一行是否含有黑块
* @return
*/
private Boolean checkHasBlack() {
Boolean hasBlack = false;
for (int i = 0; i < 4; i++) {
if (recfs[4][i].getType() == PiecesRectF.BLAKE||recfs[4][i].getType() == PiecesRectF.START){
recfs[4][i].setType(PiecesRectF.RED);
hasBlack = true;
}
}
return hasBlack;
}
/**
* 从倒数第二行开始跟新到未出现的第一行
*/
private void updateView(){
for (int i = 4; i >=0; i--) {
for (int j = 0; j < 4; j++) {
if(i==0){
recfs[i][j] = new PiecesRectF();
if (j == 1){
if (recfs[i][j-1].getType() == PiecesRectF.BLAKE)
recfs[i][j].setType(PiecesRectF.WRITE);
}else if (j == 3){
if (recfs[i][j-1].getType() == PiecesRectF.BLAKE)
recfs[i][j].setType(PiecesRectF.WRITE);
else if (recfs[i][j-2].getType() == PiecesRectF.WRITE &&
recfs[i][j-3].getType() == PiecesRectF.WRITE)
recfs[i][j].setType(PiecesRectF.BLAKE);
}
}else{
recfs[i][j] = recfs[i-1][j];
}
}
}
}
创建一个接口来监听分数和游戏是否结束:
/**
* 重新开始游戏
*/
public void restart(){
isGameOver = false;
topRectHeight= 0;//顶层高度归零
score = 0;//分数归零
onBalckCheckListener.score(score);
initRect();//重新初始化
invalidate();//再次绘制
}
public interface OnBalckCheckListener{
void score(int score);
void gameOver();
}
private OnBalckCheckListener onBalckCheckListener;
public void setOnBalckCheckListener(OnBalckCheckListener onBalckCheckListener){
this.onBalckCheckListener = onBalckCheckListener;
}
ok,核心代码就是这样Github源码下载,记得Star奥