android自定义View 别踩白块儿

作者: Hia_chuang | 来源:发表于2017-04-29 10:44 被阅读215次

    废话不说,先贴张原游戏的图片,和我们自己最后的效果图,直接进入正题。

    原图
    效果图

    可以看到别踩白块儿,将屏幕的宽和高都分成了四部分,十六小块,然后将不同的小块填充为不同的颜色,在原游戏中还有一种长长的黑块,而我们这个是简化板,只保留了每个黑块只占一小块。

    由于每个小块,都是矩形块,我们选择用canvas.drawRect()方法来绘制,那么首先要定义一个PiecesRectF类继承自RectF,并在PiecesRectF类中定义矩形块不同的状态。

    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随机一个白块或黑块
        }
    
        public void setType(int type) {
            this.type = type;
        }
    
        public int getType() {
            return type;
        }
    }
    

    接着就是最关键的自定义view了

        private final static String TAG = WhitePiecesView.class.getSimpleName();
    
        private Paint mPaint;
    
        private PiecesRectF[][] ovals = new PiecesRectF[5][4];//屏幕上的方格块
        private SparseArray<PiecesRectF> selectOvals = new SparseArray<PiecesRectF>();//点击时选中的方格块
    
        private int topOvalHeight = 0;//最上面一行方格的高度
        private int score = 0;
    
        private boolean isGameOver;//游戏是否结束
        private boolean once = true;
    
        public WhitePiecesView(Context context) {
            this(context,null);
        }
    
        public WhitePiecesView(Context context, AttributeSet attrs) {
            this(context, attrs,0);
        }
    
        public WhitePiecesView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            mPaint = new Paint();
            initOvals();//初始化方块
        }
    
        private void initOvals(){
    
            for (int i = 0;i < 5;i++){
                for (int j = 0;j<4;j++){
    
                    ovals[i][j] = new PiecesRectF();
                    if (j == 1){
                        //如果是第二列,则若第一列的是黑块将这个方块设为白块
                        if (ovals[i][j-1].getType() == PiecesRectF.BLAKE ||
                                ovals[i][j-1].getType() == PiecesRectF.START) {
                            ovals[i][j].setType(PiecesRectF.WRITE);
                        }
                    }else if (j == 3){
                        //如果是第四列,同样若第一列的是黑块将这个方块设为白块
                        if (ovals[i][j-1].getType() == PiecesRectF.BLAKE ||
                                ovals[i][j-1].getType() == PiecesRectF.START) {
                            ovals[i][j].setType(PiecesRectF.WRITE);
                        }
                        //如果是第四列,且前四列的都为白块,则设为黑块
                        else if (ovals[i][j-2].getType() == PiecesRectF.WRITE &&
                                ovals[i][j-3].getType() == PiecesRectF.WRITE) {
                            ovals[i][j].setType(PiecesRectF.BLAKE);
                        }
                    }
    
                    if (i == 4){
                        if (ovals[i][j].getType() == PiecesRectF.BLAKE)
                            ovals[i][j].setType(PiecesRectF.START);//若是最后一行,将黑块的type替换为START
                
                    }
                }
            }
        }
    
    

    根据对原游戏的观察,在方块向下移动过程中,一共有5 * 4个,因此定义一个二维数组,并且在每一行的第一第二列只会有一个黑块,同样第三第四列也是。而且每一行一定会有一个黑块。因此在初始化时加上以上的判断条件,并且设置type。
    再来设置一个监听,来监听分数变化和游戏是否结束

    private WhitePiecesListener MyWhitePiecesListener;
        
        public void setWhitePiecesListener(WhitePiecesListener myWhitePiecesListener) {
            MyWhitePiecesListener = myWhitePiecesListener;
        }
    
        public interface WhitePiecesListener{
            void getScore(int score);
            void gameOver();
        }
    

    重写onDraw方法,绘制方块

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            drawRect(canvas);
        }
    
        private void drawRect(Canvas canvas){
            int w = getWidth()/4;//得到每个小方块的宽
            int h = getHeight()/4;//得到每个小方块的高
            if (isGameOver){
                //游戏结束
                MyWhitePiecesListener.gameOver();
                isGameOver = false;
            }
            for (int i = 0;i < 5;i++){
                for (int j = 0;j < 4;j++){
    
                    ovals[i][j].left = w * j;
                    ovals[i][j].right = w * (j + 1);
                    ovals[i][j].bottom = topOvalHeight + i * h;
                    ovals[i][j].top = ovals[i][j].bottom - h;
    
                    mPaint.setStyle(Paint.Style.FILL);//绘制色块时,设置画笔为FILL
                    switch (ovals[i][j].getType()){
                        case PiecesRectF.BLAKE:{
                            //绘制黑块
                            mPaint.setColor(Color.BLACK);
                            canvas.drawRect(ovals[i][j],mPaint);
                            break;
                        }
                        case PiecesRectF.BLUE:{
                            //绘制蓝块
                            mPaint.setColor(Color.BLUE);
                            canvas.drawRect(ovals[i][j],mPaint);
                            break;
                        }
                        case PiecesRectF.RED:{
                            //绘制红块
                            mPaint.setColor(Color.RED);
                            canvas.drawRect(ovals[i][j],mPaint);
                            break;
                        }
                        case PiecesRectF.START:{
                            //先绘制黑块
                            mPaint.setColor(Color.BLACK);
                            canvas.drawRect(ovals[i][j],mPaint);
    
                            //在绘制文字
                            mPaint.setColor(Color.parseColor("#ffffff"));
                            mPaint.setTextAlign(Paint.Align.CENTER);
                            mPaint.setTextSize(50);
    
                            String start = "开始";
                            Rect bounds = new Rect();
                            mPaint.getTextBounds(start,0,start.length(),bounds);
                            float x = ovals[i][j].left / 2 + ovals[i][j].right / 2;
                            float y = ovals[i][j].top / 2 + ovals[i][j].bottom / 2 + bounds.bottom / 2 - bounds.top / 2;
                            canvas.drawText(start,x,y,mPaint);
                            break;
                        }
                    }
                    //设置画笔为STROKE,绘制边框
                    mPaint.setStyle(Paint.Style.STROKE);
                    mPaint.setColor(Color.parseColor("#ffffff"));
                    mPaint.setStrokeWidth(3);
                    canvas.drawRect(ovals[i][j],mPaint);
                }
            }
        }
    

    在drawRect()中,对二维数组进行遍历,并设定每个方块的位置,然后根据每个方块type的不同,进行了不同的绘制方法,最后绘制白色边框。
    绘制完后,我们需要设定点击事件,在点击STRAT的方块时,游戏开始所有方块向下滑,得分+1,开始后点击BLACK,得分+1,点击WHITE,游戏结束。因此重写onTouchEvent(MotionEvent event),并且使用多点触控。

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            int index = event.getActionIndex();
            switch (event.getActionMasked()){
                case ACTION_DOWN:
                case ACTION_POINTER_DOWN: {
                    int id = event.getPointerId(index);
                    for (int i = 0; i < 5; i++) {
                        for (int j = 0; j < 4; j++) {
                            PiecesRectF f = ovals[i][j];
                            if (event.getX() > f.left && event.getX() < f.right
                                    && event.getY() > f.top && event.getY() < f.bottom) {
                                selectOvals.put(id, f);//点击选中的方块存入SparseArray
    
                                switch (f.getType()){
                                    case PiecesRectF.BLAKE:{
                                        if (!once) {
                                            f.setType(PiecesRectF.BLUE);
                                            score++;
                                        }
                                        break;
                                    }
                                    case PiecesRectF.START:{
                                        if (once){
                                            //判断第一次按中
                                            startThread();
                                            once = false;
                                        }
                                        //按黑块处理
                                        f.setType(PiecesRectF.BLUE);
                                        score++;
                                        break;
                                    }
                                    case PiecesRectF.WRITE:{
                                        if (!once) {
                                            //点中白块GameOver
                                            f.setType(PiecesRectF.RED);
                                            isGameOver = true;
                                            invalidate();
                                        }
                                        break;
                                    }
                                }
                                MyWhitePiecesListener.getScore(score);
                            }
                        }
                    }
                    break;
                }
                case ACTION_UP:
                case ACTION_POINTER_UP:{
                    int id = event.getPointerId(index);
                    PiecesRectF f = selectOvals.get(id,null);//得到某个手指选中的方块
                    if (f != null && f.getType() == PiecesRectF.BLUE ){
                        //手指抬起后,将蓝色重新变红
                        f.setType(PiecesRectF.WRITE);
                    }
                    break;
                }
            }
            return true;
        }
    

    onTouchEvent(MotionEvent event)方法中,对点击不同方块进行了不同的处理,并且在第一次按下START的白块时,执行了startThread()方法来,实现所有方块的向下滑动.

    private void startThread(){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        topOvalHeight =topOvalHeight + 7;//这里写死了滑动速度
                        if (isGameOver) {//如果游戏已经结束,结束循环
                            return;
                        }
                        if (topOvalHeight > getHeight()/4) {
                            topOvalHeight = 0;//若最顶层的方块的高,超出正常方块的的高,清零。
                            if (checkBottomOvals()){//检测是否有黑色方块漏点
                                //有漏点,游戏结束
                                isGameOver = true;
                                postInvalidate();
                                return;
                            }else
                                //没有漏点,更新方块游戏结束
                                updateRectF();
                        }
                        try {
                            Thread.sleep(15);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        postInvalidate();
                    }
                }
            }).start();
        }
    
    

    然后是检测是否有黑色方块漏点的checkBottomOvals(),和更新滑块的updateRectF()

        /**
          *检测是否有漏点
          */
        private boolean checkBottomOvals(){
            boolean haveBlake = false;
            for (int i = 0;i < 4;i++){
                if (ovals[4][i].getType() == PiecesRectF.BLAKE
                        || ovals[4][i].getType() == PiecesRectF.START) {
                    //判断最后一行是否存在BLAKE或START方块
                    //如果有将其设为红色
                    ovals[4][i].setType(PiecesRectF.RED);
                    haveBlake = true;
                }
            }
            return haveBlake;
        }
    
        /**
         * 更新方块
         */
        private void updateRectF(){
    
            for (int i = 4; i >= 0; i--) {
                for (int j = 0; j < 4; j++) {
                    if (i == 0) {//如果是第一行,重新初始化一行
                        ovals[i][j] = new PiecesRectF();
    
                        if (j == 1){
                            if (ovals[i][j-1].getType() == PiecesRectF.BLAKE)
                                ovals[i][j].setType(PiecesRectF.WRITE);
                        }else if (j == 3){
                            if (ovals[i][j-1].getType() == PiecesRectF.BLAKE)
                                ovals[i][j].setType(PiecesRectF.WRITE);
                            else if (ovals[i][j-2].getType() == PiecesRectF.WRITE &&
                                    ovals[i][j-3].getType() == PiecesRectF.WRITE)
                                ovals[i][j].setType(PiecesRectF.BLAKE);
                        }
                    }
                    else//否则,将行数后移
                        ovals[i][j] = ovals[i - 1][j];
                }
            }
        }
    

    最后在加上一个重新开始的方法

    public void restart(){
            once = true;
            topOvalHeight = 0;//顶层高度归零
            score = 0;//分数归零
            MyWhitePiecesListener.getScore(score);
            initOvals();//重新初始化
            invalidate();//再次绘制
        }
    

    实测

    先是一个很简单的布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout 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="com.whitepieces.MainActivity"
        android:background="@drawable/bg">
    
        <com.whitepieces.WhitePiecesView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/write_pieces"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/score"
            android:textSize="70sp"
            android:text="0"
            android:textColor="#FF3080"
            android:layout_centerHorizontal="true"/>
    </RelativeLayout>
    

    然后是MainActivity

    public class MainActivity extends AppCompatActivity implements WhitePiecesView.WhitePiecesListener{
        private WhitePiecesView whitePiecesView;
        private TextView scoreText;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            whitePiecesView = (WhitePiecesView)findViewById(R.id.write_pieces);
            scoreText = (TextView)findViewById(R.id.score);
            whitePiecesView.setWhitePiecesListener(this);
        }
    
        @Override
        public void getScore(int score) {
            scoreText.setText(""+ score);
        }
    
        @Override
        public void gameOver() {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("Game Over")
                    .setMessage("您获得的分数为"+scoreText.getText()+"是否重新开始")
                    .setNegativeButton("是", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            whitePiecesView.restart();
                            return;
                        }
                    })
                    .setPositiveButton("否", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            finish();
                        }
                    }).show();
        }
    }
    

    同样很简单,就是显示分数,以及游戏结束时,弹出Dialog选择退出或重新开始.
    最后来张GIF图

    Test.gif

    源码点击下载

    相关文章

      网友评论

      本文标题:android自定义View 别踩白块儿

      本文链接:https://www.haomeiwen.com/subject/ylczzttx.html