本系列教程概述與目錄:http://www.cnblogs.com/chengyujia/p/5787111.html本系列教程項目源碼GitHub地址:https://github.com/jackchengyujia/HappySnake 一、本文概述 在上篇教程中,我們畫了4個背景三角形,並且 ...
本系列教程概述與目錄: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方法里,這樣看起來會更有條理些。
好了本節先到這裡,我們下節繼續。:)