Android著色器——Shader

鋸齒流沙發表於2018-01-08

簡介

Shader :顧名思義,也就是著色器,通常也有人叫做渲染器。我們看下Android原始碼中是如何介紹Shader的。

/**
 * Shader is the based class for objects that return horizontal spans of colors
 * during drawing. A subclass of Shader is installed in a Paint calling
 * paint.setShader(shader). After that any object (other than a bitmap) that is
 * drawn with that paint will get its color(s) from the shader.
 */
複製程式碼

這是Android原始碼的介紹,Shader 是一個基類物件,在繪製時會返回一個水平跨越的顏色物件,主要功能是在繪製時通過setShader方法設定著色器的子類物件之後,任何物件(除了點陣圖之外)都可從著色器中得到它的想要的顏色。

也就是說,Shader 只是一個基類,我們主要還是通過使用Shader的子類,來獲取我們想要的顏色。

那麼Shader 有那些子類呢?

其實Shader 總共有五個子類:

1)BimapShader:點陣圖的影象渲染器

2)LinearGradient:線性渲染器

3)RadialGradient:環形渲染器,一般的水波紋效果,充電水波紋擴散效果、調色盤都可以使用該渲染器實現。

4)SweepGradient:梯度渲染器(即掃描渲染),可以使用該渲染器實現,如:微信等雷達掃描效果、手機衛士垃圾掃描。

5)ComposeShader:組合渲染器

BimapShader

BitmapShader點陣圖的影象渲染器,使用時,需要建立BitmapShader物件,其建構函式如下:

/**
     * 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, @NonNull TileMode tileX, @NonNull TileMode tileY) {
        this(bitmap, tileX.nativeInt, tileY.nativeInt);
    }
複製程式碼

建立BitmapShader物件時,需要傳入Bitmap,那麼建構函式的後面兩個函式幹嘛用的呢?

tileX:X軸上繪製點陣圖的tiling模式。

tileY:Y軸上繪製點陣圖的tiling模式。

其中有三種tiling模式(TileMode):

1)TileMode.CLAMP:拉伸最後一個畫素去鋪滿剩下的地方;

2)TileMode.MIRROR:通過映象翻轉鋪滿剩下的地方;

3)TileMode.REPEAT:重複圖片平鋪整個畫面(如電腦設定桌布)。

接下來我們看下分別設定這幾種模式下的影象的效果。

TileMode.CLAMP

CLAMP模式
從上圖中可以看出該模式下,確實是只拉伸影象的最後一個畫素去鋪滿控制元件剩下的地方。

TileMode.MIRROR

MIRROR模式

這就是MIRROR模式下,通過映象翻轉鋪滿剩下的地方的效果圖。

TileMode.REPEAT

REPEAT模式

上圖就是REPEAT模式的效果,就是通過重複畫面來鋪滿整個畫面。

以上都是通過拉伸,或者重複畫面來鋪滿真個View,效果都不太好,寬/高不一致,有沒有辦法解決寬/高一致,然後讓bitmap鋪滿畫面呢?

答案當然是有的,可以通過以下方法來解決:

1)設定畫素矩陣,來調整大小,可以參考《Android BitmapShader 實戰 實現圓形、圓角圖片》這篇文章。

2)通過shapeDrawable

public class MyShaderView extends View {

    private Bitmap mBitmap;
    private Paint mPaint;
    private BitmapShader mBitmapShader;
    private int width;
    private int height;

    public MyShaderView(Context context) {
        super(context);
        mBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.animationtarget)).getBitmap();
        mPaint = new Paint();
        width = mBitmap.getWidth();
        height = mBitmap.getHeight();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);//畫布顏色
        mBitmapShader = new BitmapShader(mBitmap, TileMode.CLAMP, TileMode.CLAMP);
        ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
        shapeDrawable.getPaint().setShader(mBitmapShader);
        shapeDrawable.setBounds(0, 0, width, width);
        shapeDrawable.draw(canvas);
    }
}
複製程式碼

BitmapShader應用,製作放大鏡

public class ZoomImageView extends View {

    private Bitmap bitmap;
    private ShapeDrawable drawable;
    //放大倍數
    private static final int FACTOR = 3;
    //放大鏡的半徑
    private static final int RADIUS = 100;
    private Matrix matrix = new Matrix();

    public ZoomImageView(Context context) {
        super(context);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.animationtarget);
        Bitmap bmp = bitmap;
        //放大後的整個圖片
        bmp = Bitmap.createScaledBitmap(bmp, bmp.getWidth() * FACTOR, bmp.getHeight() * FACTOR, true);
        //製作一個圓形的圖片(放大的區域性),蓋在canvas上面
        BitmapShader shader = new BitmapShader(bmp, TileMode.CLAMP, TileMode.CLAMP);
        drawable = new ShapeDrawable(new OvalShape());
        drawable.getPaint().setShader(shader);
        //切出矩形區域---用於繪製圓(內切圓)
        drawable.setBounds(0, 0, RADIUS * 2, RADIUS * 2);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        canvas.drawBitmap(bitmap, 0, 0, null);
        //畫製作好的圓形圖片
        drawable.draw(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();

        matrix.setTranslate(RADIUS - x * FACTOR, RADIUS - y * FACTOR);
        drawable.getPaint().getShader().setLocalMatrix(matrix);

        drawable.setBounds(x - RADIUS, y - RADIUS, x + RADIUS, y + RADIUS);

        invalidate();
        return true;
    }

}
複製程式碼

放大鏡效果圖

上圖中使用放大鏡放大了男孩的頭部。

LinearGradient

LinearGradient也就是線性渲染器,也叫做線性漸變。 初始化時的構造方法:

    public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[], @Nullable float positions[], @NonNull TileMode tile) 
複製程式碼

x0、y0:渲染的起始點;

x1、y1:渲染的結束點;

colors: 渲染的顏色,也就是中間依次要出現的幾個顏色,它是一個顏色陣列,陣列長度必須大於等於2;

positions:陣列大小跟colors陣列一樣大,中間依次擺放的幾個顏色分別放置在那個位置上(參考比例從左往右);

tile:平鋪方式,有CLAMP、REPEAT和MIRROR這三種。

使用:

LinearGradient linearGradient = new LinearGradient(0, 0, 400, 400, colors, null, TileMode.REPEAT);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 400, 400, mPaint);
複製程式碼

接下來我們主要分析下TileMode的三種模式。

CLAMP

該模式是表示:重複最後一種顏色直到該View結束的地方

LinearGradient linearGradient = new LinearGradient(0, 0, getWidth()/2, 0, colors, null, TileMode.CLAMP);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 800, 400, mPaint);
複製程式碼

效果圖

上圖便是CLAMP模式的效果圖,從圖中可以看出重複了最後一種顏色(黃色),直到該View結束的地方。

CLAMP

該模式表示著色器在水平或者垂直方向上對控制元件進行重複著色。

LinearGradient linearGradient = new LinearGradient(0, 0, getWidth()/2, 0, colors, null, TileMode.REPEAT);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 800, 400, mPaint);
複製程式碼

CLAMP

上圖是REPEAT模式下的線性渲染器的效果圖,可以看到在水平方向上是重複著色的。

MIRROR

該模式表示:在水平方向或者垂直方向上以映象的方式進行渲染,這種渲染方式的一個特徵就是具有翻轉的效果。

LinearGradient linearGradient = new LinearGradient(0, 0, getWidth()/2, 0, colors, null, TileMode.MIRROR);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 800, 400, mPaint);
複製程式碼

MIRROR

效果圖中的就是在水平方向上以映象方式進行翻轉的渲染效果。

LinearGradient 應用

public class LinearGradientTextView extends AppCompatTextView {
    private TextPaint paint;
    private LinearGradient linearGradient;
    private Matrix matrix;
    private float translateX;
    private float deltaX = 20;

    public LinearGradientTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        paint = getPaint();
        //GradientSize=兩個文字的大小
        String text = getText().toString();
        float textWidth = paint.measureText(text);
        int GradientSize =(int) (3*textWidth/text.length());
        linearGradient = new LinearGradient(-GradientSize, 0, 0, 0, new int[]{0x22ffffff,0xffffffff,0x22ffffff}, new float[]{0,0.5f,1}, Shader.TileMode.CLAMP);//邊緣融合
        paint.setShader(linearGradient);
        matrix = new Matrix();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        float textWidth = getPaint().measureText(getText().toString());
        translateX += deltaX;
        if(translateX > textWidth + 1|| translateX < 1){
            deltaX = -deltaX;
        }
        matrix.setTranslate(translateX, 0);
        linearGradient.setLocalMatrix(matrix);

        postInvalidateDelayed(50);
    }
}
複製程式碼

xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#ff000000"
    tools:context="com.main.shader.ShaderActivity">

    <com.main.shader.LinearGradientTextView
        android:id="@+id/linearGradientTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="走的最慢的人,只要他不喪失目標,也比漫無目的徘徊的人走得快。"
        android:textColor="#ff000000"/>

</LinearLayout>

複製程式碼

效果圖

RadialGradient

RadialGradient:環形渲染器,呈現一般的水波紋渲染效果。

使用:

RadialGradient radialGradient = new RadialGradient(300, 300, 100, colors, null, TileMode.REPEAT);
mPaint.setShader(radialGradient);
canvas.drawCircle(300, 300, 300, mPaint);
複製程式碼

效果圖

RadialGradient的構造方法

RadialGradient(float centerX, float centerY, float radius,@NonNull @ColorInt int colors[], @Nullable float stops[],@NonNull TileMode tileMode)
複製程式碼

centerX、centerY:環形中心點座標;

radius:環形半徑;

colors:渲染的顏色;

stops:中間依次擺放的顏色分別放置的位置;

TileMode :平鋪模式。

在RadialGradient 中同樣提供了三種TileMode平鋪模式:

1)CLAMP

2)REPEAT

3)MIRROR

這三種模式呈現的效果對應LinearGradient的TileMode的三種模式呈現效果。該渲染其的應用,可以參考《自定義控制元件三部曲之繪圖篇(二十)——RadialGradient與水波紋按鈕效果》這篇文章。

SweepGradient

掃描渲染器,使用:

SweepGradient sweepGradient = new SweepGradient(300, 300, colors, null);
mPaint.setShader(sweepGradient);
canvas.drawCircle(300, 300, 300, mPaint);
複製程式碼

效果圖

我們看下SweepGradient的構造方法:

public SweepGradient(float cx, float cy,@NonNull @ColorInt int colors[], @Nullable float positions[])
複製程式碼

引數解析:

cx、cy:掃描中心點座標;

colors:顏色梯度,分佈在中心點周圍,至少提供兩種顏色;

positions:依次擺放的顏色分別放置的位置;

關於掃描渲染的應用,網上很多優秀的demo,比如:自定義控制元件之圓形顏色漸變進度條--SweepGradient

ComposeShader

組合渲染器ComposeShader,顧名思義,可以組合多種渲染器。如下程式碼所示:

ComposeShader composeShader = new ComposeShader(linearGradient, mBitmapShader, PorterDuff.Mode.SRC_OVER);
mPaint.setShader(composeShader);
canvas.drawRect(0, 0, 800, 1000, mPaint);
複製程式碼

效果圖

構造方法的最後一個引數PorterDuff.Mode,也就是過度模式,總共有17種模式,關於這十七種模式,讀者可以參考《各個擊破搞明白PorterDuff.Mode》這篇文章,寫得很詳細,相信讀者讀完之後都會感到ComposeShader的強大之處。

相關文章