Android View部分消失效果實現
本文來自網易雲社群
作者:孫有軍
老需求
我們經常會有需求就是View消失的效果,這裡我們說的消失往往是全部消失,我們可能採用一個alpha動畫,在指定的時間內消失掉View,出現則實現相反的動畫。我們一般都採用如下的實現:
採用tween動畫實現:
private void alphaTween() { AlphaAnimation alpha = new AlphaAnimation(1.0f, 0.0f); alpha.setDuration(300); imageView.startAnimation(alpha); }
或者採用屬性動畫實現:
private void alphaOB() { ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1.0f, 0.0f).setDuration(300); animator.start(); }
也可能採用xml來實現。
新需求
但是這裡我們需要的不是上面的效果,你是在逗我??不是上面的效果,你說這麼多。我們需求如下,這裡我們用兩個圖來展示:
左邊是原始圖片,右邊是處理後的圖片。可以看到從下到上越來越淡,頂部就已經像消失了一樣。說道這裡很多人肯定會聯想到圖片的濾鏡效果。但是這裡實現的方式簡單的多。
實現
這裡我們寫一個demo來實現這個效果。既然是在對View進行處理,那這裡我們就先對一張圖片進行處理。之後在擴充套件。
既然我們需要將圖片變淡消失,肯定是需要合成了什麼效果。那Android裡面一般合成我們都採用什麼方式吶?
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+款雲產品!
更多網易研發、產品、運營經驗分享請訪問網易雲社群