Android View部分消失效果實現

本文來自網易雲社區html

做者:孫有軍android


老需求

咱們常常會有需求就是View消失的效果,這裏咱們說的消失每每是所有消失,咱們可能採用一個alpha動畫,在指定的時間內消失掉View,出現則實現相反的動畫。咱們通常都採用以下的實現:canvas

採用tween動畫實現:app

private void alphaTween() {
    AlphaAnimation alpha = new AlphaAnimation(1.0f, 0.0f);
    alpha.setDuration(300);
    imageView.startAnimation(alpha);
}

或者採用屬性動畫實現:ide

private void alphaOB() {
    ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1.0f, 0.0f).setDuration(300);
    animator.start();
}

也可能採用xml來實現。函數


新需求

可是這裏咱們須要的不是上面的效果,你是在逗我??不是上面的效果,你說這麼多。咱們需求以下,這裏咱們用兩個圖來展現:動畫

左邊是原始圖片,右邊是處理後的圖片。能夠看到從下到上愈來愈淡,頂部就已經像消失了同樣。說道這裏不少人確定會聯想到圖片的濾鏡效果。可是這裏實現的方式簡單的多。this


實現

這裏咱們寫一個demo來實現這個效果。既然是在對View進行處理,那這裏咱們就先對一張圖片進行處理。以後在擴展。rest

既然咱們須要將圖片變淡消失,確定是須要合成了什麼效果。那Android裏面通常合成咱們都採用什麼方式吶?code


Xfermode

Android裏面咱們能夠採用Xfermode來實現圖片的合成,好比咱們能夠實現各類各樣的頭像,例如圓形頭像。那這裏咱們須要採用哪一種mode?

這裏先draw是dest,後draw是src,咱們須要的是將dest露出,同時將src產生的效果合成到dest上,那這裏咱們須要用的是DST_IN效果。

上面圖示表示一方是全透明的,其實還須要結合Mode定義來徹底理解該效果,Mode的計算方式以下:

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;
}

從效果圖上咱們能夠看到,圖片消失的效果,不是整塊消失,而是部分效果,就是效果是有一個漸變的過程。那這裏咱們須要合成的效果應該是一個漸變效果的圖片。好比從全透明到全不透明。


代碼實現

咱們已經分析了所有須要的效果,那就最終寫代碼來看看最後的效果。

public class FadingPic extends View {    private Paint paint;    private Bitmap bitmap;    private int height;    private int width;    public FadingPic(Context context) {        super(context);
        init(context, null);
    }    public FadingPic(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);
        init(context, attrs);
    }    public FadingPic(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }    private void init(Context context, AttributeSet attrs) {
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.beautify);
        width = bitmap.getWidth();
        height = bitmap.getHeight();
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        paint.setShader(new LinearGradient(0, 0, 0, height, 0x00000000, 0xff000000, Shader.TileMode.CLAMP));
    }    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(2 * width, height);
    }    @Override
    protected void onDraw(Canvas c) {        super.onDraw(c);
        c.drawBitmap(bitmap, 0, 0, null);// 原始圖片
        c.saveLayer(width, 0, width * 2, height, null, Canvas.ALL_SAVE_FLAG);
        c.drawBitmap(bitmap, width, 0, null);
        c.drawRect(width, 0, width * 2, height, paint);
        c.restore();
    }
}

咱們直接操做了本地的一張圖片。先draw出原圖,在draw出合成後效果圖。

1,定義了Xfermode爲PorterDuff.Mode.DST_IN 2,給paint設置一個線性漸變的shader,透明圖從全透明到全不透明,平鋪模式爲CLAMP 3,這裏必定要在新的Layer中進行操做,不然只是疊加效果


實現2

上面咱們採用在新的Layer進行操做,若是不在新的Layer只是疊加效果,那不採用Layer是否有方式能夠實現?固然是能夠的。這裏咱們改變一下當前的代碼再新建立一個bitmap:

package com.demo.opengl.widget;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.LinearGradient;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.Shader;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.view.View;import com.demo.opengl.R;/**
 * Created by hzsunyj on 2017/8/9.
 */public class FadingPic extends View {    private Paint paint;    private Bitmap bitmap;    private Bitmap wapperBitmap;    private int height;    private int width;    private Canvas canvas;    public FadingPic(Context context) {        super(context);
        init(context, null);
    }    public FadingPic(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);
        init(context, attrs);
    }    public FadingPic(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }    private void init(Context context, AttributeSet attrs) {
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.beautify);
        width = bitmap.getWidth();
        height = bitmap.getHeight();
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        paint.setShader(new LinearGradient(0, 0, 0, height, 0x00000000, 0xff000000, Shader.TileMode.CLAMP));        // new 
        wapperBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        canvas = new Canvas(wapperBitmap);
    }    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(2 * width, height);
    }    @Override
    protected void onDraw(Canvas c) {        super.onDraw(c);
        c.drawBitmap(bitmap, 0, 0, null);// 原始圖片
        //        c.saveLayer(width, 0, width * 2, height, null, Canvas.ALL_SAVE_FLAG);
        //        c.drawBitmap(bitmap, width, 0, null);
        //        c.drawRect(width, 0, width * 2, height, paint);
        //        c.restore();

        canvas.drawBitmap(bitmap, 0, 0, null);
        canvas.drawRect(0, 0, width, height, paint);
        c.drawBitmap(wapperBitmap, width, 0, null);
    }
}

這裏大部分代碼是相同的,只是現將效果合成到一個新的bitmap,最後再將bitmap draw到屏幕上。


有什麼用?

到這裏也許你會問,這個需求沒什麼用啊!哪有這樣需求。那這裏咱們就舉個栗子。好比咱們有一個列表頁,當最上的條目滾動消失的時候,不是硬生生的消失,而是比較天然的消失。

好比上面,咱們向上滾動頂部就有一個明顯的被切掉的效果。不太友好。那咱們能夠處理成以下效果: 

頂部一個漸變消失的效果。不會顯的太突兀。


實現

以前咱們實現了圖片的部分消失效果,那這裏咱們怎麼處理,好比這個RecyclerView,是針對每一行來進行處理?這樣是否是很麻煩,又這種bind狀態,還有各類重用,感受問題比較多。前面的圖片的方式根本不能重用啊!!

那這裏咱們怎麼實現吶?咱們是否能夠不對item實現效果,而是針對整個RecyclerView來實現?

public class FadingRecyclerView extends RecyclerView {    private Paint paint;    private int height;    private int width;    public FadingRecyclerView(Context context) {        super(context);
        init(context, null);
    }    public FadingRecyclerView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);
        init(context, attrs);
    }    public FadingRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);
        init(context, attrs);
    }    private void init(Context context, AttributeSet attrs) {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        paint.setShader(new LinearGradient(0, 0, 0, 160, 0x00000000, 0xff000000, Shader.TileMode.CLAMP));
    }    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);
        height = h;
        width = w;
    }    @Override
    public void draw(Canvas c) {
        c.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);        super.draw(c);
        c.drawRect(0, 0, width, 160, paint);
        c.restore();
    }

}

咱們重寫了RecyclerView,在draw函數中合成了效果,這裏RecyclerView是一個ViewGroup,須要複寫draw函數,而不是onDraw。


副產物

前面咱們說過,若是不在新的Layer裏面合成只會產生疊加效果,那這個疊加效果有什麼用吶? 咱們能夠來實現倒影效果,他也是階梯漸變的,那這裏咱們就來實現一下倒影效果。主要的區別爲是否在新的layer裏面實現。

public class FadingPic extends View {    private Paint paint;    private Paint paint1;    private Bitmap bitmap;    private Bitmap rotateBitmap;    private int height;    private int width;    public FadingPic(Context context) {        super(context);
        init(context, null);
    }    public FadingPic(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);
        init(context, attrs);
    }    public FadingPic(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }    private void init(Context context, AttributeSet attrs) {
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.beautify);
        width = bitmap.getWidth();
        height = bitmap.getHeight();
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        paint.setShader(new LinearGradient(0, 0, 0, height, 0x00000000, 0xff000000, Shader.TileMode.CLAMP));

        Matrix matrix = new Matrix();
        matrix.setScale(1, -1);        //matrix.setRotate(180); 不能造成鏡像效果
        rotateBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);

        paint1 = new Paint();
        paint1.setAntiAlias(true);
        paint1.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        paint1.setShader(new LinearGradient(0, height, 0, 2 * height, 0xff000000, 0x00000000, Shader.TileMode.CLAMP));
    }    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(2 * width, 2 * height);
    }    @Override
    protected void onDraw(Canvas c) {        super.onDraw(c);
        c.drawBitmap(bitmap, 0, 0, null);// 原始圖片
        c.saveLayer(width, 0, width * 2, height, null, Canvas.ALL_SAVE_FLAG);
        c.drawBitmap(bitmap, width, 0, null);
        c.drawRect(width, 0, width * 2, height, paint);
        c.restore();        // 倒影
        c.drawBitmap(rotateBitmap, 0, height, null);
        c.drawRect(0, height, width, 2 * height, paint1);
    }
}

這裏能夠看到主要是將圖片反轉,注意這裏不能採用旋轉,旋轉不會產生鏡像效果。其餘的都與以前的代碼沒有區別。那最終咱們來看看實現的效果:


Done

若是有人有更簡單的方式,能夠探討探討。


 

網易雲免費體驗館,0成本體驗20+款雲產品! 

更多網易研發、產品、運營經驗分享請訪問網易雲社區



相關文章:
【推薦】 聊聊WS-Federation
【推薦】 深刻淺出「跨視圖粒度計算」--二、INCLUDE表達式
【推薦】 3分鐘掌握一個有數小技能:製做動態標題

相關文章
相關標籤/搜索