本文按自己实现的思路整理成博客供参考和日后复习,直接上效果
- 1、控件分析
- 2、巧用策略模式
- 3、优化
- 4、工程源码
1.控件分析
1.1状态分析
初始状态:它的背景是一个圆
清理状态:背景圆先缩小成一个小圆,并带动几个小圆绕圆环运行,并且这些小圆之间的距离先拉开后又缩进重贴,依次反复。
清理完成:小圆拉开,向圆环中心聚合,背景圆有小扩大到初始状态(颜色根据清理程度显示不同颜色)
增强效果:以背景圆圆心为中心,扩散出很多大小、半径、颜色、运行距离不同的小圆,并最终消失。(随机的,即每次清理完,扩散小圆个数,大小、透明度等等都是随机的)
1.2定义属性
分析哪些属性可变,定义成属性,支持布局文件中配置。个人习惯于先实现效果,最后根据需求再从代码中分离出自定义属性。
1.3 onMeasure测量
这个步骤不需要也行,但为了控件效果,宽高太小肯定不好看,所以测量过程根据给定的宽高和控制要求最小的宽高进行比较,取大值。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int minHW = DensityUtils.dp2px(mMinHeightAndWidth);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
height = Math.min(width, height);
height = height > minHW ? height : minHW;
mRadius = height / 4;
setMeasuredDimension(width, height);
}
minHW 是控件要求的最小宽高。之后所有的状态都在控件宽高内绘制。
1.4 onDraw绘制
分析
根据效果图中,整个过程中出现的几何图形都是圆形,所以整个过程中也只涉及到一个API:canvas.drawCircle(cx,cy,radius,paint);
drawCircle这个方法的四个参数:圆心的坐标x和y,半径radius,画笔paint决定颜色,透明度等。
圆要运动或显示在不同位置,只要改变x,y
圆大小变化,由半径radius决定;
圆的颜色透明度等由paint设置。
所以不论何种状态,圆的动画的运行原理就改变x,y,或者半径radius或颜色。画出对应的圆,原理比较简单。既然每个阶段都是画圆,并根据一定的算法不断地改变圆坐标或半径,根据新的参数绘制圆,让其“动起来”,所以动画的执行算法和每个状态过度之间的动画才是我们需要深入考虑的。
按上述的分析,定义方法 drawBackground(Canvas canvas) 用于画背景圆
定义方法drawBoll(Canvas canvas)用于画绕圆环运动的圆等等,让后在onDraw(Canvas canvas)中根据条件调用不同的画方法,每个draw方法对应一个valueAnimator,执行具体的运动算法(详细看代码),不同的状态下执行不同的valueAnimator。哇,这样看来显得过于复杂。
每一种状态,都是一种画方法(draw)对应一种运动算法(valueAnimator)。所以可以抽取为一种“状态”如下
public abstract class ClearViewAnim {
/**
* 执行绘画
*
* @param canvas 画布
*/
public abstract void drawState(Canvas canvas);
/**
* 执行动画
*/
public abstract void startAnim();
/**
* 停止动画
*/
public abstract void stopAnim();
}
让各种状态都继承ClearViewAnim ,让后各自实现自己的画方法和运动动画。
//定义初始状态
class InitAnim extends ClearViewAnim{
public InitAnim(int status) {
currentState = status;
//初始化valueAnimator 定义具体的运动算法
}
@Override
public void drawState(Canvas canvas) {
//调用具体的绘制方法
drawBackground(canvas);
}
@Override
public void startAnim() {
valueAnimator.start();
}
@Override
public void stopAnim() {
valueAnimator.cancel();
}
}
//清理状态
class RunningAinm extends ClearViewAnim {
//省略...
}
//清理完成状态
class FinishAinm extends ClearViewAnim {
//省略...
}
//增强效果状态
class ExpendAmin extends ClearViewAnim {
//省略...
}
这个时候onDraw方法就变成这样,代码非常简洁。
//动画状态抽象类
private ClearViewAnim mViewAnim;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mViewAnim == null) {
//初始状态InitAnim
mViewAnim = new InitAnim(STATE_INIT);
}
mViewAnim.drawState(canvas);
}
由于mViewAnim 是抽象类, mViewAnim.drawState(canvas)具体执行什么状态下的画方法,由其子类决定的,所以根据不同的时机将mViewAnim替换成InitAnim、RunningAinm 、FinishAinm 、ExpendAmin 就执行对应状态下的画方法和运动算法。
2.巧用策略模式
上述动画状态的抽取,替换,使用了正是策略模式。
定义:策略模式是指对一系列的算法定义,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
使用了策略模式后,现在再加一个动画状态此时显得非常简单,比如在FinishAnim状态之后,还想执行一个Clear状态的动画,那么定义一个ClearAnim继承于ClearViewAnim。
mViewAnim = new FinishAnim();替换成mViewAnim = new ClearAnim();
无论扩展多种状态,只需要新建一个状态Anim继承于状态抽象类,而无需对onDraw进行改动,这满足了程序设计的一条重要原则"对修改关闭、对扩展开放"
3.优化
4.工程源码
本文没有对具体的运行动画展开,主要都是一些数学计算,工程已托管在github上,欢迎start!
工程源码