Android复杂自定义动画的实现思路
Posted Vigibord
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android复杂自定义动画的实现思路相关的知识,希望对你有一定的参考价值。
先上效果:
入场动画:
清理动画:
基本思路
自定义一个View,让该View执行普通的Animation动画,利用Animation来当计时器,控制整个动画流程,收到每个进度变化时,先计算每个动画元素的大小、位置、颜色等逻辑,再刷新View来显示(动画元素在draw方法中自绘)。
A.开始动画,创建一个Animation,设置好动画时间后,利用applyTransformation的回调来控制动画进度,interpolatedTime表示进度,取值范围为0到1;
progressAnimation = new Animation()
@Override
protected void applyTransformation(float interpolatedTime, Transformation t)
super.applyTransformation(interpolatedTime, t);
setProgress(interpolatedTime);
;
progressAnimation.setDuration(ANIMATION_TIME_OPENING);
this.startAnimation(progressAnimation);
B.处理动画元素的逻辑
private void setProgress(float progress)
//处理各个动画元素的基本逻辑
// 比如计算小球的位置和大小,x、y表示小球的实时位置,radius表示实时大小
// x = (int) (origin_x + (des_x_one - origin_x) * progress);
// y = (int) (origin_y + (des_y_one - origin_y) * progress);
// radius = (int) (origin_radius + (des_radius_one - origin_radius) * progress);
//刷新view
this.invalidate();
C. 在View的onDraw方法中绘制动画元素
protected void onDraw(Canvas canvas)
//以小球为例
//绘制小球,x,y坐标,radius半径
canvas.drawCircle( x, y, radius, mPaint);
动画分解
入场动画
总共分为5个部分,在公共时间轴之内完成动画,公共时间轴的进度为从0到1;
1. 4个小球
分为3个阶段,往外走(0-0.3),往中心走(0.3-0.7),再往外走(0.7-1)
其中每个阶段有个私有时间轴的概念,会把公共轴映射到私有轴上
比如从位置A到位置B,公共轴为(0-0.3),映射到私有轴(0-1)上,这样就能在(0-0.3)的时间内从A位移到B;
2. 2个中间的大球
从时间轴0.7开始绘制,内圈的时间轴为(0.7-0.9),外圈的时间轴为(0.7-1),这样两个圈一前一后有膨胀的感觉,同样也是需要将公共轴的进度映射到私有轴上
3. 分数
时间轴为(0.7-1),映射到私有轴(0-1)后,比如分数从0分到35分,每次计算一个分值并绘制,就能实现一个分数动态上涨的效果
4. 大球和小球的颜色
沿用公共轴的进度,每个进度算一个颜色值并绘制;颜色计算的基本原理为有N个色值,每个色值对应一个分数值,比如35分处于N[3]到N[4]色值之间,那么最终色值为N[3]到N[4]之间的一个渐变色;
5. 描述文案
略
清理动画
运动的分为3个部分,由于要求动画的连贯性,故在一个Animation中完成,duration设置较长时间(30s),自己划分时间周期,如将1划分为30个时间周期,一个周期的时间为1/30,在不同的时间周期内完成不同的动作;
1. 4个小球
4小球分为两组,两两绕相同轨道转动;运动阶段1为缩小、加速过程,阶段2为匀速转动等待清理结果响应,阶段3为放大、减速过程;
2. 背景色
3. 分数
难点
- 对于各个动画元素私有时间轴的计算、控制
- 4个小球两两分组,同组小球绕同一椭圆轨道环绕大球转动;
- 小球在椭圆轨道运行时,要区分在正面轨道和背面轨道;处于背面时需要在大球绘制之前绘制避免遮挡大球、没有环绕效果;
- 在收到动画结束的响应之后,需要等待小球匀速完成一个循环回归原位后,再做阶段3的运动;
代码
计算小球运动的类
private class CircleItemSmall
int x;
int y;
int alpha;
float radius;
int color;
float origin_x;
float origin_y;
float des_x_one;
float des_y_one;
float des_x_two = 0;
float des_y_two = 0;
float des_x_three;
float des_y_three;
float origin_radius;
float des_radius_one;
float des_radius_two;
float des_radius_three;
int origin_alpha;
int des_alpha;
float progress_step_one = 0.6f;
float progress_step_two = 0.85f;
boolean isUp;//是否显示是前面
int pos = 0;//0右上 1左上 2左下 3右下
double cos_del;//椭圆倾斜角
double sin_del;//椭圆倾斜角
double a;//椭圆长轴
float oval_center_x;
float oval_center_y;
public CircleItemSmall(float x, float y, float des_x_one, float des_y_one, float des_x_three, float des_y_three
, float radius, float des_radius_one , float des_radius_two, float des_radius_three
, int originAlpha, int desAlpha)
this.origin_x = x;
this.origin_y = y;
this.des_x_one = des_x_one;
this.des_y_one = des_y_one;
this.des_x_three = des_x_three;
this.des_y_three = des_y_three;
calPos();
this.origin_radius = radius;
this.des_radius_one = des_radius_one;
this.des_radius_two = des_radius_two;
this.des_radius_three = des_radius_three;
this.origin_alpha = originAlpha;
this.des_alpha = desAlpha;
private void calPos()
if(des_x_three > 0 && des_y_three < 0)
oval_center_x = (mSmallFinalDesPos[0][0] + mSmallFinalDesPos[2][0]) / 2;
oval_center_y = (mSmallFinalDesPos[0][1] + mSmallFinalDesPos[2][1]) / 2;
a = Math.sqrt((mSmallFinalDesPos[0][0] - oval_center_x) * (mSmallFinalDesPos[0][0] - oval_center_x)
+ (mSmallFinalDesPos[0][1] - oval_center_y) * (mSmallFinalDesPos[0][1] - oval_center_y));
cos_del = ( mSmallFinalDesPos[0][0] - oval_center_x )/ a;
sin_del = ( mSmallFinalDesPos[0][1] - oval_center_y) / a;
pos = 0;
else if(des_x_three < 0 && des_y_three < 0)
oval_center_x = (mSmallFinalDesPos[1][0] + mSmallFinalDesPos[3][0]) / 2;
oval_center_y = (mSmallFinalDesPos[1][1] + mSmallFinalDesPos[3][1]) / 2;
a = Math.sqrt((mSmallFinalDesPos[1][0] - oval_center_x) * (mSmallFinalDesPos[1][0] - oval_center_x)
+ (mSmallFinalDesPos[1][1] - oval_center_y) * (mSmallFinalDesPos[1][1] - oval_center_y));
cos_del = -( mSmallFinalDesPos[1][0] - oval_center_x ) / a;
sin_del = -( mSmallFinalDesPos[1][1] - oval_center_y) / a;
pos = 1;
else if(des_x_three < 0 && des_y_three > 0)
oval_center_x = mSmallCircleList.get(0).oval_center_x;//减小计算
oval_center_y = mSmallCircleList.get(0).oval_center_y;
a = mSmallCircleList.get(0).a;
cos_del = -( mSmallFinalDesPos[2][0] - oval_center_x ) / a;
sin_del = -( mSmallFinalDesPos[2][1] - oval_center_y ) / a;
pos = 2;
else if(des_x_three > 0 && des_y_three > 0)
oval_center_x = mSmallCircleList.get(1).oval_center_x;//减小计算
oval_center_y = mSmallCircleList.get(1).oval_center_y;
a = mSmallCircleList.get(1).a;
cos_del = ( mSmallFinalDesPos[3][0] - oval_center_x ) / a;
sin_del = ( mSmallFinalDesPos[3][1] - oval_center_y ) / a;
pos = 3;
public void handlerProgress(float progress)
if(state == STATE_OPENING)
if (progress <= progress_step_one)
handlerAnimStepOne(progress);
else if (progress <= progress_step_two)
handlerAnimStepTwo(progress);
else
handlerAnimStepThree(progress);
handlerAlpha(progress);
else if(state == STATE_SCAN || state == STATE_CLEAN)
handlerScaleAnim(loop, ratio);
handlerOvalAnim(loop, ratio);
else if( state == STATE_CLEAN_ENDING)
handlerCleanEndScaleAnim(loop, ratio);
handlerOvalAnim(loop, ratio);
if(progress == 1)
reset();
private void reset()
x = (int) des_x_three;
y = (int) des_y_three;
radius = des_radius_three;
alpha = des_alpha;
private void handlerCleanEndScaleAnim(int loop, float ratio)
if(current_rotate_loop == ANIMATION_ROTATE_LOOP_COUNT)
radius = (int) (origin_radius + (des_radius_three - origin_radius) * ratio);
private void handlerScaleAnim(int loop, float ratio)
if(loop < ANIMATION_ROTATE_LOOP_COUNT)
radius = (int) (des_radius_three + (origin_radius - des_radius_three) * ratio);
private void handlerOvalAnim(int loop, float ratio)
if(current_rotate_loop > ANIMATION_ROTATE_LOOP_COUNT)//收到结束消息后循环完一周停止运动
return;
double angle;
if(pos == 0 || pos == 3)
angle = 2 * Math.PI * ratio;
if(angle < Math.PI)
isUp = false;
else
isUp = true;
else
angle = Math.PI + 2 * Math.PI * ratio;
if(angle < 2 * Math.PI)
isUp = true;
else
isUp = false;
double cos_angle = Math.cos(angle);
double sin_angle = Math.sin(angle);
x = (int) (a * cos_angle * cos_del + a / 3 * sin_angle * sin_del + oval_center_x);
y = (int) (a * cos_angle * sin_del + a / 3 * sin_angle * cos_del + oval_center_y);
private void handlerAlpha(float progress)
alpha = (int) (origin_alpha + (des_alpha - origin_alpha) * progress);
private void handlerAnimStepOne(float progress)
float ratio = progress / progress_step_one;
x = (int) (origin_x + (des_x_one - origin_x) * ratio);
y = (int) (origin_y + (des_y_one - origin_y) * ratio);
radius = (int) (origin_radius + (des_radius_one - origin_radius) * ratio);
private void handlerAnimStepTwo(float progress)
float ratio = ( progress - progress_step_one ) / (progress_step_two - progress_step_one);
x = (int) (des_x_one + (des_x_two - des_x_one) * ratio);
y = (int) (des_y_one + (des_y_two - des_y_one) * ratio);
radius = (int) (des_radius_one + (des_radius_two - des_radius_one) * ratio);
private void handlerAnimStepThree(float progress)
float ratio = ( progress - progress_step_two ) / (1 - progress_step_two);
x = (int) (des_x_two + (des_x_three - des_x_two) * ratio);
y = (int) (des_y_two + (des_y_three - des_y_two) * ratio);
radius = (int) (des_radius_two + (des_radius_three - des_radius_two) * ratio);
颜色计算方法
public class CleanColorHelper
/** 颜色列表,依次是红橙绿 */
private static final int[] COLORS =
// 红
0xffff5252, //
0xfffe6530, //
0xffff8d22, //
0xffffb619, //
// 黄
0xfffcd038, //
0xffb5eb12, //
0xff7ad845, //
// 绿
0xff54d36f,
//蓝
0xff32a7de
;
/**
* 调用者控制变色的节奏
*
* @param percent 百分数
*/
public static int manualUpdateColor(Colors startColor, Colors endColor, int percent)
if (percent > 100)
percent = 100;
else if (percent < 0)
percent = 0;
// 8->0 30%
final int startColorIdx = getColorIdx(startColor);
final int endColorIdx = getColorIdx(endColor);
final float newPosition = startColorIdx + (float) (endColorIdx - startColorIdx) * percent / 100;
int showColor ;
if (newPosition == startColorIdx || newPosition == endColorIdx)
showColor = COLORS[(int) newPosition];
else
int currentIdx ;
int nextIdx ;
float offset ;
if (endColorIdx < startColorIdx)
currentIdx = (int) newPosition + 1;
nextIdx = currentIdx - 1;
offset = currentIdx - newPosition;
else
currentIdx = (int) newPosition;
nextIdx = currentIdx + 1;
offset = newPosition - currentIdx;
final int currentColor = COLORS[currentIdx];
final int nextColor = COLORS[nextIdx];
showColor = getShowColor(currentColor, nextColor, offset);
return showColor;
private static int getShowColor(int colorStart, int colorEnd, float offset)
final int startRed = Color.red(colorStart);
final int startGreen = Color.green(colorStart);
final int startBlue = Color.blue(colorStart);
final int endRed = Color.red(colorEnd);
final int endGreen = Color.green(colorEnd);
final int endBlue = Color.blue(colorEnd);
final int showRed = (int) (startRed + offset * (endRed - startRed));
final int showGreen = (int) (startGreen + offset * (endGreen - startGreen));
final int showBlue = (int) (startBlue + offset * (endBlue - startBlue));
return Color.argb(255, showRed, showGreen, showBlue);
private static int getColorIdx(Colors color)
switch (color)
case RED:
return 0;
case ORANGE:
return 4;
case GREEN:
return 8;
return 8;
public static enum Colors
RED, ORANGE, GREEN
以上是关于Android复杂自定义动画的实现思路的主要内容,如果未能解决你的问题,请参考以下文章
Android技术分享|自定义View实现Material Design的Loading效果
Android技术分享|自定义View实现Material Design的Loading效果