Android 自定義View學習(二)——開始了解Canvas和Paint

View的三大流程:測量,布局,繪制
上篇Android自定義View學習(一)——準備簡單介紹了部分測量的知識,后面會繼續學習測量的知識。本篇記錄下繪制onDraw()方法的學習,只是開始。

1.View的繪制

完成了View的測量后,根據拿到的View的大小,位置,重寫onDraw(Canvas canvas)就可以進行繪制。

現實中,如果想要畫一幅畫,必須要有畫筆和畫布。Canvas就是畫布,Paint就是畫筆。CanvasPatint有各種各樣的屬性。本篇先學習部分常用的基礎的屬性,一些可以高度化定制的屬性后續再進行學習。


2.Canvas

源碼中關于Canvas的解釋:

The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).

想要畫出一個View就必須要有4個必要的元素:

  1. 保存像素的Bitmap
  2. 管理繪制請求的Canvas
  3. 繪畫的原始基本元素,例如矩形,線,文字,Bitmap
  4. 擁有顏色和風格信息的畫筆

翻譯水平,32級 : )


Canvas有兩種常見創建方法:

  • Canvas canvas = new Canvas() 空參構造方法
  • Canvas canvas = new Canvas(bitmap) 創建一個裝載畫布。構造方法中傳入的bitmap存儲所有繪制在canvas的信息。

常用的幾個繪制方法

方法 作用
drawRect() 畫矩形
drawCircle() 畫圓
drawArc() 畫圓弧
drawRoundRect() 畫圓角矩形
drawBitmap() 畫一個Bitmap
drawOval 畫橢圓
drawText() 畫文字

Canvas的方法有很多,這里先記錄幾個簡單的繪制方法,其他的后續學習再做補充。


2.1 drawRect() 繪制矩形

drawRect()有三種重載方法:

  • drawRect(float left, float top, float right, float bottom, @NonNull Paint paint)

Draw the specified Rect using the specified paint. The rectangle will be filled or framed based on the Style in the paint.
@param left The left side of the rectangle to be drawn
@param top The top side of the rectangle to be drawn
@param right The right side of the rectangle to be drawn
@param bottom The bottom side of the rectangle to be drawn
@param paint The paint used to draw the rect

MeausreView代碼,主要繪制就是onDraw()方法:

public class MeasureView extends View {

    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public MeasureView(Context context) {
        super(context);
        initPaint();
    }

    public MeasureView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public MeasureView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    private void initPaint() {
        paint.setColor(Color.parseColor("#FF4081"));
    }

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

        float left = getLeft();
        float right = getRight();
        float top = getTop();
        float bottom = getBottom();
        canvas.drawRect(left,top,right,bottom,paint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measuredHeight(heightMeasureSpec));
    }

    /**
     * 測量寬
     *
     * @param widthMeasureSpec
     */
    private int measureWidth(int widthMeasureSpec) {
        int result;
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    /**
     * 測量高
     *
     * @param heightMeasureSpec
     */
    private int measuredHeight(int heightMeasureSpec) {
        int result;
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }
}

Activity的布局文件中:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.szlk.customview.custom.MeasureView
        android:id="@+id/mv_custom_activity"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@color/colorPrimary" />

</LinearLayout>
繪制矩形

MeausureViewwidth = right - left
MeausureViewheight = bottom - top

注意drawRect(left,top,right,bottom,paint)的參數順序。


  • drawRect(@NonNull Rect r, @NonNull Paint paint)
  • drawRect(@NonNull RectF r, @NonNull Paint paint)

兩個方法的差別在于RectRectF的差別。

Rect

Rect holds four integer coordinates for a rectangle. The rectangle is
represented by the coordinates of its 4 edges (left, top, right bottom).
These fields can be accessed directly. Use width() and height() to retrieve
the rectangle's width and height. Note: most methods do not check to see that the coordinates are sorted correctly (i.e. left <= right and top <= bottom).

RectF

RectF holds four float coordinates for a rectangle. The rectangle is represented by the coordinates of its 4 edges (left, top, right bottom). These fields can be accessed directly. Use width() and height() to retrieve
the rectangle's width and height. Note: most methods do not check to see that the coordinates are sorted correctly (i.e. left <= right and top <= bottom).

兩者差別就是:Rect 坐標為integerRectF 坐標為float

使用:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Rect rect = new RectF(100,100,200,200);
    canvas.drawRect(rect,paint);
}
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    RectF rect = new RectF(100.5f,100.5f,200.5f,200.5f);
    canvas.drawRect(rect,paint);
}

注意構造方法中的參數順序


2.2 drawCricle() 繪制圓形

  • drawCircle(float cx, float cy, float radius, @NonNull Paint paint)

Draw the specified circle using the specified paint. If radius is <= 0, then nothing will be drawn. The circle will be filled or framed based on the Style in the paint.
@param cx The x-coordinate of the center of the cirle to be drawn
@param cy The y-coordinate of the center of the cirle to be drawn
@param radius The radius of the cirle to be drawn
@param paint The paint used to draw the circle

radius: 半徑
cx : 圓心的x坐標
cy : 圓心的y坐標
使用的時候需要考慮圓心和半徑


繪制圓形

使用:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    float width = getWidth();
    float height = getHeight();
    float radius = Math.min(width,height)/2;
    canvas.drawCircle(width/2,height/2,radius,paint);
}

繪制圓形時,半徑是寬和高中較小者的二分之一


2.3 drawArc() 繪制扇形

  1. drawArc(float left, float top, float right, float bottom, float startAngle,float sweepAngle, boolean useCenter, @NonNull Paint paint)
  2. drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint)

兩個方法的差別:

  • 方法2把坐標封裝進RectF對象中
  • 方法1要求系統最低為21

drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint)

@param oval The bounds of oval used to define the shape and size of the arc
@param startAngle Starting angle (in degrees) where the arc begins
@param sweepAngle Sweep angle (in degrees) measured clockwise
@param useCenter If true, include the center of the oval in the arc, and close it if it is being stroked. This will draw a wedge
@param paint The paint used to draw the arc

  • float startAngle 開始繪制的角度
  • float sweepAngle 扇形掃過的角度,并不是停止時的角度。停止角度 = startAngle+ sweepAngle
  • boolean useCenter ture就是有焦點圓心 , false 沒有

扇形,有焦點圓心
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    RectF rect = new RectF(0f,0f,500f,500f);
    canvas.drawArc(rect,0,60,true,paint);
    canvas.drawArc(rect,60,30,true,paint_2);
}

此時的boolean useCentertrue


當把boolean useCenter設置為false

扇形無焦點圓形

此時之畫出了開始點和結束點兩點之間的區域


2.4 drawBitmap() 繪制Bitmap

  • drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint)

@param bitmap The bitmap to be drawn
@param left The position of the left side of the bitmap being drawn
@param top The position of the top side of the bitmap being drawn
@param paint The paint used to draw the bitmap (may be null)

  • left 左上角橫坐標
  • top 左上角縱坐標

繪制bitmap
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    float width = (getWidth()-bitmap.getWidth())/2;
    float height = (getHeight()-bitmap.getHeight())/2;
    canvas.drawBitmap(bitmap,width,height,paint);
}

根據lefttop確定繪制的位置,此時Paint的用于繪制文字的屬性設置在繪制Bitmap時是無效的。


2.5 drawText()繪制文字

  • drawText(@NonNull String text, float x, float y, @NonNull Paint paint)

Draw the text, with origin at (x,y), using the specified paint. The
origin is interpreted based on the Align setting in the paint.
@param text The text to be drawn
@param x The x-coordinate of the origin of the text being drawn
@param y The y-coordinate of the baseline of the text being drawn
@param paint The paint used for the text (e.g. color, size, style)

baseline

繪制文字
private void initPaint() {
    paint.setColor(Color.parseColor("#FF4081"));
    paint.setTextSize(90f);
}

/**
 * 繪制文字
 * @param canvas
 */
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawText("HelloWorld",100,100,paint);
}

繪制文字需要設置Paint的屬性。


2.6 drawPath() 繪制路徑

  • drawPath()

@param path The path to be drawn
@param paint The paint used to draw the path


繪制路徑
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Path p = new Path();
    p.moveTo(100, 100);
    p.lineTo(200, 50);
    p.lineTo(300, 100);
    p.lineTo(200,400);

    canvas.drawPath(p,paint);
}
  • moveTo()就是繪制的起始點,默認為(0,9)
  • lineTo() 連接的點

3.Paint

The Paint class holds the style and color information about how to draw geometries, text and bitmaps.

<p>
畫筆能夠拿到,所要繪制的幾何圖形、文字或者Bitmap的顏色、風格等信息
<p>
畫筆有三種構造方法:

  • public Paint() { this(0); }

Create a new paint with default settings.

創建一個默認屬性的畫筆


  • public Paint(int flags) {...}

Create a new paint with the specified flags. Use setFlags() to change these after the paint is created.
<p>
@param flags initial flag bits, as if they were passed via setFlags().

創建一個帶有標記的畫筆。也可以通過setFlags()去為一個已經創建過的畫筆設置標簽


  • public Paint(Paint paint) {...}

Create a new paint, initialized with the attributes in the specified paint parameter.
<p>
@param paint Existing paint used to initialized the attributes of the new paint.

通過一個已經配置好信息的畫筆來創建一個新的畫筆


3.1常用屬性方法

  • 繪制文字
方法 作用
setColor(@ColorInt int color) 設置畫筆顏色
setStrokeWidth(float width) 設置畫筆粗細
setTextSkewX(float f) 設置傾斜,負右斜,正為左
setARGB(int a,int r,int g,int b) 設置顏色,a為透明度
setTextSize(float textSize) 設置繪制文字大小
setFakeBoldText(boolean fakeBoldText) 是否粗體
setTextAlign(Paint.Align align) 設置文字對齊方式,LEFT,CENTER,RIGHT
setUnderlineText(boolean underlineText) 設置下劃線
setStyle(Style style) 設置畫筆樣式,FILL,STROKE,FILL_AND_STROKE
setTypeface(Typeface typeface) 設置Typeface對象,即字體風格,包括粗體,斜體以及襯線體,非襯線體等
  • 繪制圖像
方法 作用
setDither(boolean dither) 設置抖動處理
setAlpha(int a) 設置透明度
setAntiAlias(boolean aa) 是否開啟抗鋸齒
setFilterBitmap() 是否開啟優化Bitmap
setColorFilter(ColorFilter filter) 設置顏色過濾
setMaskFilter(MaskFilter maskfilter) 設置濾鏡的效果
setShader(Shader shader) 設置圖像漸變效果
setSrokeJoin(Paint.Join join) 設置圖像結合方式
setXfermode(Xfermode xfermode) 設置圖像重疊效果
setPathEffect(PathEffect effect) 設置路徑效果
reset() 恢復默認設置

暫時就是先看看,知道有這么個方法。然而方法還有很多 :)


4.最后

這篇了解部分CanvasPaint部分基礎知識。就是調用了方法而已。下篇繼續記錄學習Paint

通過這篇的學習,我再去看網絡其他的自定義View博客,感覺能大概了解所講內容了。不再是一頭的污水。才剛剛開始呢。:)

嗯,本篇有個小坑,不過也不想修改了,這里說一下,onDraw()方法中,最好不要進行new對象,會有警告。本篇這里只是學習,也并無大礙。之后會注意

共勉。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容