Android UI 篇- 實現一個揭露動畫

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

效果圖 2、 應用場景分析:

<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 程式碼中,呼叫

mClipAnimationLayout.startAnimal(RevealAnimationLayout.AnimaType.Circle);

二、流程分析

1、android 5.0 其實已經擁有對應的介面 ViewAnimationUtils.createCircularReveal ,然而還是有一定的侷限性:

2、對於這個效果,我們只能自定義 View,來實現。思路步驟如下:

3、我們複寫draw函式,對draw函式進行重寫。回顧一下draw 函式的流程:

繪製流程

可以重寫的只有:

綜上:滿足條件的只有 draw 和 dispatchDraw重寫這兩個都可以實現,譬如下面模擬程式碼:

@Override

publicvoiddraw(canvas canvas){

canvas.saveLayer(mLayer, null, Canvas.ALL_SAVE_FLAG);

super.draw(canvas); //自身繪製在canvas上,子佈局繪製在canvas

onClipDraw(canvas); //上面程式碼繪製完畢,再對整個canvas進行操作

canvas.restore();

}

@Override

protectedvoiddispatchDraw(Canvas canvas){

canvas.saveLayer(mLayer, null,Canvas.ALL_SAVE_FLAG);

/**

*子佈局繪製在canvas,自身還沒有繪製完畢,還要跑繪製

*drawAutofilledHighlight-onDrawForeground-drawDefau*ltFocusHighlight。不過沒關係我們只要對子佈局操作

*/

super.dispatchDraw(canvas);

onClipDraw(canvas); //子佈局繪製完畢,再對canvas進行操作

canvas.restore()

}

三、程式碼實現

1、首先開啟一個動畫器,拿到動畫執行的 百分比值 :mAnimatorValue

/**

* 初始化動畫類

*/

privatevoidinitAnimator() {

mUpdateListener = newValueAnimator.AnimatorUpdateListener() {

@ Override

publicvoidonAnimationUpdate(ValueAnimator animation) {

//拿到動畫的執行的百分比mAnimatorValue

mAnimatorValue = ( float) animation.getAnimatedValue();

invalidate();

}

};

mStartingAnimator = newValueAnimator().setDuration(defaultDuration);

mStartingAnimator.setInterpolator( newAccelerateInterpolator());

mStartingAnimator.addUpdateListener(mUpdateListener);

}

/**

* 開啟動畫

* @param animaType 動畫型別

*/

publicvoidstartAnimal(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

protectedvoidonSizeChanged(intw, inth, intoldw, intoldh){

super.onSizeChanged(w, h, oldw, oldh);

mLayer.set( 0, 0, w, h);

refreshRegion( this);

}

publicvoidrefreshRegion(View view){

intw = ( int) mLayer.width();

inth = ( int) mLayer.height();

RectF areas = newRectF();

areas.left = view.getPaddingLeft();

areas.top = view.getPaddingTop();

areas.right = w – view.getPaddingRight();

areas.bottom = h – view.getPaddingBottom();

mClipPath.reset();

PointF center = newPointF(w / 2, h / 2);

if(mAnimaType == AnimaType.Circle || mAnimaType == AnimaType.BackCircle) {

floatd = ( float) Math.hypot(areas.width(), areas.height());

//通過動畫的百分比mAnimatorValue,計算出圓的半徑

floatr = 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區域佔滿畫布

mClipPath.moveTo(w, h);

} else{

floaty = h / 2– r;

mClipPath.moveTo(areas.left, y);

mClipPath.addCircle(center.x, center.y, r, Path.Direction.CW);

}

}

}

3、最後這裡我們對 Draw 下手,把生成好的 mClipPath,畫到 canvas (整個已經繪製到的佈局),使用 PorterDuff.Mode.DST_OUT 拿出 和圓重疊的部分。(其中使用了一個小技巧相容 android 9.0 詳細看下面程式碼)

@Override

publicvoiddraw(Canvas canvas){

canvas.saveLayer(mLayer, null, Canvas.ALL_SAVE_FLAG);

super.draw(canvas);

onClipDraw(canvas);

canvas.restore();

}

publicvoidonClipDraw(Canvas canvas){

mPaint.setColor(Color.WHITE);

mPaint.setStyle(Paint.Style.FILL);

mPaint.setXfermode( newPorterDuffXfermode(PorterDuff.Mode.DST_OUT));

mOpClipPath.reset();

mOpClipPath.addRect( 0, 0, mLayer.width(), mLayer.height(), Path.Direction.CW);

//取反,因為 android 9.0 不對 paint 以外(paint 自身有指定區域,這裡在畫筆上新增的區域是一個圓)的佈局繪製

mOpClipPath.op(mClipPath, Path.Op.DIFFERENCE);

canvas.drawPath(mOpClipPath, mPaint);

}

程式碼很簡單,就一個佈局。可以用來實現 Activity 的跳轉,介面部分 View 的切換。也極易拓展,建議大家下載下來看看。

github 完整原始碼:

https://github.com/zhangchaojiong/RevealAnimationLayout

最後鳴謝:gcssloop作者 給了我靈感。

Comments

comments

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *