Android查缺補漏(View篇)--自定義View利器Canvas和Paint詳解

来源:https://www.cnblogs.com/codingblock/archive/2018/01/13/8227598.html
-Advertisement-
Play Games

上篇文章介紹了自定義View的創建流程,從巨集觀上給出了一個自定義View的創建步驟,本篇是上一篇文章的延續,介紹了自定義View中兩個必不可少的工具Canvas和Paint,從細節上更進一步的講解自定義View的詳細繪製方法。如果把自定義View比作蓋一座房子,那麼上篇文章就相當於教會了我們怎麼一步 ...


上篇文章介紹了自定義View的創建流程,從巨集觀上給出了一個自定義View的創建步驟,本篇是上一篇文章的延續,介紹了自定義View中兩個必不可少的工具Canvas和Paint,從細節上更進一步的講解自定義View的詳細繪製方法。如果把自定義View比作蓋一座房子,那麼上篇文章就相當於教會了我們怎麼一步步的搭建房子的骨架,而本篇文章將要教會我們的是為房子的骨架添磚加瓦直至成型,甚至是怎麼裝修。

Canvas

為了後文更為方便的講解Canvas的常用方法的使用,我們先來做一些準備工作,創建一個自定義View框架,先初始化一下Paint畫筆,並設置相關方法:

public class StudyView extends View {

    private Paint mPaint;
    private Context mContext;

    public StudyView(Context context) {
        super(context);
        init(context);
    }

    public StudyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mContext = context;
        mPaint = new Paint();
        mPaint.setAntiAlias(true); // 消除鋸齒
        mPaint.setStrokeWidth(5); // 設置筆尖寬度
        mPaint.setStyle(Paint.Style.STROKE); // 不填充
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }
}

繪製圓弧和扇形

Canvas提供drawArc()方法,通過傳遞不同的參數可用來繪製圓弧和扇形,此方法有兩個重載方法,詳細參數如下:

  • drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
  1. left:扇形或圓弧所占區域的左邊界線x坐標
  2. top:扇形或圓弧所占區域的上邊界線y坐標
  3. right:右邊界線x坐標
  4. bottom:下邊界線y坐標
  5. startAngle:扇形或圓弧的起始角度
  6. sweepAngle:掃過的角度
  7. userCenter:此參數可以理解為true就是畫扇形,false就是畫圓弧
  8. paint:畫筆
  • drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

此方法第一個參數是一個RectF類,也是邊界,就是把一個方法的left,top,right,bottom封裝到了RectF類中,剩餘參數與上一個方法一致。

接下來用著兩個重載方法分別繪製兩個90°的扇形和兩個90°的圓弧:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 繪製扇形
    canvas.drawArc(0, 0, 200, 200, 0, 90, true, mPaint);
    RectF rectF = new RectF(0, 0, 200, 200);
    canvas.drawArc(rectF, 180, 90, true, mPaint);

    // 繪製圓弧
    canvas.drawArc(300, 0, 500, 200, 0, 90, false, mPaint);
    RectF rectF1 = new RectF(300, 0, 500, 200);
    canvas.drawArc(rectF1, 180, 90, false, mPaint);
}

繪製效果如下圖所示,另外需要說明的一點是,drawArc的第五個參數startAngle中的角度,0°是指坐標系中第四象限中與x重合的角度,順時針方向代表角度增大的方向,如下圖中紅色線條所示。

繪製Bitmap

在Canvas中提供了drawBitmap方法,此方法可以讓我們直接獲取一張圖片繪製到畫布上,有了它可以讓我們的自定義View錦上添花,同時也讓我們實現一些複雜效果有了一個更加方便的途徑。下麵是drawBitmap的幾個比較常用的重載方法:

  • drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
  1. bitmap:Bitmap資源文件
  2. left和top:代表了圖片左上角落入的位置坐標。
  3. top:看2
  4. paint:畫筆
  • drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
  1. src:在Bitmap圖片上截取一部分作為繪製源,可null
  2. det:將繪製目標拉伸平鋪到det指定的矩形中
  • drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
    同第二個重載方法,幾乎一毛一樣。

  • drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)

  1. matrix:Matrix的參數傳入是的drawBitmap功能變得異常強大,讓此方法有意思了許多,通過matrix可以實現圖片的平移(postTranslate())、縮放(postScale())、旋轉(postRotate())、錯切(postSkew())等等花式炫酷效果,由於Matrix的用法稍微多一些,篇幅限制,這裡就先一帶而過了,感興趣的朋友可以自行探索。

在onDraw方法中drawBitmap的以上重載方法,註意在使用完Bitmap之後記得用Bitmap.recycle()來回收掉資源,以防止oom。

/** drawBitmap */
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), android.R.mipmap.sym_def_app_icon);
// 繪製圖片
canvas.drawBitmap(bitmap, 0, 300, null);
// 將圖片拉伸平鋪在RectF矩形內
canvas.drawBitmap(bitmap, null, new RectF(200, 300, 500, 500), null);
// 截取圖片的四分之一拉伸平鋪在RectF矩形內
canvas.drawBitmap(bitmap, new Rect(0, 0, bitmap.getWidth()/2, bitmap.getHeight()/2), new RectF(500, 300, 800, 500), null);

Matrix matrix = new Matrix();
matrix.postTranslate(800, 300); // 將bitmap平移到此位置
canvas.drawBitmap(bitmap, matrix, mPaint);

// 為防止oom,及時回收bitmap
bitmap.recycle();

效果如下圖(紅框內)。

繪製圓形

  • drawCircle(float cx, float cy, float radius, Paint paint)
  1. cx:圓心x坐標
  2. cy:圓心y坐標
  3. radius:半徑
canvas.drawCircle(100, 700, 100, mPaint);

效果如下圖:

繪製點

  • drawPoint(float x, float y, Paint paint)
  1. x:點的x坐標
  2. y:點的y坐標
  • drawPoints(float[] pts, Paint paint) 繪製一組點
  1. pts:float數組,兩位為一組,兩兩結合代表x、y坐標,例如:pts[0]、pts[1]代表第一個點的x、y坐標,pts[2]、pts[3]代表第二個點的x、y坐標,依次類推。
  • drawPoints(float[] pts, int offset, int count, Paint paint) 繪製一組點
  1. pts:float數組,兩位為一組,兩兩結合代表x、y坐標,例如:pts[0]、pts[1]代表第一個點的x、y坐標,pts[2]、pts[3]代表第二個點的x、y坐標,依次類推。
  2. offset:代表數組開始跳過幾個只開始繪製點,註意這裡不是指數組的下標,而是代表跳過幾個值。
  3. count:在跳過offset個值後,處理幾個值,註意這裡的count不是代表點的個數,而是代表數組中值的個數。
canvas.drawPoint(100, 700, mPaint); // 繪製一個點

float[] points = new float[] {
    130, 700,
    160, 700,
    190, 700,
    210, 700,
    240, 700
};

canvas.drawPoints(points, 2, 4, mPaint); // 繪製一組點(代表跳過前兩個值,處理4個值,也就是實際繪製2個點)

效果如下圖:

繪製橢圓

  • drawOval(float left, float top, float right, float bottom, Paint paint)
  1. left
  2. top
  3. right
  4. bottom

在left、top、right、bottom圍成的區域內繪製一個橢圓。

  • drawOval(RectF oval, Paint paint)
  1. 將第一個重載方法的left、top、right、bottom封裝到RectF類中,與扇形的重載方法異曲同工。
RectF rectF2 = new RectF(300, 600, 700, 800); // 創建一個RectF
canvas.drawOval(rectF2, mPaint);

效果如下圖:

繪製矩形

  • drawRect(float left, float top, float right, float bottom, Paint paint)
  • drawRect(Rect r, Paint paint)
  • drawRect(RectF rect, Paint paint)

drawRect的參數非常好理解,這裡就不啰嗦了,直接上代碼看效果:

canvas.drawRect(rectF2, mPaint);

註:這裡的rectF2即上文繪製橢圓時創建的RectF對象。

繪製圓角矩形

  • drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
  • drawRoundRect(RectF rect, float rx, float ry, Paint paint)

drawRoundRect是繪製圓角矩形,用法和drawRect類似,唯一不同的是多了兩個參數:

  1. rx:x軸方向的圓角弧度
  2. ry:y軸方向的圓角弧度

上代碼,看效果:

canvas.drawRoundRect(rectF2, 60, 30, mPaint);

這裡為了突出兩個方向的圓角弧度,特地將rx和ry設置差距比較大,效果如下圖:

繪製直線

  • drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
  • drawLines(float[] pts, int offset, int count, Paint paint)
  • drawLines(float[] pts, Paint paint)

drawLine和drawLines一個是繪製一個點,一個是繪製一組點,其中drawLines中的float數組中四個值為一組點,其用法可以參照drawPoints。

canvas.drawLine(100, 820, 800, 820, mPaint);

float[] lines = new float[]{
        100f, 850f, 800f, 850f,
        100f, 900f, 800f, 900f,
        100f, 950f, 800f, 950f
};
canvas.drawLines(lines, mPaint); // 按floats數組中,四個數為1組,繪製多條線

效果如下圖:

drawPath() 繪製不規則圖形

上面的這些Canvas方法固然已經很強大了,但是我們如果想要繪製一些不規則的圖形怎麼辦,這時候就要用到強大的drawPath()方法了,通過對Path進行設置不同的坐標、添加不同圖形,最後傳入drawPath方法中可以繪製出複雜的且不規則的形狀。以下是drawPath的方法及參數:

  • drawPath(Path path, Paint paint)

這裡的關鍵參數就是Path,Path類的方法較多,大部分用法類似,這裡挑幾個說一下:

  • Path類
  1. addArc(RectF oval, float startAngle, float sweepAngle) 往path裡面添加一個圓弧
  2. addCircle(float x, float y, float radius, Path.Direction dir) 添加一個圓形
  3. addOval(RectF oval, Path.Direction dir) 添加一個橢圓
  4. addRect(RectF rect, Path.Direction dir) 添加一個矩形
  5. lineTo(float x, float y) 連線到坐標(x,y)
  6. moveTo(float x, float y) 將path繪製點移動到坐標(x,y)
  7. close() 用直線閉合圖形,調用此方法後,path會將最後一處點與起始用直線連接起來,path起始點為moveTo()方法的坐標上,如果沒有調用moveTo()起始點將預設為(0,0)坐標。
  8. ...

接下來使用drawPath繪製一個樓梯:

// 使用 Path 繪製一個樓梯
Path path = new Path();
path.lineTo(0, 1000);
path.lineTo(100, 1000);
path.lineTo(100, 1100);
path.lineTo(200, 1100);
path.lineTo(200, 1200);
path.lineTo(300, 1200);
path.lineTo(300, 1300);
path.lineTo(400, 1300);
path.lineTo(400, 1400);
path.lineTo(0, 1400);
path.lineTo(0, 1000);
path.close();
canvas.drawPath(path, mPaint);

效果如下圖:

再用drawPath方法繪製一個Android小機器人:

/ 使用 Path 繪製一個Android機器人
// 繪製兩個觸角
path.reset();
path.moveTo(625, 1050);
path.lineTo(650, 1120);
path.moveTo(775, 1050);
path.lineTo(750, 1120);

path.addArc(new RectF(600, 1100, 800, 1300), 180, 180); // 繪製頭部
path.addCircle(666.66f, 1150, 10, Path.Direction.CW); // 繪製眼睛,CW:順時針繪製, CCW:逆時針繪製
path.addCircle(733.33f, 1150, 10, Path.Direction.CW);
path.addRect(new RectF(600, 1200, 800, 1300), Path.Direction.CW);  // 身體
canvas.drawPath(path, mPaint);

效果圖如下:

最後,上文中Canvas示例的全部代碼如下:

public class StudyView extends View {

    private Paint mPaint;
    private Context mContext;

    public StudyView(Context context) {
        super(context);
        init(context);
    }

    public StudyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mContext = context;
        mPaint = new Paint();
        mPaint.setAntiAlias(true); // 消除鋸齒
        mPaint.setStrokeWidth(5); // 設置筆尖寬度
        mPaint.setStyle(Paint.Style.STROKE); // 不填充
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /** 1、drawArc */

        // 繪製扇形
        canvas.drawArc(0, 0, 200, 200, 0, 90, true, mPaint);
        RectF rectF = new RectF(0, 0, 200, 200);
        canvas.drawArc(rectF, 180, 90, true, mPaint);

        // 繪製圓弧
        canvas.drawArc(300, 0, 500, 200, 0, 90, false, mPaint);
        RectF rectF1 = new RectF(300, 0, 500, 200);
        canvas.drawArc(rectF1, 180, 90, false, mPaint);

        /** 2、drawBitmap */
        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), android.R.mipmap.sym_def_app_icon);
        // 繪製圖片
        canvas.drawBitmap(bitmap, 0, 300, null);
        // 將圖片拉伸平鋪在RectF矩形內
        canvas.drawBitmap(bitmap, null, new RectF(200, 300, 500, 500), null);
        // 截取圖片的四分之一拉伸平鋪在RectF矩形內
        canvas.drawBitmap(bitmap, new Rect(0, 0, bitmap.getWidth()/2, bitmap.getHeight()/2), new RectF(500, 300, 800, 500), null);

        Matrix matrix = new Matrix();
        matrix.postTranslate(800, 300); // 將bitmap平移到此位置
        canvas.drawBitmap(bitmap, matrix, mPaint);

        // 為防止oom,及時回收bitmap
        bitmap.recycle();

        /** 3、drawCircle */
        canvas.drawCircle(100, 700, 100, mPaint);

        /** 4、繪製一個點 */
        canvas.drawPoint(100, 700, mPaint); // 繪製一個點

        float[] points = new float[] {
            130, 700,
            160, 700,
            190, 700,
            210, 700,
            240, 700
        };

        canvas.drawPoints(points, 2, 4, mPaint); // 繪製一組點(代表跳過前兩個值,處理4個值,也就是實際繪製2個點)


        RectF rectF2 = new RectF(300, 600, 700, 800); // 創建一個RectF

        /** 5、drawOval 繪製橢圓 */
        canvas.drawOval(rectF2, mPaint);

        /** 6、drawRect 繪製矩形*/
        canvas.drawRect(rectF2, mPaint);
        canvas.drawRoundRect(rectF2, 60, 30, mPaint);

        /** 7、drawLine */
        canvas.drawLine(100, 820, 800, 820, mPaint);

        float[] lines = new float[]{
                100f, 850f, 800f, 850f,
                100f, 900f, 800f, 900f,
                100f, 950f, 800f, 950f
        };
        canvas.drawLines(lines, mPaint); // 按floats數組中,四個數為1組,繪製多條線


        /** 8、drawPath */

        // 使用 Path 繪製一個樓梯
        Path path = new Path();
        path.moveTo(0, 1000);
        path.lineTo(100, 1000);
        path.lineTo(100, 1100);
        path.lineTo(200, 1100);
        path.lineTo(200, 1200);
        path.lineTo(300, 1200);
        path.lineTo(300, 1300);
        path.lineTo(400, 1300);
        path.lineTo(400, 1400);
        path.close();
        canvas.drawPath(path, mPaint);

        // 使用 Path 繪製一個Android機器人

        // 繪製兩個觸角
        path.reset();
        path.moveTo(625, 1050);
        path.lineTo(650, 1120);
        path.moveTo(775, 1050);
        path.lineTo(750, 1120);

        path.addArc(new RectF(600, 1100, 800, 1300), 180, 180); // 繪製頭部
        path.addCircle(666.66f, 1150, 10, Path.Direction.CW); // 繪製眼睛,CW:順時針繪製, CCW:逆時針繪製
        path.addCircle(733.33f, 1150, 10, Path.Direction.CW);
        path.addRect(new RectF(600, 1200, 800, 1300), Path.Direction.CW);  // 身體
        canvas.drawPath(path, mPaint);
    }
}

完整效果圖如下:

其實Canvas除了可以繪製圖形之外,還可以繪製文字,Canvas的繪製文字的方法有drawText()、drawTextOnPath()、drawTextRun()等方法,在繪製文字是和Paint的結合更為緊密,所以講繪製文字的方法放在下文和Paint一起講可能效果會更好一些,好了,廢話不多說了,接下來咱們就開始Paint的篇章。

Paint

為了更為清晰的講解Paint的用法,先來新建一個自定義類,暫叫PaintStudyView,接下來創建一個它的大體骨架,在此類中定義了一些變數,變數的意義請見註釋:

public class PaintStudyView extends View {

    private Paint mTextPaint;  // 繪製文字的Paint
    private Paint mPointPaint; // 繪製參考點的Paint
    private Context mContext;

    private final static float Y_SPACE = 100; // y軸方向的間距

    public PaintStudyView(Context context) {
        super(context);
        init(context);
    }

    public PaintStudyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mContext = context;
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true); // 消除鋸齒
        mTextPaint.setStrokeWidth(1); // 設置筆尖寬度
        mTextPaint.setStyle(Paint.Style.FILL); // 填充
        mTextPaint.setTextSize(30);

        mPointPaint = new Paint();
        mPointPaint.setAntiAlias(true);
        mPointPaint.setStrokeWidth(5);
        mPointPaint.setColor(Color.RED); // 將參考點的Paint設置為紅色
        mPointPaint.setStyle(Paint.Style.STROKE);// 不填充
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }
}

Canvas的繪製文字的相關方法:

drawText()的重載方法

drawText() 是Canvas的繪製文字中的最長用的方法,它只能按照從左至右的普通方式來繪製文字。

  • drawText(String text, float x, float y, Paint paint)
  1. text:待繪製的文字內容
  2. x:文字繪製位置的x坐標
  3. y:文字繪製位置的y坐標
  4. paint:Paint畫筆,可以通過Paint.setTextAlign()來決定文字的方位,有:Paint.Align.LEFT(居左),Paint.Align.RIGHT(居右),Paint.Align.CENTER(居中)三個位置。
  • drawText(String text, int start, int end, float x, float y, Paint paint)
  1. start:代表從text中的第幾個字元開始截取繪製,包含第start個字元。
  2. end:代表截取到text的第幾個字元,不包含第end個字元。

例如:我是一個自定義View的控制項,start=1,end=6,截取後為:是一個自定

下麵兩個重載方法可以參考第二個很容易就能理解:

  • drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
  • drawText(char[] text, int index, int count, float x, float y, Paint paint)

以下示例說明瞭文字的不同位置,同時也說明瞭第二個和第四個重載方法對字元串截取時的用法:

String str = "我是一個自定義View的控制項";// 待繪製文字

float x = getWidth() / 2;
float y = 100;
canvas.drawPoint(x, y, mPointPaint); // 繪製參考點,便於觀察文字處於x,y坐標的位置,從而來學習setTextAlign()方法

mTextPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(str, x, y, mTextPaint);

y += Y_SPACE;
canvas.drawPoint(x, y, mPointPaint);
mTextPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(str, 0, 6, x, y, mTextPaint);

y += Y_SPACE;
canvas.drawPoint(x, y, mPointPaint);
mTextPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(str.toCharArray(), 1, 6, x, y, mTextPaint);

效果圖如下:

其中的紅點為額外添加的參考坐標,目的是為了突出setTextAlign中參數的位置。

drawTextOnPath()的重載方法

drawTextOnPath() 由方法名字我們就可以看出來他可以按照Path的走向來繪製文字,例如我們在path中傳入一個圓弧,那麼繪製出來的文字走向就是圓弧狀的,是不是很酷,來看一下它的重載方法:

  • drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
  1. text:同drawText的第一個參數。
  2. path:Path參數,用法在前文已經說過了。
  3. hOffset:水平方向的偏移量。
  4. vOffset:垂直方向的偏移量。

關鍵點:有一點一定要提的就是,這裡的hOffset是相對於path路徑的水平偏移量,而vOffset也是相對於path路徑的垂直偏移量,這麼說可能還有點不清楚,結合下麵的示例來說明,請仔細體會這裡的意思:

// 1、下開口圓弧方向繪製文字
mTextPaint.setTextAlign(Paint.Align.LEFT);
y += Y_SPACE;
Path path = new Path();
path.addArc(new RectF(x - 150, y, x + 150, y + 300), 180,180);
canvas.drawPath(path, mPointPaint); // 參考弧度線
canvas.drawTextOnPath(str, path, 0, 0, mTextPaint); // 按照path路徑繪製文字,不偏移
canvas.drawTextOnPath(str, path, 30, 30, mTextPaint);// 向水平、垂直方向各偏移30
canvas.drawTextOnPath(str, path, 60, 60, mTextPaint);// 向水平、垂直方向各偏移60

// 2、上開口圓弧方向繪製文字
path.reset();
y += Y_SPACE;
path.addArc(new RectF(x - 150, y, x + 150, y + 300), 0, 180);
canvas.drawPath(path, mPointPaint); // 參考弧度線
canvas.drawTextOnPath(str, path, 0, 0, mTextPaint);
canvas.drawTextOnPath(str, path, 30, 30, mTextPaint);
canvas.drawTextOnPath(str, path, 60, 60, mTextPaint);
path.close();

// 3、豎直方向繪製文字
path.reset();
path.moveTo(200, y);
path.lineTo(200, y + 4 * Y_SPACE);
canvas.drawPath(path, mPointPaint); // 參考弧度線
canvas.drawTextOnPath(str, path, 0, 0, mTextPaint);
canvas.drawTextOnPath(str, path, 30, 60, mTextPaint);


y += Y_SPACE;
y += Y_SPACE;
y += Y_SPACE;
y += Y_SPACE;

// 4、水平方向繪製文字
path.reset();
path.moveTo(x, y);
path.lineTo(x + 4 * Y_SPACE, y);
canvas.drawPath(path, mPointPaint); // 參考弧度線
canvas.drawTextOnPath(str, path, 0, 0, mTextPaint);
canvas.drawTextOnPath(str, path, 30, 60, mTextPaint);

如下是效果圖,註意看圖片中的紅色部分,紅色的線是用代碼繪製出來的path參考線,紅色的箭頭是path的水平和垂直方向的走向,結合下圖可以更好的理解drawTextOnPath的hOffset和vOffset參數。

  • drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)

這個方法的套路想必不用解釋了。

drawTextRun()的重載方法

  • drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, float x, float y, boolean isRtl, Paint paint)
  • drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint paint)

drawTextRun()可以文字的是從左到右還是從右到左的順序來繪製,其中倒數第二個參數isRtl就是用來控制方向的,true就是倒序繪製,false就是正序繪製,其他的參數就沒啥好說的了,這個方法用法比較簡單,這裡就不貼代碼了。另外這個方法是在API 23才開始添加的,使用時要註意。

到目前為止,Canvas的常用用法基本介紹完了,接下來就可以著重來看Paint的使用了,Paint和Canvas兩者是不可分離的,兩者協作,相輔相成。所以在下麵的用法示例中不免要用到Canvas的相關方法。

使用Paint測量文字的尺寸,定位文字

我們在開發自定義控制項時,免不了要精確定位文字的文字,例如必須把文字放在某個區域的正中間,或者必須讓一行文字的幾何中心精確的處於某個點上,這時我們如果不懂這裡的竅門可能就要盲目的試位置了,這樣一點一點試出來的位置很不可靠,可能換個屏幕尺寸位置就不對了,接下來怎麼來看看怎麼樣用最優雅的姿勢來精確的定位文字。

其實在水平方向的定位還比較好說,直接使用Paint.setTextAlign()就能搞定大多需求,主要是在水平方向上稍稍複雜一點,想要定位位置,首先需要先獲取文字的高度,要用到Paint的以下兩個方法:

  • float ascent():根據文字大小獲取文字頂端到文字基線的距離(返回的是負值)
  • float descent():根據文字大小獲取文字底部到文字基線的距離(返回的事正值)

有了這兩個方法那就非常好辦了,首先用代碼結合效果圖說明一下基線、ascent、descent和文字的關係:

y += Y_SPACE;
canvas.drawPoint(x, y, mPointPaint);
canvas.drawLine(x - 300, y, x+300, y, mPointPaint);
mTextPaint.setTextAlign(Paint.Align.CENTER);// 水平方向上讓文字居中
float ascent = mTextPaint.ascent(); // 根據文字大小獲取文字頂端到文字基線的距離(返回的是負值)
float descent = mTextPaint.descent(); // 根據文字大小獲取文字底部到文字基線的距離(返回的事正值)
canvas.drawLine(x - 300, y + ascent, x+300, y + ascent, mPointPaint);
canvas.drawLine(x - 300, y + descent, x+300, y + descent, mPointPaint);
canvas.drawText(str, x, y, mTextPaint);

效果圖如下,它們之間的關係註意看圖片裡面的說明。(註:在這裡感謝園友在截圖中指出的一處錯誤,現已修正)

接下來就讓文字的中心落在參考點上:

// 將文字的中心定位在參考點上
y += Y_SPACE;
canvas.drawPoint(x, y, mPointPaint);
canvas.drawText(str, x, y - ascent / 2 - descent / 2, mTextPaint);

效果圖如下,仔細看參考點(紅點)和文字的位置:

利用Paint.setShader()(著色器)繪製漸變色

使用 setShader() 方法可以添加漸變顏色也可以使用圖片作為背景,其參數是一個Shader類,傳入不同的Shader子類可以實現不同的漸變效果或者添加背景圖片,其子類有一下幾種:

  • LinearGradient:線性漸變
  • RadialGradient:放射狀漸變
  • SweepGradient:掃描漸變
  • BitmapShader:添加背景圖片
  • ComposeShader:多種Shader組合

上面接個Shader的子類在使用方式上都差不多,這裡只用LinearGradient為例說明一下,並註意對LinearGradient構造器的最後一個參數傳入不同的參數對應的效果圖:

/* Shader 漸變 */
y = 100;
Shader shader = new LinearGradient(x - 50, y - 80, x + 50, y + 80,
        Color.parseColor("#FFCCBB"), Color.parseColor("#FF0000"), Shader.TileMode.CLAMP);
mTextPaint.setShader(shader);
canvas.drawRect(x - 500, y - 80, x + 500, y + 80, mTextPaint);

y += 3 * Y_SPACE;
Shader shader1 = new LinearGradient(x - 50, y - 80, x + 50, y + 80,
        Color.parseColor("#FFCCBB"), Color.parseColor("#FF0000"), Shader.TileMode.REPEAT);
mTextPaint.setShader(shader1);
canvas.drawRect(x - 500, y - 80, x + 500, y + 80, mTextPaint);

y += 3 * Y_SPACE;
Shader shader2 = new LinearGradient(x - 50, y - 80, x + 50, y + 80,
        Color.parseColor("#FFCCBB"), Color.parseColor("#FF0000"), Shader.TileMode.MIRROR);
mTextPaint.setShader(shader2);
canvas.drawRect(x - 500, y - 80, x + 500, y + 80, mTextPaint);

效果圖如下:

除了以上這些,Paint的用法還有很多很多,一時半會也列不完,博主在這也只能拋磚引玉,感興趣的朋友可查看官方API自行探索。


最後想說的是,本系列文章為博主對Android知識進行再次梳理,查缺補漏的學習過程,一方面是對自己遺忘的東西加以複習重新掌握,另一方面相信在重新學習的過程中定會有巨大的新收穫,如果你也有跟我同樣的想法,不妨關註我一起學習,互相探討,共同進步!

參考文獻:


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ELF全稱Executable and Linkable Format,可執行連接格式,ELF格式的文件最早用於存儲Linux程式,後演變到ARM系統上存儲ARM程式 ...
  • relocatable文件,即可重定向文件,這個文件是由編譯器彙編源文件(.c/.s)而成的。直接生成的重定向文件叫object file,經過封裝的重定向文件稱為library file。relocatable文件是一個中間的過渡文件,其本身也不能被ARM直接執行,需經過第二步轉換,即鏈接,所以這... ...
  • 一、系統環境 操作系統:Windows10專業版 64位Redis版本:redis-64.3.0.503 二、問題描述 1.命令行啟動: 可以啟動成功; 2.將Redis安裝為Windows系統服務: 3.進入系統服務頁面: Win + r打開運行命令框,services.msc打開系統服務頁面 4 ...
  • <?php $link = mysqli_connect("localhost","root","root","dbname"); //連接資料庫 $sql = "select field from dbname limit 1"; $ressql = mysqli_query($link,$sql ...
  • 連接資料庫: 輸入資料庫密碼即可登陸。 查看mysql版本信息: 查看當前時間: 實現Windows與Ubuntu虛擬機之間的文件互傳問題。 其實很簡單就是需要安裝一個VMware tools即可。 打開文件管理就有一個VM的tar包,沒有的話就需要多掛一個cd驅動器。 右鍵,設置, 我有兩個cd驅 ...
  • Note:這篇文章是基於Android Studio 3.01版本的,NDK是R16。step1:創建一個包含C++的項目其他預設就可以了。C++ Standard指定編譯庫的環境,其中Toolchain Default使用的是預設的CMake環境;C++ 11也就是C++環境。兩種環境都可以編庫,... ...
  • 1 Cookie、Session 和 Token 都是用來做持久化處理的,目的就是讓客戶端和服務端相互認識。Http 請求預設是不持久的沒有狀態的,誰也不認識誰。 2 Cookie: 是存放在客戶端的信息,伺服器通過響應頭 Set-Cookie 欄位給客戶端,如果 Cookie 已過期一般是會被清楚 ...
  • 1. 寫這個只是為了自己記憶,有相關pdf文件,如需要留下郵箱。。 2. 在類的頭文件中儘量少引入其他頭文件 除非確有必要,否則不要引入頭文件。一般來說,應在某個類的頭文件中使用向前聲明來提及別的類(使用@class),併在實現文件中引入那些類的頭文件,這樣做可以儘量降低類之間的耦合。 如果要聲明某 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...