原标题:Android UI 篇- 实现一个揭露动画
一、应用场景1、 先上效果图:
效果图 2、 应用场景分析:
适用于 Activity 界面跳转
适用于 View 的切换
支持所有 View 布局的动画效果
3、代码使用(非常简洁好用):
在你需要做动画的布局上,套上RevealAnimationLayout 就可以了,支持套任何布局!!!
android:id="@+id/animat_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:scaleType="centerCrop"
android:src="@mipmap/test"/>
// java 代码中,调用
mClipAnimationLayout.startAnimal(RevealAnimationLayout.AnimaType.Circle);
二、流程分析
1、android 5.0 其实已经拥有对应的接口 ViewAnimationUtils.createCircularReveal ,然而还是有一定的局限性:
只能再 android 5.0 上使用
只提供圆形的揭露效果
也没有提供拓展其他图形接口
2、对于这个效果,我们只能自定义 View,来实现。思路步骤如下:
做一个空父布局,提供出去,可以套任何布局
在空的布局对套进来的子布局操作。
要在 draw 这个函数下手,并且要在 super.draw() 之后去做剪裁,目的是确保子布局先 draw 完。
使用画笔 setXfermode 去实现这个动画效果。先用 Path 画出圆形/矩形,重叠在画布上面,取出重叠的 View 。
3、我们复写draw函数,对draw函数进行重写。回顾一下draw 函数的流程:
绘制流程
可以重写的只有:
draw
onDraw (只能绘制自己,绘制不了内部子布局)
dispatchDraw
综上:满足条件的只有 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作者 给了我灵感。
责任编辑: