• Android快乐贪吃蛇游戏实战项目开发教程-06虚拟方向键(五)绘制方向键箭头


    本系列教程概述与目录:http://www.cnblogs.com/chengyujia/p/5787111.html
    本系列教程项目源码GitHub地址:https://github.com/jackchengyujia/HappySnake

    一、本文概述

    在上篇教程中,我们画了4个背景三角形,并且实现了点击变色的按钮效果。
    在本篇教程中,我们将在这4个三角形上分别绘制表示方向的箭头,并且让箭头也有点击变色的效果。
    我们先看一下运行效果,有一个直观的了解,然后再从代码的角度分析和讲解。

    二、运行效果

    没有触摸时的效果:

    按左键时的效果:

    按上键时的效果:

    按右键时的效果:

    按下键时的效果:

    三、代码分析

    我们前面画三角形是用Path对象来告诉程序我们要画的图形,这里画箭头也是这样的。
    先定义4个Path对象,分别用来表示4个箭头的路径数据。

        //画左箭头的路径
        private Path pathLeftArrow = new Path();
        //画上箭头的路径
        private Path pathUpArrow = new Path();
        //画右箭头的路径
        private Path pathRightArrow = new Path();
        //画下箭头的路径
        private Path pathDownArrow = new Path();

     
    然后在初始化方法中设置各自的路径数据。
    从上面的运行截图上可以看到,每个箭头由一个三角形和一个矩形组成。
    同样,在程序中也是先设计一个三角形,然后拼接一个矩形,这样一个箭头的path数据就设置好了。
    这里有两点需要注意:
    1.箭头上每个转折点的坐标要计算准确。
    2.我们只需要设置左箭头和上箭头的详细数据,右箭头和下箭头可以通过旋转来得到。
    箭头相关的初始化程序我都放在了initArrow()方法中了,该方法代码如下:

        //初始化与箭头相关的数据。
        private void initArrow() {
            /*
            这里我们规定:
            1.每个箭头由一个三角形和一个矩形组成;
            2.每个箭头整体的宽和高分别为画布宽和高的1/4;
            3.每个箭头的三角形部分和矩形部分在宽(左右箭头)或高(上下箭头)上各占一半。
            4.每个三角形矩形部分的高(左右箭头)或宽(上下箭头)为箭头整体高或宽的一半。
            */
    
            //每个箭头整体的宽和高。
            final int arrowWidth = width / 4;
            final int arrowHeight = height / 4;
    
            /*
            设计左箭头
            先设计三角形部分,再设计矩形部分。
            */
            //设置箭头尖的坐标。这里我们规定让左键头尖的横坐标为画布宽的1/16;纵坐标为画布高度的1/2,也就是垂直方向居中。
            int arrowStartX = width / 16;
            int arrowStartY = height / 2;
            //设计箭头的三角形部分
            int arrowX = arrowStartX;
            int arrowY = arrowStartY;
            //从箭头尖开始
            pathLeftArrow.moveTo(arrowX, arrowY);
            //直线移动到三角形的上顶点
            pathLeftArrow.lineTo(arrowX += arrowWidth / 2, arrowY -= arrowHeight / 2);
            //然后直线移动到三角形的下顶点
            pathLeftArrow.lineTo(arrowX, arrowY += arrowHeight);
            //闭合三角形
            pathLeftArrow.close();
    
            //重置坐标,准备设计矩形部分。在计算机中画矩形是最容易的,只要知道左上角和右下角两点的坐标即可。
            arrowX = arrowStartX;
            arrowY = arrowStartY;
            //矩形左边界到画布左边界的距离(左上角横坐标)
            float left = arrowX += arrowWidth / 2;
            //矩形上边界到画布上边界的距离(左上角纵坐标)
            float top = arrowY -= arrowHeight / 4;
            //矩形右边界到画布左边界的距离(右下角横坐标)
            float right = arrowX += arrowWidth / 2;
            //矩形下边界到画布上边界的距离(右下角纵坐标)
            float bottom = arrowY += arrowHeight / 2;
            //在已有三角形的基础上增加一个矩形。最后一个参数是一个枚举,只有两个值,Direction.CW表示顺时针,Direction.CCW表示逆时针。在我们这里选那个都行,没有影响。
            pathLeftArrow.addRect(left, top, right, bottom, Path.Direction.CW);
    
            /*
            设计右箭头
            由于右箭头与左箭头是中心对称图形,只要把左箭头旋转180度即可。这里我们使用矩阵来做旋转。
             */
            Matrix matrix = new Matrix();
            matrix.setRotate(180, width / 2, height / 2);
            pathLeftArrow.transform(matrix, pathRightArrow);
    
            /*
            设计上箭头
            和左箭头一样,先设计三角形部分,再设计矩形部分。
             */
            //设置箭头尖的坐标。这里我们规定让上键头尖的横坐标为画布宽的1/2,也就是水平方向居中;纵坐标为画布高度的1/16。
            arrowStartX = width / 2;
            arrowStartY = height / 16;
            //设计三角形部分
            arrowX = arrowStartX;
            arrowY = arrowStartY;
            pathUpArrow.moveTo(arrowX, arrowY);
            pathUpArrow.lineTo(arrowX += arrowWidth / 2, arrowY += arrowHeight / 2);
            pathUpArrow.lineTo(arrowX - +arrowWidth, arrowY);
            pathUpArrow.close();
            //设计矩形部分
            arrowX = arrowStartX;
            arrowY = arrowStartY;
            pathUpArrow.addRect(arrowX -= arrowWidth / 4, arrowY += arrowHeight / 2, arrowX += arrowWidth / 2, arrowY += arrowHeight / 2, Path.Direction.CW);
    
            /*
            设计下箭头
            同理,将上箭头旋转180度得到下箭头。
             */
            pathUpArrow.transform(matrix, pathDownArrow);
        }

    注释比较详细,看过之章节的朋友,看这个应该问题不大。


    下面是目前为止DirectionKeys类的全部代码:

    package net.chengyujia.happysnake;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Matrix;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    
    /**
     * 屏幕上的虚拟方向键
     * Created by ChengYuJia on 2016/8/19.
     */
    public class DirectionKeys extends View {
        //左三角形按压时的颜色(较亮)
        private int leftPressedColor = 0xFFFF0000;
        //左三角形正常显示的颜色(较暗)
        private int leftNormalColor = 0xFFAA0000;
        //上三角形按压时的颜色(较亮)
        private int upPressedColor = 0xFF00FF00;
        //上三角形正常显示的颜色(较暗)
        private int upNormalColor = 0xFF00AA00;
        //右三角形按压时的颜色(较亮)
        private int rightPressedColor = 0xFF0000FF;
        //右三角形正常显示的颜色(较暗)
        private int rightNormalColor = 0xFF0000AA;
        //下三角形按压时的颜色(较亮)
        private int downPressedColor = 0xFFFFFF00;
        //下三角形正常显示的颜色(较暗)
        private int downNormalColor = 0xFFAAAA00;
    
        //箭头按压时的颜色(较亮)
        private int arrowPressedColor = 0xFFFFFFFF;
        //箭头正常显示的颜色(较暗)
        private int arrowNormalColor = 0xFFAAAAAA;
    
        //画笔
        private Paint paint = new Paint();
    
        //画左三角形的路径
        private Path pathLeft = new Path();
        //画上三角形的路径
        private Path pathUp = new Path();
        //画右三角形的路径
        private Path pathRight = new Path();
        //画下三角形的路径
        private Path pathDown = new Path();
    
        //画左箭头的路径
        private Path pathLeftArrow = new Path();
        //画上箭头的路径
        private Path pathUpArrow = new Path();
        //画右箭头的路径
        private Path pathRightArrow = new Path();
        //画下箭头的路径
        private Path pathDownArrow = new Path();
    
        //画布的宽
        private int width;
        //画布的高
        private int height;
        //初始化方法是否执行过,确保初始化方法只执行一次。
        private boolean initDone = false;
        //记录当前哪个方向键被按下
        private Direction currentDirection = Direction.none;
    
        //只有一个参数的构造方法是我们在程序中通过“new”关键字创建实例时调用。
        public DirectionKeys(Context context) {
            super(context);
        }
    
        //有两个参数的构造方法是系统在XML布局文件中创建实例时调用。
        public DirectionKeys(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        //初始化方法
        private void init(Canvas canvas) {
            //抗锯齿。让图形边界的锯齿模糊,看起来图形边界将更加光滑。
            paint.setAntiAlias(true);
    
            /*获取画布的长和宽*/
            width = canvas.getWidth();
            height = canvas.getHeight();
            /*
            (小提示:在计算机中一般都是将左上角作为坐标原点的)
            画布上四个顶点和中心点的坐标如下:
            左上点 0,0
            左下点 0,height
            右上点 width,0
            右下点 width,height
            中心点 width/2,height/2
            */
    
            initBackgroundTriangle();
            initArrow();
        }
    
        //初始化与背景三角形相关的数据。
        private void initBackgroundTriangle() {
             /*设置左三角形的路径数据*/
            //从画布左上点开始
            pathLeft.moveTo(0, 0);
            //画直线到画布中心点
            pathLeft.lineTo(width / 2, height / 2);
            //再画直线到画布左下点
            pathLeft.lineTo(0, height);
            //自动闭合图形。从最后一个点(左下点)画直线到第一个点(左上点)。
            pathLeft.close();
    
            /*同理设置上三角形的路径数据*/
            pathUp.moveTo(0, 0);
            pathUp.lineTo(width / 2, height / 2);
            pathUp.lineTo(width, 0);
            pathUp.close();
    
            /*同理设置右三角形的路径数据*/
            pathRight.moveTo(width, 0);
            pathRight.lineTo(width / 2, height / 2);
            pathRight.lineTo(width, height);
            pathRight.close();
    
            /*同理设置下三角形的路径数据*/
            pathDown.moveTo(width, height);
            pathDown.lineTo(width / 2, height / 2);
            pathDown.lineTo(0, height);
            pathDown.close();
        }
    
        //初始化与箭头相关的数据。
        private void initArrow() {
            /*
            这里我们规定:
            1.每个箭头由一个三角形和一个矩形组成;
            2.每个箭头整体的宽和高分别为画布宽和高的1/4;
            3.每个箭头的三角形部分和矩形部分在宽(左右箭头)或高(上下箭头)上各占一半。
            4.每个三角形矩形部分的高(左右箭头)或宽(上下箭头)为箭头整体高或宽的一半。
            */
    
            //每个箭头整体的宽和高。
            final int arrowWidth = width / 4;
            final int arrowHeight = height / 4;
    
            /*
            设计左箭头
            先设计三角形部分,再设计矩形部分。
            */
            //设置箭头尖的坐标。这里我们规定让左键头尖的横坐标为画布宽的1/16;纵坐标为画布高度的1/2,也就是垂直方向居中。
            int arrowStartX = width / 16;
            int arrowStartY = height / 2;
            //设计箭头的三角形部分
            int arrowX = arrowStartX;
            int arrowY = arrowStartY;
            //从箭头尖开始
            pathLeftArrow.moveTo(arrowX, arrowY);
            //直线移动到三角形的上顶点
            pathLeftArrow.lineTo(arrowX += arrowWidth / 2, arrowY -= arrowHeight / 2);
            //然后直线移动到三角形的下顶点
            pathLeftArrow.lineTo(arrowX, arrowY += arrowHeight);
            //闭合三角形
            pathLeftArrow.close();
    
            //重置坐标,准备设计矩形部分。在计算机中画矩形是最容易的,只要知道左上角和右下角两点的坐标即可。
            arrowX = arrowStartX;
            arrowY = arrowStartY;
            //矩形左边界到画布左边界的距离(左上角横坐标)
            float left = arrowX += arrowWidth / 2;
            //矩形上边界到画布上边界的距离(左上角纵坐标)
            float top = arrowY -= arrowHeight / 4;
            //矩形右边界到画布左边界的距离(右下角横坐标)
            float right = arrowX += arrowWidth / 2;
            //矩形下边界到画布上边界的距离(右下角纵坐标)
            float bottom = arrowY += arrowHeight / 2;
            //在已有三角形的基础上增加一个矩形。最后一个参数是一个枚举,只有两个值,Direction.CW表示顺时针,Direction.CCW表示逆时针。在我们这里选那个都行,没有影响。
            pathLeftArrow.addRect(left, top, right, bottom, Path.Direction.CW);
    
            /*
            设计右箭头
            由于右箭头与左箭头是中心对称图形,只要把左箭头旋转180度即可。这里我们使用矩阵来做旋转。
             */
            Matrix matrix = new Matrix();
            matrix.setRotate(180, width / 2, height / 2);
            pathLeftArrow.transform(matrix, pathRightArrow);
    
            /*
            设计上箭头
            和左箭头一样,先设计三角形部分,再设计矩形部分。
             */
            //设置箭头尖的坐标。这里我们规定让上键头尖的横坐标为画布宽的1/2,也就是水平方向居中;纵坐标为画布高度的1/16。
            arrowStartX = width / 2;
            arrowStartY = height / 16;
            //设计三角形部分
            arrowX = arrowStartX;
            arrowY = arrowStartY;
            pathUpArrow.moveTo(arrowX, arrowY);
            pathUpArrow.lineTo(arrowX += arrowWidth / 2, arrowY += arrowHeight / 2);
            pathUpArrow.lineTo(arrowX - +arrowWidth, arrowY);
            pathUpArrow.close();
            //设计矩形部分
            arrowX = arrowStartX;
            arrowY = arrowStartY;
            pathUpArrow.addRect(arrowX -= arrowWidth / 4, arrowY += arrowHeight / 2, arrowX += arrowWidth / 2, arrowY += arrowHeight / 2, Path.Direction.CW);
    
            /*
            设计下箭头
            同理,将上箭头旋转180度得到下箭头。
             */
            pathUpArrow.transform(matrix, pathDownArrow);
        }
    
        //画路径的共用方法
        private void drawPath(Path path, int color, Canvas canvas) {
            //设置画笔颜色
            paint.setColor(color);
            //用画笔在画布上按照路径数据画出图形
            canvas.drawPath(path, paint);
        }
    
        //画左方向键正常状态
        private void drawLeftNormal(Canvas canvas) {
            drawPath(pathLeft, leftNormalColor, canvas);
            drawPath(pathLeftArrow, arrowNormalColor, canvas);
        }
    
        //画左方向键按压状态(高亮)
        private void drawLeftPressed(Canvas canvas) {
            drawPath(pathLeft, leftPressedColor, canvas);
            drawPath(pathLeftArrow, arrowPressedColor, canvas);
        }
    
        //画上方向键正常状态
        private void drawUpNormal(Canvas canvas) {
            drawPath(pathUp, upNormalColor, canvas);
            drawPath(pathUpArrow, arrowNormalColor, canvas);
        }
    
        //画上方向键按压状态(高亮)
        private void drawUpPressed(Canvas canvas) {
            drawPath(pathUp, upPressedColor, canvas);
            drawPath(pathUpArrow, arrowPressedColor, canvas);
        }
    
        //画右方向键正常状态
        private void drawRightNormal(Canvas canvas) {
            drawPath(pathRight, rightNormalColor, canvas);
            drawPath(pathRightArrow, arrowNormalColor, canvas);
        }
    
        //画右方向键按压状态(高亮)
        private void drawRightPressed(Canvas canvas) {
            drawPath(pathRight, rightPressedColor, canvas);
            drawPath(pathRightArrow, arrowPressedColor, canvas);
        }
    
        //画下方向键正常状态
        private void drawDownNormal(Canvas canvas) {
            drawPath(pathDown, downNormalColor, canvas);
            drawPath(pathDownArrow, arrowNormalColor, canvas);
        }
    
        //画下方向键按压状态(高亮)
        private void drawDownPressed(Canvas canvas) {
            drawPath(pathDown, downPressedColor, canvas);
            drawPath(pathDownArrow, arrowPressedColor, canvas);
        }
    
        //所有方向键恢复正常状态
        private void reset(Canvas canvas) {
            drawLeftNormal(canvas);
            drawUpNormal(canvas);
            drawRightNormal(canvas);
            drawDownNormal(canvas);
        }
    
        //当按左方向键时,左方向键高亮,其它正常。
        private void drawWhenLeftPressed(Canvas canvas) {
            drawLeftPressed(canvas);
            drawUpNormal(canvas);
            drawRightNormal(canvas);
            drawDownNormal(canvas);
        }
    
        //当按上方向键时,上方向键高亮,其它正常。
        private void drawWhenUpPressed(Canvas canvas) {
            drawLeftNormal(canvas);
            drawUpPressed(canvas);
            drawRightNormal(canvas);
            drawDownNormal(canvas);
        }
    
        //当按右方向键时,右方向键高亮,其它正常。
        private void drawWhenRightPressed(Canvas canvas) {
            drawLeftNormal(canvas);
            drawUpNormal(canvas);
            drawRightPressed(canvas);
            drawDownNormal(canvas);
        }
    
        //当按下方向键时,下方向键高亮,其它正常。
        private void drawWhenDownPressed(Canvas canvas) {
            drawLeftNormal(canvas);
            drawUpNormal(canvas);
            drawRightNormal(canvas);
            drawDownPressed(canvas);
        }
    
        /**
         * 通过重写父类的onDraw方法来绘制我们需要的图形
         * 该方法会在控件第一次显示时被系统调用,并在之后每次调用invalidate方法后被系统调用。
         *
         * @param canvas 这里的canvas是系统提供的一块矩形画布,我们要做的就是在这块画布上画我们想要的东西。
         */
        @Override
        protected void onDraw(Canvas canvas) {
            if (!initDone) {
                init(canvas);
                //确保初始化方法只执行一次
                initDone = true;
            }
    
            //按下不同的方向键,绘制不同的界面效果。
            switch (currentDirection) {
                case left://按左键时
                    drawWhenLeftPressed(canvas);
                    break;
                case up://按上键时
                    drawWhenUpPressed(canvas);
                    break;
                case right://按右键时
                    drawWhenRightPressed(canvas);
                    break;
                case down://按下键时
                    drawWhenDownPressed(canvas);
                    break;
                default://其它情况
                    reset(canvas);
            }
        }
    
        /**
         * 当用户触摸到该控件时,系统通过该方法告诉控件“你被摸了,要不要有反应啊?有反应返回true,没反应返回false。”
         * 控件说“那要看是怎么摸的了。如果是按下,我就将对应的方向键高亮显示;如果是抬起,我就将所有的方向键恢复成正常的颜色。其它情况我就不反应了,摸就摸吧。”
         *
         * @param event 系统给我们传递的触摸事件参数
         * @return 如果该触摸事件被我们处理了返回true,反之返回false。
         */
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            //获取当前的触摸动作
            int action = event.getAction();
            if (action == MotionEvent.ACTION_DOWN) {//按下
                //获取触摸点的坐标
                float x = event.getX();
                float y = event.getY();
                currentDirection = getDirection(x, y);
                invalidate();//重绘
                return true;
            } else if (action == MotionEvent.ACTION_UP) {//抬起
                currentDirection = Direction.none;
                invalidate();//重绘
                return true;
            } else {//其它不处理
                return false;
            }
        }
    
        //根据坐标判断哪个三角形方向键被按下
        private Direction getDirection(float x, float y) {
            //经过坐标转换,统一成边长为1的正方形处理。对角线分割形成的4个区域,分别代表4个方向。
            float relativeX = x / width;//0<=relativeX<=1
            float relativeY = y / height;//0<=relativeY<=1
            /*
            注意:原点是左上角。
            左上角到右下角对角线方程为y=x;
                则:
                y>x的区域包含左和下三角形
                y<x的区域包含右和上三角形
    
            左下角到右上角对角线方程为y=-x+1;
                则:
                y>1-x的区域包含右和下三角形
                y<1-x的区域包含左和上三角形
    
            综上可得:
                y>x 且 y<1-x 表示左三角
                y<x 且 y<1-x 表示上三角
                y<x 且 y>1-x 表示右三角
                y>x 且 y>1-x 表示下三角
             */
    
            if (relativeY > relativeX) {//左和下
                if (relativeY < 1 - relativeX) {//
                    return Direction.left;
                } else {//
                    return Direction.down;
                }
            } else {//上和右
                if (relativeY < 1 - relativeX) {//
                    return Direction.up;
                } else {//
                    return Direction.right;
                }
            }
        }
    }

    相比上节的代码,这里主要是增加了initArrow方法,另外把背景三角形相关的初始化代码放到了initBackgroundTriangle方法里,这样看起来会更有条理些。

    好了本节先到这里,我们下节继续。:)

  • 相关阅读:
    Reflector7.5.2.1(最新免费破解版)
    linux shell中变量的特殊处理
    linux中什么是shell?
    关于sql server中主键的一点研究
    根据数据库连接,登录操作系统的一个方法
    无图片取得圆角效果
    结对编程作业(java实现)
    第三章、android入门
    第七章:android应用
    javascript编辑excel并下载
  • 原文地址:https://www.cnblogs.com/chengyujia/p/5812592.html
Copyright © 2020-2023  润新知