Android自定義View之圖片外形特效——輕鬆實現圓角和圓形圖片

xxq2dream發表於2018-08-07

Android自定義View系列

在日常開發過程中,圖片的特效處理是一個很常見的需求。除了顏色特效,還有就是外形的特效,比如圓角圖片,圓形圖片等。今天我們就來學習下圖片的外形特效相關的知識

Paint畫筆特效

Paint有一個專門用於處理圖片外形特效的API

//Paint.class
public Xfermode setXfermode(Xfermode xfermode) {
    long xfermodeNative = 0;
    if (xfermode != null)
        xfermodeNative = xfermode.native_instance;
    nSetXfermode(mNativePaint, xfermodeNative);
    mXfermode = xfermode;
    return xfermode;
}
複製程式碼

在Android的SDK中Xfermode只有一個子類:PorterDuffXfermode

PorterDuffXfermode控制影象的混合模式,影響的是2個圖層交集區域的顯示方式。

//PorterDuff.class
public enum Mode {
    /** [0, 0] */
    CLEAR       (0),
    /** [Sa, Sc] */
    SRC         (1),
    /** [Da, Dc] */
    DST         (2),
    /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
    SRC_OVER    (3),
    /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
    DST_OVER    (4),
    /** [Sa * Da, Sc * Da] */
    SRC_IN      (5),
    /** [Sa * Da, Sa * Dc] */
    DST_IN      (6),
    /** [Sa * (1 - Da), Sc * (1 - Da)] */
    SRC_OUT     (7),
    /** [Da * (1 - Sa), Dc * (1 - Sa)] */
    DST_OUT     (8),
    /** [Da, Sc * Da + (1 - Sa) * Dc] */
    SRC_ATOP    (9),
    /** [Sa, Sa * Dc + Sc * (1 - Da)] */
    DST_ATOP    (10),
    /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
    XOR         (11),
    /** [Sa + Da - Sa*Da,
         Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
    DARKEN      (16),
    /** [Sa + Da - Sa*Da,
         Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
    LIGHTEN     (17),
    /** [Sa * Da, Sc * Dc] */
    MULTIPLY    (13),
    /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
    SCREEN      (14),
    /** Saturate(S + D) */
    ADD         (12),
    OVERLAY     (15);

    Mode(int nativeInt) {
        this.nativeInt = nativeInt;
    }

    /**
     * @hide
     */
    public final int nativeInt;
}
複製程式碼

這麼多種,分別是什麼效果呢?看看下面的效果圖就明白了。

各種影象疊加Mode的效果示意圖

其中需要說明的是Dst表示底層圖層,也可以理解為遮罩層,而Src則是原圖

實現圓角圖片效果

原理很簡單,就是通過一個和圖片大小一樣的帶有圓角的圖層和圖片圖層疊加起來 (1)初始化畫布Canvas和畫筆Paint

Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
複製程式碼

(2)在畫布上畫一個帶圓角的圖層,圖層尺寸和原圖一樣

//這裡80為圓角的半徑
canvas.drawRoundRect(new RectF(0, 0, src.getWidth(), src.getHeight()),80,80, paint);
複製程式碼

(3)為Paint設定圖形混合模式SRC_IN,SRC_IN模式從上面的模式效果圖中我們可以看出疊加後顯示的是重疊部分後面一張圖的部分

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
複製程式碼

(4)繪製圖片,並把新的Bitmap物件設定給ImageView

canvas.drawBitmap(src, 0, 0, paint);
imageView.setImageBitmap(bitmap)
複製程式碼

通過以上的簡單幾步,我們就得到了一個圓角圖片的效果。

圓角圖片效果

上面我們採用的混合顯示模式是SRC_IN,如果我們採用的是SRC_OUT呢?我們得到的是什麼效果?

如果你理解了上面效果示意圖所展示的效果,我想這個問題應該不難。SRC_OUT模式就是原圖去掉疊加的部分,在我們的例子中也就是四個圓角了。

SRC_OUT模式效果

其他的模式如果不太理解,大家可以按照上面的例子試試,看看實際的效果,可以加深理解。

總結
  • 通過Paint畫筆實現圖形特效的關鍵在於為setXfermode()方法設定合適的疊加效果
  • 而要理解疊加效果就需要明白在Paint的setXfermode()方法之前canvas畫的對應的是Mode效果示意圖中的Dst圖層,在Paint的setXfermode()方法之後canvas畫的圖則對應的是Mode效果示意圖中的Src圖層

Shader

Shader又被稱之為著色器、渲染器,主要用來實現一系列的漸變、渲染效果。Android中一共有5種Shader,分別是BitmapShader(點陣圖Shder)、LinearGradient(線性Shader)、RadialGradient(光束Shader)、SweepGradient(梯度Shader)、ComposeShader(混合Shader)

此外,Shader還提供了幾種填充的模式

//Shader.class
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;
}
複製程式碼
  • CLAMP:拉伸,最常用的一種模式
  • REPEAT:橫向縱向不斷重複
  • MIRROR:橫向不斷翻轉重複、縱向不斷翻轉重複
BitmapShader(點陣圖Shder)

BitmapShader(點陣圖Shder)跟其他4種Shader不一樣,它產生的是一個影象,有點類似PS中的影象填充漸變。它的作用就是通過Paint對畫布進行指定的Bitmap填充。

BitmapShader一種常用的情景就是生成圓形圖片

利用BitmapShader生成圓形圖片

原理就是把要繪製的圖片利用BitmapShader填充到圓形的畫布上

(1)初始化畫布Canvas和畫筆Paint

Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
複製程式碼

(2)利用Bitmap生成BitmapShader,模式為拉伸模式

BitmapShader shader = new BitmapShader(src, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
複製程式碼

(3)將BitmapShader設定給Paint,便於後面繪製

paint.setShader(shader);
複製程式碼

(4)利用設定好的Paint,在圓形的畫布上填充圖片,也就是Canvas要呼叫drawCircle方法

//這裡圓形的半徑我們用圖片邊長的一半
float radius = Math.min(src.getWidth()/2, src.getHeight()/2);
//這裡圓形的圓點我們設在原圖的中間
canvas.drawCircle(src.getWidth()/2, src.getHeight()/2, radius, paint);
複製程式碼

通過以上簡單的幾步,我們就獲得了圖片的圓形效果

圓形圖片

圖裡面上面的圖是我們畫出來的圓形圖,下面的是開源庫CircleImageView實現的圓形圖,可見效果是一樣的,難道CircleImageView也是用這個原理實現的?讓我們一探究竟。

public class CircleImageView extends ImageView {
    ...
    
    @Override
    protected void onDraw(Canvas canvas) {
        if (mDisableCircularTransformation) {
            super.onDraw(canvas);
            return;
        }

        if (mBitmap == null) {
            return;
        }

        if (mCircleBackgroundColor != Color.TRANSPARENT) {
            canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mCircleBackgroundPaint);
        }
        //這裡就是繪製圓形圖的地方
        canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
        if (mBorderWidth > 0) {
            canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
        }
    }
    
    private void setup() {
        ...

        //找到了,可見所採用的方法也是BitmapShader
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setShader(mBitmapShader);

       ...
    }
    
    ...
}
複製程式碼

從上面CircleImageView的原始碼中我們可以看出,CircleImageView也是通過BitmapShader來實現圖片的圓形特效的。有興趣的朋友可以找來研究一下。

其他的填充漸變效果從字面意思就能看出來,大家有興趣可以自己寫個例子看看效果。

總結

  • 圖形的外形特效主要是用到了Paint的setXfermode方法和setShader方法
  • setXfermode方法是通過設定不同的圖層疊加模式來控制圖形疊加後的效果,而setShader方法則是利用了類似PS中的影象填充漸變的理念為Paint設定了不同的填充漸變效果
  • 利用BitmapShader可以很方便地得到圓形的圖片效果

歡迎關注我的微信公眾號,和我一起每天進步一點點!

AntDream

相關文章