一、應用場景
1、 先上效果圖:

效果圖
2、 應用場景分析:
- 適用于
Activity界面跳轉 - 適用于
View的切換 - 支持所有
View布局的動畫效果
3、代碼使用(非常簡潔好用):
- 在你需要做動畫的布局上,套上
RevealAnimationLayout就可以了,支持套任何布局!??!
<com.revealanimation.RevealAnimationLayout
android:id="@+id/animat_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:scaleType="centerCrop"
android:src="@mipmap/test"/>
</com.revealanimation.RevealAnimationLayout>
// java 代碼中,調(diào)用
mClipAnimationLayout.startAnimal(RevealAnimationLayout.AnimaType.Circle);
二、流程分析
1、android 5.0 其實已經(jīng)擁有對應的接口 ViewAnimationUtils.createCircularReveal ,然而還是有一定的局限性:
- 只能再
android 5.0上使用 - 只提供圓形的揭露效果
- 也沒有提供拓展其他圖形接口
2、對于這個效果,我們只能自定義 View,來實現(xiàn)。思路步驟如下:
- 做一個空父布局,提供出去,可以套任何布局
- 在空的布局對套進來的子布局操作。
- 要在
draw這個函數(shù)下手,并且要在super.draw()之后去做剪裁,目的是確保子布局先draw完。 - 使用畫筆
setXfermode去實現(xiàn)這個動畫效果。先用Path畫出圓形/矩形,重疊在畫布上面,取出重疊的View。
3、我們復寫draw函數(shù),對draw函數(shù)進行重寫?;仡櫼幌?code>draw 函數(shù)的流程:

繪制流程
可以重寫的只有:
draw-
onDraw(只能繪制自己,繪制不了內(nèi)部子布局) dispatchDraw
綜上:滿足條件的只有 draw 和 dispatchDraw重寫這兩個都可以實現(xiàn),譬如下面模擬代碼:
@Override
public void draw(Canvas canvas) {
canvas.saveLayer(mLayer, null, Canvas.ALL_SAVE_FLAG);
super.draw(canvas); //自身繪制在canvas上,子布局繪制在canvas
onClipDraw(canvas); //上面代碼繪制完畢,再對整個canvas進行操作
canvas.restore();
}
@Override
protected void dispatchDraw(Canvas canvas) {
canvas.saveLayer(mLayer, null,Canvas.ALL_SAVE_FLAG);
/**
*子布局繪制在canvas,自身還沒有繪制完畢,還要跑繪制
*drawAutofilledHighlight-onDrawForeground-drawDefau*ltFocusHighlight。不過沒關系我們只要對子布局操作
*/
super.dispatchDraw(canvas);
onClipDraw(canvas); //子布局繪制完畢,再對canvas進行操作
canvas.restore()
}
三、代碼實現(xiàn)
1、首先開啟一個動畫器,拿到動畫執(zhí)行的 百分比值 :mAnimatorValue
/**
* 初始化動畫類
*/
private void initAnimator() {
mUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//拿到動畫的執(zhí)行的百分比mAnimatorValue
mAnimatorValue = (float) animation.getAnimatedValue();
invalidate();
}
};
mStartingAnimator = new ValueAnimator().setDuration(defaultDuration);
mStartingAnimator.setInterpolator(new AccelerateInterpolator());
mStartingAnimator.addUpdateListener(mUpdateListener);
}
/**
* 開啟動畫
* @param animaType 動畫類型
*/
public void startAnimal(AnimaType animaType) {
this.mAnimaType = animaType;
setVisibility(View.VISIBLE);
mStartingAnimator.cancel();
if(mAnimaType == AnimaType.BackCircle ||
mAnimaType == AnimaType.BackLeftRight ||
mAnimaType == AnimaType.BackUpDown ) {
mStartingAnimator.setFloatValues(1,0);
} else {
mStartingAnimator.setFloatValues(0,1);
}
mStartingAnimator.start();
}
2、我們再來生成個剪裁路徑 mClipPath(Path類),需要通過百分比 mAnimatorValue 計算出 mClipPath 需要添加半徑為多大的圓 :
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mLayer.set(0, 0, w, h);
refreshRegion(this);
}
public void refreshRegion(View view) {
int w = (int) mLayer.width();
int h = (int) mLayer.height();
RectF areas = new RectF();
areas.left = view.getPaddingLeft();
areas.top = view.getPaddingTop();
areas.right = w - view.getPaddingRight();
areas.bottom = h - view.getPaddingBottom();
mClipPath.reset();
PointF center = new PointF(w / 2, h / 2);
if (mAnimaType == AnimaType.Circle || mAnimaType == AnimaType.BackCircle) {
float d = (float) Math.hypot(areas.width(), areas.height());
//通過動畫的百分比mAnimatorValue,計算出圓的半徑
float r = d / 2 * mAnimatorValue;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
//這里添加了一個圓
mClipPath.addCircle(center.x, center.y, r, Path.Direction.CW);
mClipPath.moveTo(0, 0); // 通過空操作讓Path區(qū)域占滿畫布
mClipPath.moveTo(w, h);
} else {
float y = h / 2 - r;
mClipPath.moveTo(areas.left, y);
mClipPath.addCircle(center.x, center.y, r, Path.Direction.CW);
}
}
}
3、最后這里我們對 Draw 下手,把生成好的 mClipPath,畫到 canvas (整個已經(jīng)繪制到的布局),使用 PorterDuff.Mode.DST_OUT 拿出 和圓重疊的部分。(其中使用了一個小技巧兼容 android 9.0 詳細看下面代碼)
@Override
public void draw(Canvas canvas) {
canvas.saveLayer(mLayer, null, Canvas.ALL_SAVE_FLAG);
super.draw(canvas);
onClipDraw(canvas);
canvas.restore();
}
public void onClipDraw(Canvas canvas) {
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mOpClipPath.reset();
mOpClipPath.addRect(0, 0, mLayer.width(), mLayer.height(), Path.Direction.CW);
//取反,因為 android 9.0 不對 paint 以外(paint 自身有指定區(qū)域,這里在畫筆上添加的區(qū)域是一個圓)的布局繪制
mOpClipPath.op(mClipPath, Path.Op.DIFFERENCE);
canvas.drawPath(mOpClipPath, mPaint);
}
代碼很簡單,就一個布局??梢杂脕韺崿F(xiàn)
Activity的跳轉,界面部分View的切換。也極易拓展,建議大家下載下來看看。
最后鳴謝:gcssloop作者 給了我靈感。