1. 程式人生 > >Android自定義View【實戰教程】5⃣️---Canvas詳解及程式碼繪製安卓機器人

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)


/**
  *  引數1float left 
  *  引數2float top 
  *  引數3float right 
  *  引數4float 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手機上都是支