Android自定義View【實戰教程】5⃣️---Canvas詳解及程式碼繪製安卓機器人
友情連結:
神馬是Canvas
基本概念
Canvas:可以理解為是一個為我們提供了各種工具的畫布,我們可以在上面盡情的繪製(旋轉,平移,縮放等等)。可以理解為系統分配給我們一個一個記憶體空間,然後提供了一些對這個記憶體空間操作的方法(API), 實際儲存是在下面的bitmap。
兩種畫布
這裡canvas可以繪製兩種型別的畫圖,分別是view和surfaceView。
View:是普通畫圖,適合處理量比較小,幀率比較小的動畫,比如說象棋遊戲之類的。
SurfaceView:主要用在遊戲,高品質動畫方面的畫圖。
區別:在SurfaceView中定義一個專門的執行緒來完成畫圖工作,應用程式不需要等待View的刷圖,提高效能。
Canvas座標系與繪圖座標系
Canvas繪圖中牽扯到兩種座標系:Canvas座標系與繪圖座標系。
Canvas座標系
Canvas座標系指的是Canvas本身的座標系,Canvas座標系有且只有一個,且是唯一不變的,其座標原點在View的左上角,從座標原點向右為x軸的正半軸,從座標原點向下為y軸的正半軸。繪圖座標系
Canvas的drawXXX方法中傳入的各種座標指的都是繪圖座標系中的座標,而非Canvas座標系中的座標。預設情況下,繪圖座標系與Canvas座標系完全重合,即初始狀況下,繪圖座標系的座標原點也在View的左上角,從原點向右為x軸正半軸,從原點向下為y軸正半軸。但不同於Canvas座標系,繪圖座標系並不是一成不變的,可以通過呼叫Canvas的translate方法平移座標系,可以通過Canvas的rotate方法旋轉座標系,還可以通過Canvas的scale方法縮放座標系,而且需要注意的是,translate、rotate、scale的操作都是基於當前繪圖座標系的,而不是基於Canvas座標系,一旦通過以上方法對座標系進行了操作之後,當前繪圖座標系就變化了,以後繪圖都是基於更新的繪圖座標系了。也就是說,真正對我們繪圖有用的是繪圖座標系而非Canvas座標系。
我們看下面程式碼就可以明白:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//未平移 在原點
canvas.drawLine(0, 0, width, 0, mPaint);//繪製x軸
canvas.drawLine(0, 0, 0, height, mPaint);//繪製y軸
//第一次移動
canvas.translate(200,200);
canvas.drawLine(0, 0, width, 0 , mPaint);//繪製x軸
canvas.drawLine(0, 0, 0, height, mPaint);//繪製y軸
canvas.restore();
//第二次移動並旋轉
canvas.translate(200,200);
canvas.rotate(30);
canvas.drawLine(0, 0, width, 0, mPaint);//繪製x軸
canvas.drawLine(0, 0, 0, height, mPaint);//繪製y軸
}
每次繪製同樣的(startX, startY,stopX,stopY, paint)線,
但是我們發現平移或者旋轉之後畫出的線座標發生了變化
那麼有童鞋問了,如果我不想讓座標發生變化,或者再回去原點怎麼搞?
別擔心,只需要執行canvas.restore(),下面詳細講解。
Canvas儲存和還原
- canvas.save()
儲存當前座標- canvas.restore()
回覆上一次座標,如果有儲存,回到最後一次儲存的座標,如果沒儲存,則會報錯java.lang.IllegalStateException: Underflow in restore - more restores than saves
,要先存再取。- restoreToCount(int saveCount)
回到第幾次的儲存座標狀態
對Canvas的操作 — 平移,旋轉,縮放
Canvas平移
/**
* 畫布向(dx,dy)方向平移
*
* 引數1: 向X軸方向移動dx距離
* 引數2: 向Y軸方向移動dy距離
*/
canvas.translate(float dx, float dy);
Canvas縮放
/**
* 在X軸方向放大為原來sx倍,Y軸方向方大為原來的sy倍
* 預設原點為左上角
* 引數1: X軸的放大倍數
* 引數2: Y軸的放大倍數
*/
canvas.scale(float sx, float sy);
/**
* 在X軸方向放大為原來sx倍,Y軸方向方大為原來的sy倍
* 引數1: X軸的放大倍數
* 引數2: Y軸的放大倍數
* 引數3: 原點X座標
* 引數4: 原點Y座標
*/
canvas.scale(float sx, float sy, float px, float py);
Canvas旋轉
/**
* 原點為中心,旋轉degrees度(順時針方向為正方向 )
* 引數: 旋轉角度
*/
canvas.rotate(float degrees);
/**
* 以(px,py)為中心,旋轉30度,順時針方向為正方向
* 引數1: 旋轉角度
* 引數2: 原點X座標
* 引數3: 原點Y座標
*/
canvas.rotate(float degrees, float px, float py);
繪製
畫文字
/**
* 引數1:輸入的內容
* 引數2:文字x軸的位置
* 引數3:文字Y軸的位置
* 引數4:畫筆物件
*/
drawText(String text, float x, float y, Paint paint)
/**
* 引數1:輸入的內容
* 引數2:要從第幾個字開始繪製
* 引數3:要繪製到第幾個文字
* 引數4:文字x軸的位置
* 引數5:文字Y軸的位置
* 引數6:畫筆物件
*/
drawText(String text, int start, int end, float x, float y,Paint paint)
樣例:
canvas.drawText("開始寫字啦!", 200,200,mPaint);
canvas.drawText("開始寫字啦!",2,3, 200,400,mPaint);
畫圓
/**
* 引數1:圓心X
* 引數2:圓心Y
* 引數3:半徑R
* 引數4:畫筆物件
*/
drawCircle(float cx, float cy, float radius, Paint paint)
樣例:
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(300,300,80,mPaint);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(300,500,80,mPaint);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(300,700,80,mPaint);
畫線
/*
* 引數1:startX
* 引數2:startY
* 引數3:stopX
* 引數4:stopY
* 引數5:畫筆物件
*/
canvas.drawLine(float startX, float startY, float stopX, float stopY,Paint paint);
/*
* 同時繪製多條線。
* 引數1:float陣列:每四個一組為一條線。
* 引數2:畫筆物件
*/
canvas.drawLines(@Size(multiple=4)float[] pts, Paint paint);
樣例:
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawLine(50,50,200,50,mPaint);
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawLines(new float[]{200,200,300,200,300,300,300,400},mPaint);
畫橢圓
/**
* 引數1: 矩形
* 引數2: 畫筆
* /
canvas.drawOval(RectF oval, Paint paint);
/**
* 引數1:float left
* 引數2:float top
* 引數3:float right
* 引數4:float bottom
* 引數5:畫筆
*/
canvas.drawOval(float left, float top, float right, float bottom, @NonNull Paint paint);
樣例:
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawOval(new RectF(50,50,400,400),mPaint);
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawOval(50,500,700,700,mPaint);
}
畫弧度
/**
* 引數1:RectF物件。
* 引數2:開始的角度。(水平向右為0度順時針反向為正方向)
* 引數3:掃過的角度
* 引數4:是否和中心連線
* 引數5:畫筆物件
*/
canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,Paint paint);
/**
* 引數1:float left
* 引數2:float top
* 引數3:float right
* 引數4:float bottom
* 引數5:開始的角度。(水平向右為0度順時針反向為正方向)
* 引數6:掃過的角度
* 引數7:是否和中心連線
* 引數8:畫筆物件
*/
canvas.drawArc(float left, float top, float right, float bottom,float startAngle, float sweepAngle, boolean useCenter,Paint paint);
樣例:
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawArc(new RectF(50,50,400,400),45,135,true,mPaint);
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc(50,500,700,700,45,135,false,mPaint);
}
矩形
/**
* 矩形
* 引數1:float left
* 引數2:float top
* 引數3:float right
* 引數4:float bottom
* 引數5:畫筆
*/
canvas.drawRect(float left, float top, float right, float bottom,Paint paint);
/**
* 引數1:矩形
* 引數2:畫筆
*/
canvas.drawRectRect r,Paint paint);
樣例:
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawRect(new RectF(50,50,400,400),mPaint);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawRect(50,500,700,700,mPaint);
圓角矩形
/**
* 引數1:矩形
* 引數2:x半徑
* 引數3:y半徑
* 引數4: 畫筆
*/
drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)
/**
* 引數1:float left
* 引數2:float top
* 引數3:float right
* 引數4:float bottom
* 引數5:x半徑
* 引數6:y半徑
* 引數4: 畫筆
*/
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Paint paint)
樣例:
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawRoundRect(new RectF(50,50,400,400),20,20,mPaint);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawRoundRect(50,500,700,700,30,50,mPaint);
}
畫點
/**
* 引數1、2:點的x、y座標
*/
canvas.drawPoint(60, 390, p);//畫一個點
/**
* 引數1:多個點,每兩個值為一個點。最後個數不夠兩個的值,忽略。
*/
canvas.drawPoints(new float[]{60,400,65,400,70,400}, p);//畫多個點
樣例:
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawPoint(50,50,mPaint);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawPoints(new float[]{100,100,200,200,300, 300, 400,400,500,500,600,600},mPaint);
畫圖片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
/**
* 引數1:bitmap物件
* 引數2:影象左邊座標點
* 引數3:影象上邊座標點
*/
canvas.drawBitmap(Bitmap bitmap, float left, float top, Paint paint);
樣例:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
canvas.drawBitmap(bitmap, 200,300, mPaint);
到這裡基本屬性就講完了,接下來是一個練習。
程式碼繪製安卓小機器人
下面是程式碼 , 相當簡單,就是計算一下座標,就不詳細講了,有問題可以留言。
public class AndroidView extends View {
private float bodyWidth;
private float bodyHeigh;
private float armWidth;
private float armHeight;
private float legWidth;
private float legHeight;
private static final int INTERSPACE = 20;
private Paint mPaint;
private RectF bodyRect;
private RectF legRect;
private RectF armRect;
public AndroidView(Context context) {
this(context, null);
}
public AndroidView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AndroidView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(getResources().getColor(android.R.color.holo_green_dark));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setBodyParams();
setArmParams();
setLegParams();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
//畫身體
canvas.drawRoundRect(bodyRect, 20, 20, mPaint);
//畫頭
canvas.translate(0, -(bodyWidth / 2 + INTERSPACE));
canvas.drawArc(bodyRect, 0, -180, true, mPaint);
//畫左胳膊
canvas.drawRoundRect(armRect, 30, 30, mPaint);
//畫右胳膊
canvas.translate(bodyWidth + 5 * INTERSPACE, 0);
canvas.drawRoundRect(armRect, 30, 30, mPaint);
//畫左腿
canvas.translate(-(bodyWidth + 7 * INTERSPACE),bodyWidth*11/10);
canvas.drawRoundRect(legRect, 30, 30, mPaint);
//畫右腿
canvas.translate(2*INTERSPACE+legWidth,0);
canvas.drawRoundRect(legRect, 30, 30, mPaint);
//畫左眼
canvas.translate(0,-bodyHeigh-5*INTERSPACE);
mPaint.setColor(getResources().getColor(android.R.color.white));
canvas.drawCircle(getWidth()/2,getHeight()/2,INTERSPACE/2,mPaint);
//畫右眼
canvas.translate(-(2*INTERSPACE+legWidth),0);
mPaint.setColor(getResources().getColor(android.R.color.white));
canvas.drawCircle(getWidth()/2,getHeight()/2,INTERSPACE/2,mPaint);
canvas.restore();
mPaint.setTextSize(60);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawText("我是安卓小機器人",150,100,mPaint);
}
private void setBodyParams() {
bodyWidth = getWidth() * 2 / 5;
bodyHeigh = bodyWidth;
bodyRect = new RectF();
bodyRect.left = (getWidth() - bodyWidth) / 2;
bodyRect.top = (getHeight() - bodyHeigh) / 2;
bodyRect.right = bodyRect.left + bodyWidth;
bodyRect.bottom = bodyRect.top + bodyHeigh;
}
private void setLegParams() {
legWidth = getWidth() * 1 / 13;
legHeight = getHeight() * 1 / 7;
legRect = new RectF();
legRect.left = (getWidth() - legWidth) / 2;
legRect.top = (getHeight() - legHeight) / 2;
legRect.right = legRect.left + legWidth;
legRect.bottom = legRect.top + legHeight;
}
private void setArmParams() {
armWidth = getWidth() * 1 / 13;
armHeight = getHeight() * 1 / 6;
armRect = new RectF();
armRect.left = (getWidth() - bodyWidth) / 2 - INTERSPACE * 4;
armRect.top = getHeight() / 2 + INTERSPACE * 2;
armRect.right = armRect.left + armWidth;
armRect.bottom = armRect.top + armHeight;
}
}
掃碼關注公眾號“偉大程式猿的誕生“,更多幹貨等著你~
掃碼關注公眾號“偉大程式猿的誕生“,更多幹貨等著你~
掃碼關注公眾號“偉大程式猿的誕生“,更多幹貨等著你~
相關推薦
Android自定義View【實戰教程】5⃣️---Canvas詳解及程式碼繪製安卓機器人
友情連結: 神馬是Canvas 基本概念 Canvas:可以理解為是一個為我們提供了各種工具的畫布,我們可以在上面盡情的繪製(旋轉,平移,縮放等等)。可以理解為系統分配給我們一個一個記憶體空間,然後提供了一些對這個記憶體空間操作的方法(AP
Android 自定義View之Scroller處理滾動工具類詳解
public class ScrollerLayout extends ViewGroup { /** * 用於完成滾動操作的例項 */ private Scroller mScroller; /** * 判定為拖動的最小移動畫素數 */
【朝花夕拾】Android自定義View篇之(四)Canvas繪製文字教程
前言 前面的文章中在介紹Canvas的時候,提到過後續單獨講Canvas繪製文字,因為這一節內容比較細緻,內容很多。這裡先宣告一下,本文的內容的來源於騰訊課堂中“仍物線學堂”中課件,因為該課件對常用的繪製文字基本技巧做了比較詳細的講解
【朝花夕拾】Android自定義View篇之(二)Canvas常用技巧
前言 上一篇講View的繪製流程中講到過,最後一步是draw流程,在這個過程中,子view需要重寫onDraw方法來畫出自己的內容。在自定義View繪製自身內容的時候,系統提供了3個非常重要的類來幫助開發者畫各種炫酷的圖形:Canvas、Paint、Pa
Android 自定義view完全解析--帶你通透了解自定義view
Android LayoutInflater原理分析 相信接觸Android久一點的朋友對於LayoutInflater一定不會陌生,都會知道它主要是用於載入佈局的。而剛接觸Android的朋友可能對LayoutInflater不怎麼熟悉,因
自定義View之Paint(畫筆)的詳解
Android提供了2D圖形繪製的各種工具,如Canvas(畫布)、Point(點)、Paint(畫筆)、Rectangles(矩形)等,利用這些工具可以直接在介面上進行繪製。 在自定義View中,我們經常用到的Canvas(畫布)和Paint(畫筆),像我們
Android自定義控制元件熱身之scrollTo和scrollBy詳解
View通過ScrollTo和ScrollBy 方法可以實現滑動。那麼兩者有什麼區別呢?我們先來看一下原始碼 ScrollTo原始碼: public void scrollTo(int x,
【Android自定義View實戰】之自定義評價打分控制元件RatingBar,可以自定義星星大小和間距
在Android開發中,我們經常會用到對商家或者商品的評價,運用星星進行打分。然而在Android系統中自帶的打分控制元件,RatingBar特別不好用,間距和大小無法改變。所以,我就自定義了一個特別好用的打分控制元件。在專案中可以直接使用,特別簡
【Android自定義View實戰】之自定義超簡單SearchView搜尋框
package cn.bluemobi.dylan.searchview; import android.content.Context; import android.text.Editable; import android.text.TextWatcher; import android.util.A
【Android 自定義View】之PermuteView
1.前言 最近在專案迭代時,遇到新的UI需求,如下: 看到之後我分析了一下有那些實現方式: 1.使用第三款庫分別實現上下部分的UI功能。 2.讓UI做圖片,同background+press實現。 3.自定義View實現。 第 1
【Android自定義View】美觀個性的進度條
在很多開發中,例如網路請求中,是個比較耗時的操作,這時就需要一個進度條,不僅視覺上有很好的使用者體驗,操作上也讓使用者直觀的看到後臺操作的進度。所以進度條是必須會的。 效果如圖: 這樣的進度條比傳統的官方的美觀許多 下面介紹編寫過程 1.繼承VIew 編寫一個新的自定義
【Android自定義View】仿Photoshop取色器ColorPicker(三)
ColorPicker 一款基於HSV顏色空間的仿Photoshop取色器的Android版顏色拾取器。 前言 注: - 1 如果你對HSV顏色空間和RGB顏色空間不夠熟悉的話,請參看該系列的第一篇文章——仿Photoshop取色器Colo
【Android自定義View】仿Photoshop取色器ColorPicker(四)完結篇
ColorPicker 一款基於HSV顏色空間的仿Photoshop取色器的Android版顏色拾取器。 前言 注: - 1 如果你對HSV顏色空間和RGB顏色空間不夠熟悉的話,請參看該系列的第一篇文章——仿Photoshop取色器Colo
【朝花夕拾】Android自定義View篇之(四)自定義View的三種實現方式及自定義屬性詳解
前言 儘管Android系統提供了不少控制元件,但是有很多酷炫效果仍然是系統原生控制元件無法實現的。好在Android允許自定義控制元件,來彌補原生控制元件的不足。但是在很多初學者看來,自定義View似乎很難掌握。其中有很大一部分原因是我們平時看到的自定
【朝花夕拾】Android自定義View篇之(五)Android事件分發及傳遞機制
前言 在自定義View中,經常需要處理Android事件分發的問題,尤其在有多個輸入裝置(如遙控、滑鼠、遊戲手柄等)時,事件處理問題尤為突出。Android事件分發機制,一直以來都是一個讓眾多開發者困擾的難點,至少筆者在工作的前幾年中,沒有特意研究它之前
【朝花夕拾】Android自定義View篇之(五)Android事件分發機制(上)三個重要方法的處理邏輯
前言 在自定義View中,經常需要處理Android事件分發的問題,尤其在有多個輸入裝置(如遙控、滑鼠、遊戲手柄等)時,事件處理問題尤為突出。Android事件分發機制,一直以來都是一個讓眾多開發者困擾的難點,至少筆者在工作的前幾年中,沒有特意研究它之前
【朝花夕拾】Android自定義View篇之(六)Android事件分發機制(中)從原始碼分析事件分發邏輯及經常遇到的一些“詭異”現象
前言 轉載請註明,轉自【https://www.cnblogs.com/andy-songwei/p/11039252.html】謝謝! 在上一篇文章【【朝花夕拾】Android自定義View篇之(
【朝花夕拾】Android自定義View篇之(七)Android事件分發機制(下)解決滑動衝突
前言 前面兩篇文章,花了很大篇幅講解了Android的事件分發機制的原理性知識。然而,“紙上得來終覺淺,絕知此事要躬行”,前面講的那些原理,也都是為解決實際問題而服務的。本文將結合實際工作中經常遇到的滑動衝突案例,總結滑動衝突的場
【朝花夕拾】Android自定義View篇之(七)Android事件分發機制(下)滑動衝突解決方案總結
前言 轉載請宣告,轉自【https://www.cnblogs.com/andy-songwei/p/11072989.html】,謝謝! 前面兩篇文章,花了很大篇幅講解了Android的事件分發機制的
【朝花夕拾】Android自定義View篇之(八)多點觸控(上)MotionEvent簡介
前言 在前面的文章中,介紹了不少觸控相關的知識,但都是基於單點觸控的,即一次只用一根手指。但是在實際使用App中,常常是多根手指同時操作,這就需要用到多點觸控相關的知識了。多點觸控是在Android2.0開始引入的,在現在使用的Android手機上都是支