專治花裏胡哨(三)自定義View中的畫筆 Paint,你知道多少?

看本篇文章前,請先閱讀治花裏胡哨(二)征服自定義View,各種最基本的drawXXX()方法你都會了嗎?


作爲 花裏胡哨系列的第三篇,這篇文章就詳細的講解一下 Paint 的各種 api 的使用。我們要知道的是 View 上的內容都是通過 Canvas 畫出來的,但是畫成什麼樣子,都是需要通過 Paint 來指揮的,所以說對於 Paint 瞭解的越詳細,那麼我們在後期繪製時,可用的方法就越多。廢話不多說,馬上進入主題。

本人水平有限,如有錯誤,請在下方評論中多多指正



一、 創建畫筆

// 1.創建一個默認畫筆,使用默認的配置
Paint()
// 2.創建一個新畫筆,並通過 flags 參數進行配置。
Paint(int flags)
// 3.創建一個新畫筆,並複製參數中畫筆的設置。
Paint(Paint paint)

第一種創建默認畫筆,第二種中的 flags 設置可以有很多,最常見的是下面的這個,如果需要設置多個參數,參數之間用 | 進行連接即可。

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG ); 

第三種是根據已有的畫筆複製一個畫筆,複製後的畫筆是一個全新的畫筆,對複製後的畫筆進行任何修改調整都不會影響到被複制的畫筆。



二、 畫筆顏色

 mPaint.setColor(Color.RED);//設置顏色
 
 mPaint.setARGB(255, 255, 255, 0);//設置Paint對象顏色,範圍爲0~255
 
 int color = context.getResources().getColor(R.color.colorPrimary);
 mPaint.setColor(color);

第一種是最常用的,第二種通過RGB值設置,第三種是使用系統內置的顏色資源。



三、設置透明度

mPaint.setAlpha(200);//設置alpha不透明度,範圍爲0~255


四、設置抗鋸齒

mPaint.setAntiAlias(true);

開啓抗鋸齒的原因是修改圖形邊緣處的像素顏色,從而讓圖形在肉眼看來具有更加平滑的感覺。大多數情況下,都需要開啓,給個效果圖解釋一下:
在這裏插入圖片描述



五、設置描邊寬度

mPaint.setStyle(Paint.Style.STROKE);  

mPaint.setStrokeWidth(1);
canvas.drawCircle(150, 125, 100, paint);

mPaint.setStrokeWidth(5);
canvas.drawCircle(400, 125, 100, paint);

mPaint.setStrokeWidth(40);
canvas.drawCircle(650, 125, 100, paint);

效果圖:
在這裏插入圖片描述
在畫筆寬度爲 0 的情況下,使用 drawLine 或者使用描邊模式(STROKE)也可以繪製出內容。只是繪製出的內容始終是 1 像素,不受畫布縮放的影響。該模式被稱爲hairline mode (髮際線模式)。



六、設置描邊模式

// 畫筆初始設置
Paint mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(50);
mPaint.setColor(0xFF7FC2D8);

// 填充,默認
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(500, 200, 100, paint);

// 描邊
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(500, 500, 100, paint);

// 描邊 + 填充
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(500, 800, 100, paint);

效果圖:
在這裏插入圖片描述
在這裏設置了描邊的寬度較大值,這樣顯示出來的效果比較明顯,紅色表示圓的實際大小。



七、設置畫筆線帽

mPaint.setStrokeCap(Paint.Cap.BUTT); //無線帽
mPaint.setStrokeCap(Paint.Cap.SQUARE);//方形線帽
mPaint.setStrokeCap(Paint.Cap.ROUND); //圓角效果

效果圖:
在這裏插入圖片描述



八、設置線段連接方式

mPaint.setStrokeJoin(Paint.Join.MITER);//尖角(默認模式)
mPaint.setStrokeJoin(Paint.Join.BEVEL);//平角
mPaint.setStrokeJoin(Paint.Join.ROUND);//圓角

效果圖:
在這裏插入圖片描述



九 、斜接模式長度限制

Android 中線段連接方式默認是 MITER,即在拐角處延長外邊緣,直到相交位置。當連接角度小於一定程度時會自動將連接模式轉換爲 BEVEL(平角)。這個角度大約是 28.96°。即 MITER (尖角) 模式下小於該角度的線段連接方式會自動轉換爲 BEVEL (平角) 模式。我們可以通過下面的方法來更改默認限制:

// 參數 miter 就是對長度的限制,
// 它可以通過這個公式計算:miter = 1 / sin ( angle / 2 ) ,
// angel 是兩條線的形成的夾角
// 這個參數的默認值是 4
paint.setStrokeMiter(10);


十 、雙線性過濾

圖像在放大繪製的時候,默認使用的是最近鄰插值過濾,這種算法簡單,但會出現馬賽克現象;而如果開啓了雙線性過濾,就可以讓結果圖像顯得更加平滑。通過以下代碼設置:

mPaint.setFilterBitmap(true);

效果圖:
在這裏插入圖片描述



十一 、PathEffect

什麼是 PathEffect ?它是用來給圖形的輪廓設置效果。對 Canvas 所有的圖形繪製有效。總共有 6 種,如下表格顯示:

名稱 說明
CornerPathEffect 圓角效果,將尖角替換爲圓角。
DashPathEffect 虛線效果,用於各種虛線效果。
PathDashPathEffect Path 虛線效果,虛線中的間隔使用 Path 代替。
DiscretePathEffect 把線條進行隨機的偏離,讓輪廓變得亂七八糟。亂七八糟的方式和程度由參數決定
SumPathEffect 兩個 PathEffect 效果組合,同時繪製兩種效果。
ComposePathEffect 兩個 PathEffect 效果疊加,先使用效果1,之後使用效果2。

最常用的是前兩種,例如第一種:

RectF rect = new RectF(0, 0, 600, 600);
float corner = 300;

// 使用 CornerPathEffect 實現類圓角效果
canvas.translate((1080 - 600) / 2, (1920 / 2 - 600) / 2);
mPaint.setPathEffect(new CornerPathEffect(corner));
canvas.drawRect(rect, mPaint);

效果如圖:
在這裏插入圖片描述
例如第二種:

Path path_dash = new Path();
path_dash.lineTo(0, 1720);

canvas.save();
canvas.translate(980, 100);
/**
* intervals[]:
* 間隔,用於控制虛線顯示長度和隱藏長度,它必須爲偶數(且至少爲 2 個),
* 按照[顯示長度,隱藏長度,顯示長度,隱藏長度]的順序來顯示。
*
* phase:
* 相位(和正餘弦函數中的相位類似,週期爲intervals長度總和),
* 也可以簡單的理解爲偏移量。
*/
mPaint.setPathEffect(new DashPathEffect(new float[]{200, 100}, 0));
canvas.drawPath(path_dash, mPaint);
canvas.restore();

canvas.save();
canvas.translate(400, 100);
mPaint.setPathEffect(new DashPathEffect(new float[]{200, 100}, 100));
canvas.drawPath(path_dash, mPaint);
canvas.restore();

效果如圖:
在這裏插入圖片描述



十二、setMaskFilter

這個方法的作用是給繪製內容的上方添加效果。寫法如下

/**
 * radius: 模糊半徑
 * NORMAL: 內外都模糊繪製
 * SOLID: 內部正常繪製,外部模糊
 * INNER: 內部模糊,外部不繪製
 * OUTER: 內部不繪製,外部模糊
 */
mPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.NORMAL));//設置畫筆遮罩濾鏡 ,傳入度數和樣式

效果圖就盜用一張:
在這裏插入圖片描述



十三 、渲染器

1. 線性渲染

       /**
         * 1.線性渲染,LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[], @Nullable float positions[], @NonNull TileMode tile)
         * (x0,y0):漸變起始點座標
         * (x1,y1):漸變結束點座標
         * color0:漸變開始點顏色,16進制的顏色表示,必須要帶有透明度
         * color1:漸變結束顏色
         * colors:漸變數組
         * positions:位置數組,position的取值範圍[0,1],作用是指定某個位置的顏色值,如果傳null,漸變就線性變化。
         * tile:用於指定控件區域大於指定的漸變區域時,空白區域的顏色填充方法
         */
        mShader = new LinearGradient(0, 0, 500, 500, new int[]{Color.RED, Color.BLUE, Color.GREEN}, new float[]{0.f, 0.7f, 1}, Shader.TileMode.REPEAT);
        mPaint.setShader(mShader);
        canvas.drawRect(0,0,500,500, mPaint);

效果圖:

在這裏插入圖片描述


2. 環形渲染

       /**
         * 環形渲染,RadialGradient(float centerX, float centerY, float radius, @ColorInt int colors[], @Nullable float stops[], TileMode tileMode)
         * centerX ,centerY:shader的中心座標,開始漸變的座標
         * radius:漸變的半徑
         * centerColor,edgeColor:中心點漸變顏色,邊界的漸變顏色
         * colors:漸變顏色數組
         * stops:漸變位置數組,類似掃描漸變的positions數組,取值[0,1],中心點爲0,半徑到達位置爲1.0f
         * tileMode:shader未覆蓋以外的填充模式。
         */
        mShader = new RadialGradient(250, 250, 250, new int[]{Color.GREEN, Color.YELLOW, Color.RED}, null, Shader.TileMode.CLAMP);
        mPaint.setShader(mShader);
        canvas.drawCircle(250, 250, 250, mPaint);

效果圖:

在這裏插入圖片描述


3. 掃描渲染

/**
  * 掃描渲染,SweepGradient(float cx, float cy, @ColorInt int color0,int color1)
  * cx,cy 漸變中心座標
  * color0,color1:漸變開始結束顏色
  * colors,positions:類似LinearGradient,用於多顏色漸變,positions爲null時,根據顏色線性漸變
  */
mShader = new SweepGradient(250, 250, Color.RED, Color.GREEN);
mPaint.setShader(mShader);
canvas.drawCircle(250, 250, 250, mPaint);

效果圖:

在這裏插入圖片描述


4. 位圖渲染

/**
  * 位圖渲染,BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY)
  * Bitmap:構造shader使用的bitmap
  * tileX:X軸方向的TileMode
  * tileY:Y軸方向的TileMode
  * Shader.TileMode:
         * REPEAT, 繪製區域超過渲染區域的部分,重複排版
         * CLAMP, 繪製區域超過渲染區域的部分,會以最後一個像素拉伸排版
         * MIRROR, 繪製區域超過渲染區域的部分,鏡像翻轉排版
*/
mShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);
mPaint.setShader(mShader);
canvas.drawRect(0,0,500, 500, mPaint);
canvas.drawCircle(250, 250, 250, mPaint);

效果圖:

在這裏插入圖片描述


5. 組合渲染

       /**
         * 組合渲染,
         * ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, Xfermode mode)
         * ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, PorterDuff.Mode mode)
         * shaderA,shaderB:要混合的兩種shader
         * Xfermode mode: 組合兩種shader顏色的模式
         * PorterDuff.Mode mode: 組合兩種shader顏色的模式
         */
        BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
        LinearGradient linearGradient = new LinearGradient(0, 0, 1000, 1600, new int[]{Color.RED, Color.GREEN, Color.BLUE}, null, Shader.TileMode.CLAMP);
        mShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY);
        mPaint.setShader(mShader);
        canvas.drawCircle(250, 250, 250, mPaint);

效果圖:

在這裏插入圖片描述


上面介紹了這麼多的 api 的使用,可能看的眼都花了,下面介紹一個需要點理解力的。


十四、圖層混合模式

在上面的組合渲染中,有一個參數 PorterDuff.Mode ,也就是圖層混合模式。

我們所知的圖層混合所使用的地方一共有三處:

  • 組合渲染 ComposeShader 中會使用到。
  • 畫筆 Paint.setXfermode() 方法中會使用到。
  • PorterDuffColorFilter 中會使用到。

現在說的是第二種,它將所繪製的圖形的像素與 Canvas 中對應位置的像素按照一定規則進行混合,形成新的像素值,從而更新 Canvas 中最終的像素顏色值。一共有 18 種模式:
在這裏插入圖片描述
對各種模式說明:

//所繪製不會提交到畫布上
            new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
            //顯示上層繪製的圖像
            new PorterDuffXfermode(PorterDuff.Mode.SRC),
            //顯示下層繪製圖像
            new PorterDuffXfermode(PorterDuff.Mode.DST),
            //正常繪製顯示,上下層繪製疊蓋
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),

            //上下層都顯示,下層居上顯示
            new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
            //取兩層繪製交集,顯示上層
            new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
            //取兩層繪製交集,顯示下層
            new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
            //取上層繪製非交集部分,交集部分變成透明
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),

            //取下層繪製非交集部分,交集部分變成透明
            new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
            //取上層交集部分與下層非交集部分
            new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
            //取下層交集部分與上層非交集部分
            new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
            //去除兩圖層交集部分
            new PorterDuffXfermode(PorterDuff.Mode.XOR),

            //取兩圖層全部區域,交集部分顏色加深
            new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
            //取兩圖層全部區域,交集部分顏色點亮
            new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
            //取兩圖層交集部分,顏色疊加
            new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
            //取兩圖層全部區域,交集部分濾色
            new PorterDuffXfermode(PorterDuff.Mode.SCREEN),

            //取兩圖層全部區域,交集部分飽和度相加
            new PorterDuffXfermode(PorterDuff.Mode.ADD),
            //取兩圖層全部區域,交集部分疊加
            new PorterDuffXfermode(PorterDuff.Mode.OVERLAY)

展示一下效果圖:

在這裏插入圖片描述

另外,要想使用 setXfermode() 正常繪製,必須使用離屏緩存 (Off-screen Buffer) 把內容繪製在額外的層上,再把繪製好的內容貼回 View 中。也就是這樣:

在這裏插入圖片描述

使用離屏緩衝有兩種方式:

saveLayer()
  • 可以做短時的離屏緩衝。使用方法很簡單,在繪製代碼的前後各加一行代碼,在繪製之前保存,繪製之後恢復。
  int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);

  canvas.drawBitmap(rectBitmap, 0, 0, paint); // 畫方
  paint.setXfermode(xfermode); // 設置 Xfermode
  canvas.drawBitmap(circleBitmap, 0, 0, paint); // 畫圓
  paint.setXfermode(null); // 用完及時清除 Xfermode

  canvas.restoreToCount(saved);
View.setLayerType()
//直接把整個 View 都繪製在離屏緩衝中。 
View.setLayerType() 
//使用 GPU 來緩衝。
setLayerType(LAYER_TYPE_HARDWARE)  
//直接直接用一個 Bitmap 來緩衝。
setLayerType(LAYER_TYPE_SOFTWARE) 

一般情況下,使用 第一種 方法作離屏緩衝。



十五、顏色過濾

1. LightingColorFilter
 /**
   * R' = R * mul.R / 0xff + add.R
   * G' = G * mul.G / 0xff + add.G
   * B' = B * mul.B / 0xff + add.B
  */
//原始圖片效果
LightingColorFilter lighting = new LightingColorFilter(0xffffff,0x000000);
mPaint.setColorFilter(lighting);
canvas.drawBitmap(mBitmap, 0,0, mPaint);

效果圖:

在這裏插入圖片描述

這個表示原圖效果,如果我們需要修改顏色,該如何修改?

比如說去掉紅色,根據註釋中的公式,只要將 0xffffff 改爲 0x00ffff 即可,效果如下:

在這裏插入圖片描述

如果要讓綠色更亮,又該怎麼辦,看到公式右邊有個 add 的參數了嗎?,修改
0x000000 爲 0x003000 ,再來看一下效果圖:

在這裏插入圖片描述

2. PorterDuffColorFilter
  • 使用一個指定的顏色和一種指定的 PorterDuff.Mode 來與繪製對象進行合成。

這個比較簡單,看一下代碼就懂:

PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DARKEN);
mPaint.setColorFilter(porterDuffColorFilter);
canvas.drawBitmap(mBitmap, 100, 0, mPaint);

效果圖:

在這裏插入圖片描述

3. ColorMatrixColorFilter

這個就比較厲害了,這是一個 4x5 的矩陣,直接看代碼吧:

float[] colorMatrix = {
                1,0,0,0,0,   //red
                0,1,0,0,0,   //green
                0,0,1,0,0,   //blue
                0,0,0,1,0    //alpha
        };
mColorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
mPaint.setColorFilter(mColorMatrixColorFilter);
canvas.drawBitmap(mBitmap, 100, 0, mPaint);

這個顯示原圖,如何修改,只需要修改其中的表示 1 的值,如果紅色要加強,就將表示紅色的 1 改爲更高的數字,去掉紅色則將 1 改爲 0 就行了,這個就不想演示了。

其實還有另外一種寫法,如下:

ColorMatrix cm = new ColorMatrix();
mColorMatrixColorFilter = new ColorMatrixColorFilter(cm);
mPaint.setColorFilter(mColorMatrixColorFilter);
canvas.drawBitmap(mBitmap, 100, 0, mPaint);

這個也是顯示原圖的,其中調節亮度,調用這個方法:

//什麼都不改
cm.setScale(1,1,1,1);
//綠色加強
cm.setScale(1,2,1,1);

它還有一個增強飽和度的方法:

//飽和度調節0-無色彩, 1- 默認效果, >1飽和度加強
cm.setSaturation(2);

顯示效果如下:

在這裏插入圖片描述



好了,關於 Paint 的相關 api 基本就介紹到位了,希望各位看官有收穫。



最後

如果你認可我的文章,請小手抖兩下,點贊 + 收藏 。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章