Canvas&Paint 知識梳理(5) Paint#setShader

1、概述

Shader稱爲着色器,經過給Paint設置Shader,咱們能夠對圖像進行渲染,在實際的使用當中,咱們通常使用Shader的如下五個子類來實現不一樣的效果:canvas

  • BitmapShader
  • LinearGradient
  • SweepGradient
  • RadialGradient
  • ComposeShader

其中第1個用來設置Bitmap的變換,第2~4用來設置顏色的變換,第5個用來組合上面的幾個Shader,下面咱們一塊兒來看如下各個子類的使用和應用場景。數組

2、使用示例

2.1 BitmapShader

BitmapShader是全部五個子類當中惟一一個對Bitmap進行操做的,咱們看一下它的構造函數:bash

/**
     * Call this to create a new shader that will draw with a bitmap.
     *
     * @param bitmap            The bitmap to use inside the shader
     * @param tileX             The tiling mode for x to draw the bitmap in.
     * @param tileY             The tiling mode for y to draw the bitmap in.
     */
    public BitmapShader(@NonNull Bitmap bitmap, TileMode tileX, TileMode tileY) {
        mBitmap = bitmap;
        mTileX = tileX;
        mTileY = tileY;
        init(nativeCreate(bitmap, tileX.nativeInt, tileY.nativeInt));
    }
複製代碼

第一個參數很好理解,就是須要繪製的Bitmap,咱們看一下後面的兩個參數,它的取值有:app

public enum TileMode {
        /**
         * replicate the edge color if the shader draws outside of its
         * original bounds
         */
        CLAMP   (0),
        /**
         * repeat the shader's image horizontally and vertically */ REPEAT (1), /** * repeat the shader's image horizontally and vertically, alternating
         * mirror images so that adjacent images always seam
         */
        MIRROR  (2);
    
        TileMode(int nativeInt) {
            this.nativeInt = nativeInt;
        }
        final int nativeInt;
    }
複製代碼

須要注意的是,下面幾種模式都是創建在繪製的區域要比原來的bimtap大的狀況下的。ide

the shader draws outside of its original bounds
複製代碼
  • CLAMP:取bitmap邊緣的最後一個像素進行擴展。
  • REPEAT:水平地重複整張bitmap
  • MIRROR:和REPEAT相似,可是每次重複的時候,將bitmap進行翻轉。

2.1.1 CLAMP

首先,咱們取一張寬高爲200dp * 200dp的圖片,咱們整個View的寬高爲300dp * 300dp函數

咱們首先採用 CLAMP的模式:

private Bitmap mOriginalBitmap;
    private Paint mPaint;

    private void init() {
        mOriginalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shader_pic);
        mPaint = new Paint();
    }

    private void drawBitmapShader(Canvas canvas) {
        BitmapShader shader = new BitmapShader(mOriginalBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mPaint.setShader(shader);
        canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mPaint);
    }
複製代碼

最終獲得的結果爲下圖,能夠看到,因爲Paint繪製的寬高要比Bitmap本來的寬高大,所以對於多出的部分,取了邊緣最後一個像素的顏色進行重複: 學習

如今有個疑問,由於整個圖片的大小爲 600 * 600,而咱們繪製的大小爲 900 * 900,按前面的說法,對於 (600,0) - (899, 600)的區域,取的是 (599, 0) - (599, 599)這一列的顏色,而對於 (0, 600) - (600, 899)取的是 (0, 599) - (599, 599)這一行的顏色,那麼 (600, 600) - (899, 899)這一區域是怎麼取的呢? 如今,咱們試一下,把最後 drawRect的起始點改成 (100, 100)

canvas.drawRect(100, 100, canvas.getWidth(), canvas.getHeight(), mPaint);
複製代碼

獲得的效果以下圖,能夠看到,邊緣部分被切割掉了。 ui

2.1.2 REPEAT/MIRROR

對於這兩種模式,實現方式和上面相似,咱們就再也不重複描述了,只給出下面運行的結果:this

BitmapShader shader = new BitmapShader(mOriginalBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
複製代碼

BitmapShader shader = new BitmapShader(mOriginalBitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
複製代碼

獲得的結果都是和描述相符的。

2.1.3 當X軸和Y軸的TileMode不一樣時

上面討論的狀況,都是x軸和y軸的TileMode相同的狀況,如今,咱們來看一下,當二者不一樣時,會發生什麼狀況:spa

BitmapShader shader = new BitmapShader(mOriginalBitmap, Shader.TileMode.CLAMP, Shader.TileMode.MIRROR);
複製代碼

最終的結果以下,能夠看到,咱們是先按x軸的模式進行處理,而後將x軸處理完畢後的圖像再按y軸的模式進行處理,這也解釋了咱們前面在2.1.1中留下的疑問。

2.2 LinearGradient

LinearGradient用來處理線性漸變,同理咱們先來看它的構造函數說明,和前面不一樣,它有兩個構造函數,其中一種是另外一種的簡化版,咱們直接來看複雜的一種:

/** Create a shader that draws a linear gradient along a line.
        @param x0           The x-coordinate for the start of the gradient line
        @param y0           The y-coordinate for the start of the gradient line
        @param x1           The x-coordinate for the end of the gradient line
        @param y1           The y-coordinate for the end of the gradient line
        @param  colors      The colors to be distributed along the gradient line
        @param  positions   May be null. The relative positions [0..1] of
                            each corresponding color in the colors array. If this is null,
                            the the colors are distributed evenly along the gradient line.
        @param  tile        The Shader tiling mode
    */
    public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[], TileMode tile) {
複製代碼

下面,咱們從幾個方面來分析一下這個構造函數中的參數。

2.2.1 起點座標和終點座標

對於這兩個點的座標咱們能夠這麼理解,起點的顏色就是color[]數組的第一個元素,終點的顏色就是color[]數組的最後一個元素,這兩個點的連線決定了線性變化的方向,若是兩點連線和x軸的正方向是重合的時候,那麼就是水平地變化,當和x軸正方向有度數時,那麼這個連線相對於x軸旋轉了多少,最後線性變化的圖像也就會相對於水平變化的圖像旋轉了多少,下面咱們用兩個例子來講明。 首先是水平方向的:

private void drawLinearGradient(Canvas canvas) {
        LinearGradient gradient = new LinearGradient(0, 0, 100, 0, new int[]{ Color.WHITE, Color.BLACK }, null, Shader.TileMode.REPEAT);
        mPaint.setShader(gradient);
        canvas.drawRect(0, 0, 900, 900, mPaint);
    }
複製代碼

這時候的圖像爲:

下面,咱們將終點的 y軸座標下移一點,讓起點座標和終點座標的連線,與 x軸造成必定的角度:

private void drawLinearGradient(Canvas canvas) {
        LinearGradient gradient = new LinearGradient(0, 0, 100, 10, new int[]{ Color.WHITE, Color.BLACK }, null, Shader.TileMode.REPEAT);
        mPaint.setShader(gradient);
        canvas.drawRect(0, 0, 900, 900, mPaint);
    }
複製代碼

這時候的圖像爲,能夠看到,因爲此時連線相對於x軸,順時針旋轉了必定的度數,那麼最終的圖像也相對於上面那種狀況順時針旋轉了相應的度數。

2.2.2 colorspositions

這兩個參數很好理解,由於在顏色由起點顏色變到終點顏色的過程當中,咱們可能還但願中間會通過別的顏色,那麼這時候,咱們就能夠在數組的第一個和最後一個元素當中插入別的元素,這些元素就是中間會通過的顏色,而且當positions不爲null的時候,colors的大小要和positions相同。

private void drawLinearGradient(Canvas canvas) {
        LinearGradient gradient = new LinearGradient(0, 0, 100, 10, new int[]{ Color.WHITE, Color.BLUE, Color.BLACK }, new float[]{0, 0.5f, 1f}, Shader.TileMode.REPEAT);
        mPaint.setShader(gradient);
        canvas.drawRect(0, 0, 900, 900, mPaint);
    }
複製代碼

結果爲:

2.2.3 TileMode

BitmapShader不一樣,此時咱們只用指定一個方向的變化,這個方向就是顏色線性變化對應的方向。

2.2.4 另外一個構造函數

/** Create a shader that draws a linear gradient along a line.
        @param x0       The x-coordinate for the start of the gradient line
        @param y0       The y-coordinate for the start of the gradient line
        @param x1       The x-coordinate for the end of the gradient line
        @param y1       The y-coordinate for the end of the gradient line
        @param  color0  The color at the start of the gradient line.
        @param  color1  The color at the end of the gradient line.
        @param  tile    The Shader tiling mode
    */
    public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
            TileMode tile) {
        mType = TYPE_COLOR_START_AND_COLOR_END;
        mX0 = x0;
        mY0 = y0;
        mX1 = x1;
        mY1 = y1;
        mColor0 = color0;
        mColor1 = color1;
        mTileMode = tile;
        init(nativeCreate2(x0, y0, x1, y1, color0, color1, tile.nativeInt));
    }
複製代碼

惟一不一樣的就是去掉了colorsposition數組,變成了color0color1,那麼咱們就只能指定起點和終點的顏色了,其它的原理和上面那個構造函數是相同的。

2.3 SweepGradient

它用來提供相似雷達的效果,同理,咱們看一下構造函數:

/**
     * A subclass of Shader that draws a sweep gradient around a center point.
     *
     * @param cx       The x-coordinate of the center
     * @param cy       The y-coordinate of the center
     * @param colors   The colors to be distributed between around the center.
     *                 There must be at least 2 colors in the array.
     * @param positions May be NULL. The relative position of
     *                 each corresponding color in the colors array, beginning
     *                 with 0 and ending with 1.0. If the values are not
     *                 monotonic, the drawing may produce unexpected results.
     *                 If positions is NULL, then the colors are automatically
     *                 spaced evenly.
     */
    public SweepGradient(float cx, float cy, int colors[], float positions[]) 
複製代碼

2.3.1 中心點座標(cx, cy)

對於(cx, cy)中心點的座標,咱們能夠把它想象成一個時鐘的指針,這個指針開始時指向3點鐘方向,它初始的顏色就是起點顏色,那麼它會以此爲起點,順時針旋轉360度,在旋轉的過程當中,這個指針的顏色不斷變化,當旋轉到360度後,指針就變成了終點顏色,在旋轉過程當中,指針所造成的軌跡就是最終的圖像。

2.3.2 TileMode

須要注意到,它和LinearGradient不一樣的是,因爲指針是無限長的,因此造成的圖像在x軸和y軸所拼接成的區域是無限大的,所以也就不存在了TileMode這個參數的必要了。

2.3.3 colors[]positions[]

這兩個數組的做用和上面LinearGradient的兩個數組的做用是相同的,這裏就不重複說明了。

2.3.4 舉例

下面舉個簡單的例子:

private void drawSweepGradient(Canvas canvas) {
        SweepGradient gradient = new SweepGradient(450, 450, Color.WHITE, Color.BLACK);
        mPaint.setShader(gradient);
        canvas.drawRect(0, 0, 900, 900, mPaint);
    }
複製代碼

最後的結果爲:

2.3.5 另外一個構造函數

/**
     * A subclass of Shader that draws a sweep gradient around a center point.
     *
     * @param cx       The x-coordinate of the center
     * @param cy       The y-coordinate of the center
     * @param color0   The color to use at the start of the sweep
     * @param color1   The color to use at the end of the sweep
     */
    public SweepGradient(float cx, float cy, int color0, int color1) {
        mType = TYPE_COLOR_START_AND_COLOR_END;
        mCx = cx;
        mCy = cy;
        mColor0 = color0;
        mColor1 = color1;
        init(nativeCreate2(cx, cy, color0, color1));
    }
複製代碼

和前面LinearGradient中討論的同樣,color0color1就是colors[]positions[]的簡化版本。

2.4 RadialGradient

它被稱爲圓形漸變,構造函數以下:

/** Create a shader that draws a radial gradient given the center and radius.
        @param centerX  The x-coordinate of the center of the radius
        @param centerY  The y-coordinate of the center of the radius
        @param radius   Must be positive. The radius of the circle for this gradient.
        @param colors   The colors to be distributed between the center and edge of the circle
        @param stops    May be <code>null</code>. Valid values are between <code>0.0f</code> and
                        <code>1.0f</code>. The relative position of each corresponding color in
                        the colors array. If <code>null</code>, colors are distributed evenly
                        between the center and edge of the circle.
        @param tileMode The Shader tiling mode
    */
    public RadialGradient(float centerX, float centerY, float radius, @NonNull int colors[], @Nullable float stops[], @NonNull TileMode tileMode) 
複製代碼

2.4.1 原點座標(centerX, centerY)和半徑radius

對於圓形漸變,咱們能夠這麼理解,開始的時候,有一個半徑無限小的圓環位於(centerX, centerY),它的顏色就是起點顏色,以後它開始慢慢變大,直到變爲半徑是radius爲止,在此期間,圓環的顏色慢慢變爲終點顏色,在整個變化的過程當中,圓環所造成的軌跡就是最終的圖像。

2.4.2 TileMode

因爲在這種狀況下,圖像的大小是有限的,最大就是radius指定的範圍,所以對於超出範圍的圖像,咱們須要定義它的行爲,可是原理仍是和前面討論的TileMode的三種狀況同樣的。

2.4.3 colors[]stops[]

原理和上面討論的colors[]positions[]同樣。

2.4.4 示例

private void drawRadialGradient(Canvas canvas) {
        RadialGradient gradient = new RadialGradient(200, 200, 50, Color.BLUE, Color.RED, Shader.TileMode.REPEAT);
        mPaint.setShader(gradient);
        canvas.drawRect(0, 0, 900, 900, mPaint);
    }
複製代碼

最終的結果爲:

2.5 ComposeShader

上面,咱們已經學習了四種Shader的實現方式,可是有時候,咱們但願可以將它組合起來,ComposeShader就爲咱們提供了這種途徑,能夠組合兩種Shader的實現。

/** Create a new compose shader, given shaders A, B, and a combining mode.
        When the mode is applied, it will be given the result from shader A as its
        "dst", and the result from shader B as its "src".
        @param shaderA  The colors from this shader are seen as the "dst" by the mode
        @param shaderB  The colors from this shader are seen as the "src" by the mode
        @param mode     The mode that combines the colors from the two shaders. If mode
                        is null, then SRC_OVER is assumed.
    */
    public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)
複製代碼

這就涉及到以前咱們學過的PorterDuff.Mode,第一個Shader做爲DST,而第二個Shader做爲SRC,兩個組合的結果會根據Mode的不一樣而發生改變,下面咱們用一個簡單的例子,來看一下BitmapShaderRadialGradient的組合:

private void drawComposeShader(Canvas canvas) {
        BitmapShader bitmapShader = new BitmapShader(mOriginalBitmap, Shader.TileMode.CLAMP, Shader.TileMode.MIRROR);
        RadialGradient radialGradient = new RadialGradient(300, 300, 300, Color.TRANSPARENT, Color.WHITE, Shader.TileMode.CLAMP);
        ComposeShader composeShader = new ComposeShader(bitmapShader, radialGradient, PorterDuff.Mode.SRC_OVER);
        mPaint.setShader(composeShader);
        canvas.drawCircle(300, 300, 300, mPaint);
    }
複製代碼

最終的結果爲下圖,能夠看到,因爲咱們採用了SRC_OVER,所以就會出現朦朧的效果。

相關文章
相關標籤/搜索