Android属性动画2-----自定义属性动画
在《Android属性动画1-----属性动画的简单使用》中,简单介绍了属性动画的基本使用,在本章节中,将会结合前面的基础知识,实现一个自定义属性动画。
基本的特效:旋转 + 逃逸聚合 + 扩散 + 策略设计模式,完整源码如下:
public class SplashView extends View {
//动画
private ValueAnimator valueAnimator;
//绘制圆的画笔
private Paint mPaint = new Paint();
//绘制背景的画笔
private Paint mPaintBackground = new Paint();
//当前大圆旋转角度(弧度)
private float mCurrentRotationAngle = 0F;
// 大圆(里面包含很多小圆的)的半径
private float mRotationRadius = 90;
// 每一个小圆的半径
private float mCircleRadius = 18;
//当前大圆的半径
private float mCurrentRotationRadius = mRotationRadius;
//颜色的集合
private int[] mCircleColors;
//圆圈旋转的时间
private int mRotationDuration = 1200;
// 屏幕正中心点坐标
private float mCenterX;
private float mCenterY;
//屏幕对角线的一般
private int DisBank;
//空心圆初始半径
private float mHoleRadius = 0F;
public SplashView(Context context) {
this(context,null,0);
}public SplashView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}public SplashView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}private void init() {
//抗锯齿
mPaint.setAntiAlias(true);
mPaintBackground.setAntiAlias(true);
//描边
mPaintBackground.setStyle(Paint.Style.STROKE);
mPaintBackground.setColor(Color.DKGRAY);
//颜色集合
mCircleColors = new int[]{R.color.colorBlue,R.color.colorOrange,R.color.colorGreen,
R.color.colorRed,R.color.colorYellow,R.color.colorPupple};
}//初始状态
SplashState state = null;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(state == null){
//初始状态 旋转
state = new RotateState();
}
//画圆
state.drawState(canvas);
}//状态转变
public void splashDisappear(){
if(state != null && state instanceof RotateState){
//动画取消
state.cancel();
post(new Runnable() {
@Override
public void run() {
//开始聚合动画
state = new MergingState();
}
});
}
}//业务逻辑状态
//1.刚进来时,执行旋转动画
private class RotateState extends SplashState{
public RotateState(){
//2π == 360度
valueAnimator = ValueAnimator.ofFloat(0, (float) (Math.PI*2));
//设置匀速插值器
valueAnimator.setInterpolator(new LinearInterpolator());
//设置监听
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//当前圆的角度
mCurrentRotationAngle = (float) animation.getAnimatedValue();
//重新绘制
postInvalidate();
}
});
valueAnimator.setDuration(mRotationDuration);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.start();
}
@Override
public void drawState(Canvas canvas) {
//画背景
drawBackground(canvas);
//画圆
drawCircle(canvas);
}
}@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w/2;
mCenterY = h/2;
//屏幕对角线的一半
DisBank = (int) (Math.sqrt(w*w + h*h)/2f);
}private void drawCircle(Canvas canvas) {
//每个圆对应的角度(360/6 = 60°)
float rotationAngle = (float) (Math.PI * 2 / mCircleColors.length);
//关于每个点的坐标
for (int i = 0;
i < mCircleColors.length;
i++) {
//角度是动态变化的
float angle = i*rotationAngle + mCurrentRotationAngle;
float cx = (float) (mCenterX + mCurrentRotationRadius * Math.cos(angle));
float cy = (float) (mCenterY + mCurrentRotationRadius * Math.sin(angle));
//开始画
mPaint.setColor(mCircleColors[i]);
canvas.drawCircle(cx,cy,mCircleRadius,mPaint);
}
}private void drawBackground(Canvas canvas) {
if(mHoleRadius >0){
//画圆
float strokWidth = DisBank - mHoleRadius;
mPaintBackground.setStrokeWidth(strokWidth);
float radius = mHoleRadius + strokWidth/2;
canvas.drawCircle(mCenterX,mCenterY,radius,mPaintBackground);
}else {
canvas.drawColor(Color.WHITE);
}
}//2.数据加载完毕之后,聚合逃逸动画
private class MergingState extends SplashState{
public MergingState(){
valueAnimator = ValueAnimator.ofFloat(mRotationRadius,0);
valueAnimator.setDuration(mRotationDuration);
valueAnimator.setInterpolator(new OvershootInterpolator(10));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentRotationRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
//监听动画完成
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
//启动扩散动画
state = new ExpandState();
}
});
valueAnimator.start();
}
@Override
public void drawState(Canvas canvas) {
drawBackground(canvas);
drawCircle(canvas);
}
}
//3.扩散动画以屏幕到中心线的对角线为半径
private class ExpandState extends SplashState{
publicExpandState(){
valueAnimator = ValueAnimator.ofFloat(mCircleRadius,DisBank);
valueAnimator.setDuration(mRotationDuration);
//valueAnimator.setInterpolator(new AccelerateInterpolator(5));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mHoleRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
@Override
public void drawState(Canvas canvas) {
drawBackground(canvas);
}
}//策略模式
private abstract class SplashState{
//画动画状态
public abstract void drawState(Canvas canvas);
//取消动画
public void cancel(){
valueAnimator.cancel();
}
}}
下面就详细介绍动画的实现。
1、旋转动画
该动画主要分为3种:一开始是旋转动画,然后是聚合逃逸动画,最后是一个扩散动画,3种动画为3种状态,因此使用策略模式,实现3种状态。
策略模式的优点:
(1)如果在使用上述3种状态时,在判断某种状态时往往会使用if-else语句,也就是用户不选择A那么就选择B这样的一种情况。这种情况耦合性太高了,而且代码臃肿,有了策略模式我们就可以避免这种现象,
(2)策略模式遵循开闭原则,实现代码的解耦合。扩展新的方法时也比较方便,只需要继承策略基类就好了。
private abstract class SplashState{
//画动画
public abstract void drawState(Canvas canvas);
//取消动画
public void cancel(){
animator.cancel();
}
}//3种状态的实现
//旋转动画
public class RotateState extends SplashState{@Override
public void drawState(Canvas canvas) {}
}//聚合逃逸动画
public class MergingState extends SplashState{@Override
public void drawState(Canvas canvas) {}
}//扩散动画
public class ExpandState extends SplashState{@Override
public void drawState(Canvas canvas) {}
}
完成了3种状态的实现之后,默认打开的第一个动画就是旋转动画,因此在
onDraw
方法中,就需要先画出旋转动画。SplashState mState = null;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mState == null){
mState = new RotateState();
}
//开始画动画
mState.drawState(canvas);
}
接下来就需要处理RotateState旋转类动画中的逻辑。
//旋转动画
public class RotateState extends SplashState{
public RotateState(){
//360度旋转
animator = ValueAnimator.ofFloat(0, (float) (Math.PI * 2));
//匀速旋转
animator.setInterpolator(new LinearInterpolator());
//设置监听
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取当前大圆旋转的角度
mCurrentRotationAngle = (float) animation.getAnimatedValue();
postInvalidate();
}
});
//设置动画的时间
animator.setDuration(mRotationDuration);
//开启动画
animator.start();
}
@Override
public void drawState(Canvas canvas) {
//画背景
drawBackground(canvas);
//画圆圈
drawCircle(canvas);
}
}private void drawCircle(Canvas canvas) {
//360度,每个小圆平均分得的度数
float mCircleAngle = (float) (Math.PI * 2 / mCircleColors.length);
for (int i = 0;
i < mCircleColors.length;
i++) {
//当前每个圆对应的角度
float mCurrentCircleAngle = i * mCircleAngle + mCurrentRotationAngle;
float cx = (float) (mCenterX + mRotationRadius * Math.cos(mCurrentCircleAngle));
float cy = (float) (mCenterY + mRotationRadius * Math.sin(mCurrentCircleAngle));
//开始画圆
mPaint.setColor(mCircleColors[i]);
canvas.drawCircle(cx,cy,mCircleRadius,mPaint);
}
}
文章图片
在旋转动画完成之后,需要切换状态,转变为聚合逃逸动画
public void splashDisappear(){
if(mState != null && mState instanceof RotateState){
//结束当前的动画
mState.cancel();
post(new Runnable() {
@Override
public void run() {
mState = new MergingState();
}
});
}
}
2、聚合逃逸动画
【Android属性动画2-----自定义属性动画】聚合逃逸动画,在旋转动画完成之后,会以中心为基准,全部的圆向圆心内聚集,因此在处理这一部分逻辑时,需要动态改变大圆的半径即可。
//聚合逃逸动画
public class MergingState extends SplashState{
public MergingState(){
//从大圆的半径---递减到0
animator = ValueAnimator.ofFloat(mRotationRadius,0);
//添加张力插值器
animator.setInterpolator(new OvershootInterpolator(10));
//添加监听
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentCircleRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.setDuration(mRotationDuration);
animator.start();
}
@Override
public void drawState(Canvas canvas) {
drawBackground(canvas);
drawCircle(canvas);
}
}
3、扩散动画
聚合动画的执行需要监听是否完成,如果已经完成了,那么就开始扩散动画。
//监听动画是否完成
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
//启动扩散动画
mState = new ExpandState();
}
});
当监听到聚合动画已经完成,就开启扩散动画,接下来需要处理扩散动画的逻辑。
扩散动画,是以屏幕对角线的一半为半径,然后从小圆半径开始,逐渐扩散。
//扩散动画
public class ExpandState extends SplashState{
public ExpandState(){
//设置动画 最小半径---屏幕对角线的一半
animator = ValueAnimator.ofFloat(mCircleRadius,mDiaLine);
//设置监听
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mHoleRedis = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.setDuration(mRotationDuration);
//开启动画
animator.start();
}
@Override
public void drawState(Canvas canvas) {
drawBackground(canvas);
}
}
private void drawBackground(Canvas canvas) {
if(mHoleRedis >0) {
//画圆
float strokeWidth = mDiaLine - mCircleRadius;
mBackgroundPaint.setStrokeWidth(strokeWidth);
//空心圆的半径
float redis = mHoleRedis + strokeWidth/2;
canvas.drawCircle(mCenterX,mCenterY,redis,mBackgroundPaint);
}else {
canvas.drawColor(getResources().getColor(R.color.colorBlue));
}
}
通过以上3步骤,配合
ValueAnimator
实现了整体的动画设计,在实际的项目中,大部分的动画实现都是以上套路,只是其中的细节可能涉及估值器和插值器的使用,下一节中,将会手写一个动画加载框架。推荐阅读
- 第6.2章(设置属性)
- android第三方框架(五)ButterKnife
- Android中的AES加密-下
- 带有Hilt的Android上的依赖注入
- android|android studio中ndk的使用
- Android事件传递源码分析
- RxJava|RxJava 在Android项目中的使用(一)
- Android7.0|Android7.0 第三方应用无法访问私有库
- 深入理解|深入理解 Android 9.0 Crash 机制(二)
- android防止连续点击的简单实现(kotlin)