????????除了使用已有的圖片之外,Android應用還常常需要在運行時動態地生成圖片,比如一個手機游戲,游戲界面看上去豐富多彩,而且可以隨著用戶動作而動態改變,這就需要借助于Android的繪圖支持了。
1. Android繪圖基礎:Canvas、Paint等
????????Android的繪圖應該繼承View組件,并重寫它的onDraw (Canvas canvas)方法即可。
????????重寫onDraw (Canvas canvas)方法時涉及一個繪圖APl:Canvas,Canvas代表“依附"于指定View的畫布,它提供了如表所示的方法來繪制各種圖形。
方法簽名 | 簡要說明 |
---|---|
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) | 繪制弧 |
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) | 在指定點繪制從源位圖中“挖取”的一塊 |
drawBitmap(Bitmap bitmap, float left, float top, Paint paint) | 在指定點繪制位圖 |
drawCircle(float cx, float cy, float radius, Paint paint) | 在指定點繪制一個圓 |
drawLine(float startX, float startY, float stopX, float stopY, Paint paint) | 繪制一條直線 |
drawLines(float[pts,int offset, int count,Paint paint) | 繪制多條直線 |
drawOval(RectF oval, Paint paint) | 繪制橢圓 |
drawPath(Path path,Paint paint) | 沿著指定Path 繪制任意形狀 |
drawPoint(float x, float y, Paint paint) | 繪制一個點 |
drawPoints(float[] pts, int offset, int count,Paint paint) | 繪制多個點 |
drawRect(float left, float top, float right, float bottom, Paint paint) | 繪制矩形 |
drawRoundRect(RectF rect, float rx, float ry, Paint paint) | 繪制圓角矩形 |
draw Text(String text, int start, int end,Paint paint) | 繪制字符串 |
drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) | 沿著路徑繪制字符串 |
clipRect(float left, float top, float right, float bottom) | 剪切一個矩形區域 |
clipRegion(Region region) | 剪切指定區域 |
Canvas還提供了如下方法進行坐標變換:
- rotate (float degrees, float px,float py) :對Canvas執行旋轉變換。
- scale (float sx,float sy,float px,float py) :對Canvas執行縮放變換。
- skew (float sx, float sy) :對Canvas執行傾斜變換。
- translate (float dx,float dy):移動Canvas。向右移動dx距離(dx為負數即向左移動);向下移動dy距離(dy為負數即向上移動)。
????????Canvas提供的方法還涉及一個API: Paint,Paint代表Canvas上的畫筆,因此Paint類主要用于設置繪制風格,包括畫筆顏色、畫筆筆觸粗細、填充風格等。Paint提供了如表所示的方法。
方法簽名 | 簡要說明 |
---|---|
setARGB(int a, int r, int g, int b)/setColor(int color) | 設置顏色 |
setAlpha(int a) | 設置透明度 |
setAntiAlias(boolean aa) | 設置是否抗鋸齒 |
setColor(int color) | 設置顏色 |
setPathEffect(PathEffect effect) | 設置繪制路徑時的路徑效果 |
setShader(Shader shader) | 設置畫筆的填充效果 |
setShadowLayer(float radius, float dx, float dy, int color) | 設置陰影 |
setStrokeWidth(float width) | 設置畫筆的筆觸寬度 |
setStrokeJoin(Paint.Join join) | 設置畫筆轉彎處的連接風格 |
setStyle(Paint.Style style) | 設置 Paint的填充風格 |
setTextAlign(Paint.Align align) | 設置繪制文本時的文字對齊方式 |
setTextSize(float textSize) | 設置繪制文本時的文字大小 |
????????在Canvas提供的繪制方法中還用到了一個API: Path,Path代表任意多條直線連接而成的任意圖形,當Canvas根據Path繪制時,它可以繪制出任意的形狀。
1.1 例子
public class MyView extends View {private Path path1 = new Path();private Path path2 = new Path();private Path path3 = new Path();private Path path4 = new Path();private Path path5 = new Path();private Path path6 = new Path();public MyView(Context context, AttributeSet set){super(context,set);}private LinearGradient mShader = new LinearGradient(0f,0f,40f,60f,new int[]{Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW},null, Shader.TileMode.REPEAT);private RectF rect = new RectF();//定義畫筆private Paint paint = new Paint();//重寫方法,進行繪圖@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//把整張畫布繪制成白色canvas.drawColor(Color.WHITE);//去鋸齒paint.setAntiAlias(true);paint.setColor(Color.BLUE);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(4f);int viewWidth = this.getWidth();//繪制圓形canvas.drawCircle(viewWidth/10 + 10,viewWidth/10 + 10, viewWidth / 10, paint);//繪制正方形canvas.drawRect (10 , viewWidth / 5 + 20 , viewWidth / 5 + 10, viewWidth *2/ 5 + 20 , paint) ;//繪制矩形canvas.drawRect (10,viewWidth * 2/ 5 + 30,viewWidth / 5 + 10, viewWidth / 2 + 30, paint);@SuppressLint("DrawAllocation")RectF re1 = new RectF (10,viewWidth / 2 +40, 10 + viewWidth / 5 , viewWidth * 3 / 5 +40) ;//繪制圓角矩形canvas.drawRoundRect (re1, 15,15, paint) ;@SuppressLint("DrawAllocation")RectF re11 = new RectF(10,viewWidth * 3 / 5 + 50,10 + viewWidth / 5 ,viewWidth * 7 / 10 + 50);//繪制橢圓canvas.drawOval(re11, paint) ;//定義一個Path對象,封閉成一個三角形path1.moveTo (10,viewWidth * 9 / 10 + 60);path1.lineTo(viewWidth / 5 + 10,viewWidth * 9 /10 + 60);path1.lineTo (viewWidth / 10 + 10,viewWidth * 7 /10 + 60 );path1.close () ;//根據Path進行繪制,繪制三角形canvas.drawPath (path1, paint);//定義一個 Path對象,封閉成一個五角形path2.moveTo(10 + viewWidth / 15,viewWidth * 9 / 10 + 70);path2.lineTo(10 + viewWidth * 2/ 15,viewWidth * 9 / 10 +70);path2.lineTo(10 + viewWidth / 5, viewWidth + 70);path2.lineTo(10 + viewWidth / 10,viewWidth * 11/10 +70);path2.lineTo (10 , viewWidth + 70);path2.close();//根據Path進行繪制,繪制五角形canvas .drawPath (path2 , paint) ;//----------設置填充風格后繪制-—--------paint.setStyle(Paint.Style.FILL);paint.setColor(Color.RED);//繪制圓形canvas.drawCircle(viewWidth * 3 / 10 + 20,viewWidth / 10 + 10, viewWidth / 10, paint) ;//繪制正方形canvas.drawRect (viewWidth / 5 +20 , viewWidth / 5 +20, viewWidth * 2/ 5 + 20 , viewWidth * 2/ 5 + 20 , paint) ;//繪制矩形canvas.drawRect (viewWidth / 5 + 20,viewWidth * 2/ 5 + 30, viewWidth * 2 / 5 + 20 , viewWidth / 2 + 30,paint);@SuppressLint("DrawAllocation") RectF re2 = new RectF (viewWidth / 5 + 20,viewWidth / 2 + 40,20 + viewWidth * 2 / 5 ,viewWidth * 3 / 5 + 40);//繪制圓角矩形canvas.drawRoundRect (re2,15,15, paint) ;@SuppressLint("DrawAllocation") RectF re21 = new RectF(20 + viewWidth / 5, viewWidth * 3 / 5 + 50,20 + viewWidth * 2/ 5 ,viewWidth * 7 / 10 + 50);//繪制橢圓canvas.drawOval(re21, paint) ;//定義一個Path對象,封閉成一個三角形path3.moveTo(20 + viewWidth / 5,viewWidth * 9 / 10 + 60);path3.lineTo(viewWidth * 2/ 5 + 20,viewWidth * 9 / 10 + 60);path3.lineTo(viewWidth * 3 / 10 + 20,viewWidth * 7 / 10 + 60);path3.close ( ) ;//根據Path進行繪制,繪制三角形canvas.drawPath (path3, paint) ;//定義一個Path對象,封閉成一個五角形path4.moveTo(20 + viewWidth *4 / 15,viewWidth * 9 / 10 + 70);path4.lineTo(20 + viewWidth / 3, viewWidth * 9 / 10 +70);path4.lineTo(20 + viewWidth * 2/ 5, viewWidth + 70);path4.lineTo(20 + viewWidth * 3 / 10,viewWidth * 11/10 + 70);path4.lineTo(20 + viewWidth / 5 , viewWidth + 70);path4.close ( ) ;//根據Path進行繪制,繪制五角形canvas.drawPath (path4, paint) ;//----------設置漸變器后繪制-—------// 為Paint設置漸變器@SuppressLint("DrawAllocation")Shader mShader = new LinearGradient(0,0,40,60,new int[] {Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW }, null , Shader.TileMode.REPEAT);paint.setShader(mShader) ;//設置陰影paint.setShadowLayer (25 , 20 , 20 , Color.GRAY);//繪制圓形canvas.drawCircle (viewWidth / 2 + 30, viewWidth / 10 + 10, viewWidth / 10,paint) ;//繪制正方形canvas.drawRect (viewWidth * 2 / 5 + 30 , viewWidth / 5 + 20, viewWidth * 3 / 5 + 30 , viewWidth * 2/ 5 + 20 , paint);//繪制矩形canvas.drawRect (viewWidth * 2 / 5+ 30,viewWidth * 2/ 5 + 30, viewWidth * 3 / 5 + 30 , viewWidth / 2 + 30,paint);@SuppressLint("DrawAllocation")RectF re3 = new RectF(viewWidth * 2 / 5 + 30,viewWidth / 2 + 40,30 + viewWidth * 3 / 5 , viewWidth * 3 / 5 + 40);//繪制圓角矩形canvas.drawRoundRect (re3, 15,15, paint) ;@SuppressLint("DrawAllocation")RectF re31 = new RectF(30 + viewWidth *2/ 5,viewWidth * 3 / 5 + 50,30 + viewWidth * 3 / 5 , viewWidth * 7 / 10 + 50 ) ;//繪制彬圓canvas.drawOval (re31, paint) ;//定義一個Path對象,封閉成一個三角形path5.moveTo(30 + viewWidth * 2/ 5,viewWidth * 9 / 10 + 60 ) ;path5.lineTo(viewWidth * 3 / 5 + 30,viewWidth * 9 / 10 + 60);path5.lineTo (viewWidth / 2 + 30,viewWidth * 7 / 10 + 60);path5.close();//根據Path進行繪制,繪制三角形canvas .drawPath (path5, paint) ;//定義一個Path對象,封閉成一個五角形path6.moveTo (30 + viewWidth * 7 / 15,viewWidth * 9 / 10 + 70);path6.lineTo(30 + viewWidth * 8 / 15,viewWidth * 9 / 10 + 70);path6.lineTo (30 + viewWidth* 3/ 5,viewWidth + 70);path6.lineTo (30 + viewWidth / 2,viewWidth * 11/10 + 70);path6.lineTo (30 + viewWidth * 2/ 5 , viewWidth + 70 );path6.close ( ) ;//根據Path進行繪制,繪制五角形canvas.drawPath (path6, paint);//----------設置字符大小后繪制paint.setTextSize(48);paint.setShader(null);//繪制7個字符串canvas.drawText(getResources().getString(R.string.circle),60 + viewWidth * 3 / 5, viewWidth / 10 + 10,paint);canvas.drawText(getResources ( ).getString(R.string.square),60 + viewWidth * 3 / 5,viewWidth * 3 / 10 + 20,paint);canvas.drawText (getResources ( ).getString (R.string.rect),60 + viewWidth * 3 / 5, viewWidth * 1 / 2 + 20,paint);canvas.drawText(getResources ( ).getString (R.string.round_rect),60 + viewWidth * 3 / 5,viewWidth * 3 / 5 + 30,paint) ;canvas.drawText (getResources ().getString (R.string.oval),60 + viewWidth * 3 / 5,viewWidth * 7 / 10 + 30,paint);canvas.drawText (getResources ( ).getString (R.string.triangle),60 + viewWidth * 3 / 5, viewWidth * 9 / 10 + 30,paint);canvas.drawText (getResources ( ).getString (R.string.pentagon),60 + viewWidth * 3 / 5,viewWidth * 11 / 10 + 30,paint);}}
????????Android的Canvas不僅可以繪制這種簡單的幾何圖形,還可以直接將一個Bitmap繪制到畫布上,這樣就給了開發者巨大的靈活性,只要前期美工把應用程序所需的圖片制作出來,后期開發時把這些圖片繪制到Canvas上即可。
1.2 額外知識點
(1).?LinearGradient類
????????此類實現線性漸變效果,目前只在實現卡拉ok字幕上使用過,就是讓歌詞隨著歌聲逐漸變色的效果。
LinearGradient lg = new LinearGradient(0,0,100,100, new int[]{Color.RED,Color.GREEN,Color.BLUE,Color.WHITE}, null, Shader.TileMode.REPEAT);
參數說明:?
第一個?起始的x坐標?
第二個?起始的y坐標??
第三個?結束的x坐標???
第四個?結束的y坐標???
?——以上4個坐標參數除了設置漸變區域,還決定漸變的方向?
第五個?顏色數組,如{#000000,#ffffff}??
第六個?這個也是一個數組,如{0.5f,0.51f},?
用來指定顏色數組的相對位置,?
取值從0.0f到1.0f按第五個參數的顏色組切分漸變區域??
如果為null?就沿坡度線均勻分布???
——以上2個參數配對出現,第六個如果不設置null,則需對應第五個參數的數組元素個數?
第七個?渲染模式
(2).RectF類
????????Rect和RextF都是用來創建一個矩形的,Rect的參數是int型,RectF的參數是float型,由此可以看出RectF比Rect的精確度更高。他們都是通過四個坐標參數來確定矩形的區域。
- RectF.left 矩形左上角的x坐標。
- RectF.top 矩形左上角的y坐標。
- RectF.right 矩形右下角的y坐標。
- RectF.right 矩形右下角的y坐標。
2. Path類
????????調用Canvas的drawPath (path,paint)方法可沿著路徑繪制圖形。Android還為路徑繪制提供了PathEffect來定義繪制效果,PathEffect包含了如下子類(每個子類代表一種繪制效果)。
- ComposePathEffect
- CornerPathEffect
- DashPathEffect
- DiscretePathEffect
- PathDashPathEffect
- sumPathEffect
????????這些繪制效果使用語言來表述總顯得有點空洞,下面通過一個程序來讓讀者理解這些繪制效果。該程序繪制7條路徑,分別示范了不使用效果和使用上面6種效果的效果。
2.1 例子
public class Test7Activity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(new Test7View(this));}class Test7View extends View{private float phase;private PathEffect[] effects = new PathEffect[7];private int[] colors;private Paint paint = new Paint();//定義創建并初始化Pathprivate Path path = new Path();public Test7View(Context context){super(context);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(4f);path.moveTo(0f,0f);for (int i = 1; i <=40 ; i++) {//生成40個點,隨機生成他們的坐標,并將它們連接成一條Pathpath.lineTo(i*25f,(float)(Math.random()*90));}//初始化7種顏色colors = new int[]{Color.BLACK,Color.BLUE,Color.CYAN,Color.GREEN,Color.MAGENTA,Color.RED,Color.YELLOW};//--------下面開始初始化 7 條路徑的效果----------//不使用路徑效果effects[0]=null;// 使用CornerPathEffecteffects[1]= new CornerPathEffect(10f);// 初始化DiscretePathEffecteffects[2]= new DiscretePathEffect(3.0f,5.0f);}@SuppressLint("DrawAllocation")@Overrideprotected void onDraw(Canvas canvas) {//將背景填充成白色canvas.drawColor(Color.WHITE);//將畫布移到(8,8)處開始繪制canvas.translate(8f,8f);//依次使用7種不同的路徑效果,7種不同的顏色來繪制路徑for (int i = 0; i <effects.length ; i++) {paint.setPathEffect(effects[i]);paint.setColor(colors[i]);canvas.drawPath(path,paint);canvas.translate(0f,90f);}// 初始化DashPathEffecteffects[3] = new DashPathEffect(new float[]{20f,10f,5f,10f},phase);// 初始化PathDashPathEffectPath p = new Path();p.addRect(0f,0f,8f,8f,Path.Direction.CCW);effects[4] = new PathDashPathEffect(p,12f,phase,PathDashPathEffect.Style.ROTATE);// 初始化ComposePathEffecteffects[5] = new ComposePathEffect(effects[2],effects[4]);// 初始化sumPathEffecteffects[6] = new SumPathEffect(effects[4],effects[3]);//改變phase值 ,形成動畫效果phase +=1f;invalidate();}}
}
????????正如上面的程序中所看到的,當定義DashPathEffect、PathDashPathEffect時可指定一個phase參數,該參數用于指定路徑效果的相位,當該phase參數改變時,繪制效果也略有變化。上面的程序不停地改變phase參數,并不停地重繪該View組件,這將產生動畫效果。
2.2 六條路徑效果詳解
- CornerPathEffect:使路徑變得圓潤
通過將線段之間的任何銳角替換為指定半徑的圓角,轉換繪制的幾何圖形(描邊或填充式)。 ?? ? ? ??
參數:radius ?相當于線段之間的圓角。
- DiscretePathEffect:類似毛刺一樣的效果
官方的解釋: ? ? ? ? ? ?
DiscretePathEffect :切斷線段
segmentLength:是指定切斷的長度
deviation:為切斷之后線段的偏移量,隨機的,小于等于deviation。
- DashPathEffect:實線與虛線之間交替
第一個,float intervals[],它是一個數組,這里用來存放,顯示的實線與虛線的長度。? ? ? ? ? ? 其中20和5屬于實線 ;兩個10屬于虛線 ? ? ? ? ? ?
第二個,phase,它是一個偏移的數值,就是左右偏移量,正數向左,負數向右。
- PathDashPathEffect
// 通過用指定的形狀沖壓繪制的路徑來劃線。這僅適用于繪畫樣式為“描邊”或“描邊和填充”時的繪圖。 ? ? ? ? ? ?
// 如果繪畫的樣式是填充,那么這個效果會被忽略。繪畫的筆劃寬度不會影響結果。 ? ? ? ? ? ?
//shape?? ? ? ?Path:要踩踏的路徑 ? ? ? ? ? ?
//advance?? ?float:形狀的每個印章之間的間距 ? ? ? ? ? ?
//phase?? ? ? ?float:沖壓第一個形狀前的偏移量? ? ?
//style? ? ? ? ? PathDashPathEffect.Style:如何在沖壓時變換每個位置的形狀 ? ? ? ? ? ?
//? ? ? ? ? ? ? ? ? MORPH ?ROTATE ?TRANSLATE ?變形 旋轉 平移
- ComposePathEffect
構建一個PathEffect,其效果是首先應用內部效果和外部pathEffect(例如outer(inner(path))。
// 第一個參數:outerpe 第二個參數:innerpe
- sumPathEffect
構建一個PathEffect,其效果是依次應用兩個效果。(例如第一個(路徑)+第二個(路徑))
????????Canvas還提供了一個drawTextOnPath (String text,Pathpath,float hOffset,float vOffset,Paint paint)方法,該方法可以沿著Path繪制文本。其中hOffset參數指定水平偏移,vOffset參數指定垂直偏移。