Shader
稱爲着色器,經過給Paint
設置Shader
,咱們能夠對圖像進行渲染,在實際的使用當中,咱們通常使用Shader
的如下五個子類來實現不一樣的效果:canvas
BitmapShader
LinearGradient
SweepGradient
RadialGradient
ComposeShader
其中第1
個用來設置Bitmap
的變換,第2~4
用來設置顏色的變換,第5
個用來組合上面的幾個Shader
,下面咱們一塊兒來看如下各個子類的使用和應用場景。數組
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
進行翻轉。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
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);
複製代碼
獲得的結果都是和描述相符的。
X
軸和Y
軸的TileMode
不一樣時上面討論的狀況,都是x
軸和y
軸的TileMode
相同的狀況,如今,咱們來看一下,當二者不一樣時,會發生什麼狀況:spa
BitmapShader shader = new BitmapShader(mOriginalBitmap, Shader.TileMode.CLAMP, Shader.TileMode.MIRROR);
複製代碼
最終的結果以下,能夠看到,咱們是先按x
軸的模式進行處理,而後將x
軸處理完畢後的圖像再按y
軸的模式進行處理,這也解釋了咱們前面在2.1.1
中留下的疑問。
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) {
複製代碼
下面,咱們從幾個方面來分析一下這個構造函數中的參數。
對於這兩個點的座標咱們能夠這麼理解,起點的顏色就是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
軸,順時針旋轉了必定的度數,那麼最終的圖像也相對於上面那種狀況順時針旋轉了相應的度數。
colors
和positions
這兩個參數很好理解,由於在顏色由起點顏色變到終點顏色的過程當中,咱們可能還但願中間會通過別的顏色,那麼這時候,咱們就能夠在數組的第一個和最後一個元素當中插入別的元素,這些元素就是中間會通過的顏色,而且當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);
}
複製代碼
結果爲:
TileMode
和BitmapShader
不一樣,此時咱們只用指定一個方向的變化,這個方向就是顏色線性變化對應的方向。
/** 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));
}
複製代碼
惟一不一樣的就是去掉了colors
和position
數組,變成了color0
和color1
,那麼咱們就只能指定起點和終點的顏色了,其它的原理和上面那個構造函數是相同的。
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[])
複製代碼
(cx, cy)
對於(cx, cy)
中心點的座標,咱們能夠把它想象成一個時鐘的指針,這個指針開始時指向3
點鐘方向,它初始的顏色就是起點顏色,那麼它會以此爲起點,順時針旋轉360
度,在旋轉的過程當中,這個指針的顏色不斷變化,當旋轉到360
度後,指針就變成了終點顏色,在旋轉過程當中,指針所造成的軌跡就是最終的圖像。
TileMode
須要注意到,它和LinearGradient
不一樣的是,因爲指針是無限長的,因此造成的圖像在x
軸和y
軸所拼接成的區域是無限大的,所以也就不存在了TileMode
這個參數的必要了。
colors[]
和positions[]
這兩個數組的做用和上面LinearGradient
的兩個數組的做用是相同的,這裏就不重複說明了。
下面舉個簡單的例子:
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);
}
複製代碼
最後的結果爲:
/**
* 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
中討論的同樣,color0
和color1
就是colors[]
和positions[]
的簡化版本。
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)
複製代碼
(centerX, centerY)
和半徑radius
對於圓形漸變,咱們能夠這麼理解,開始的時候,有一個半徑無限小的圓環位於(centerX, centerY)
,它的顏色就是起點顏色,以後它開始慢慢變大,直到變爲半徑是radius
爲止,在此期間,圓環的顏色慢慢變爲終點顏色,在整個變化的過程當中,圓環所造成的軌跡就是最終的圖像。
TileMode
因爲在這種狀況下,圖像的大小是有限的,最大就是radius
指定的範圍,所以對於超出範圍的圖像,咱們須要定義它的行爲,可是原理仍是和前面討論的TileMode
的三種狀況同樣的。
colors[]
和stops[]
原理和上面討論的colors[]
和positions[]
同樣。
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);
}
複製代碼
最終的結果爲:
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
的不一樣而發生改變,下面咱們用一個簡單的例子,來看一下BitmapShader
和RadialGradient
的組合:
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
,所以就會出現朦朧的效果。