半年更系列
之前写过不少自定义View及Canvas绘制的文章, 不过都是怎么做, 没有写明为什么要这么做. 刚好这次有了一个比较简单经典的动画, 记录下完成的过去, 希望可以起到一个抛转引玉的效果.
最终效果
可以看到这个效果还是比较简单的, 是一个模拟日光晃动的效果, 一般出现与和天气有关的场景中.
设计思路
设计思路和大家都一样:
关于动画如何动起来. 我一般习惯于先将静态的动画完成, 再重新考虑如何动起来, 虽然看起来影响到之前已经完成的代码. 不过相较于在动态静态一并完成, 这样分开考虑更加的简单. 而且针对与自定义动画的实现来说, 代码量还没有大到无法二次开发的程度.
分解静态动画
简单来说就是将动画分解为一个个最基础的效果, 毕竟代码还是要讲规则的.
需要注意的是, 有的动画在前后的变化过程中, 出现的元素时不一样的.这都是需要一个一个进行分解的. 在这个过程中, 也不能完全规避动态的部分.
简单来说, 如果你的动画中有一个圆, 你可以把它看成一个圆, 也可以把它看成两个半圆拼接而成. 如果动画流程中, 它一直都是一个圆, 那这两个方法倒无差异, 但如果你最后需要在中间加一个矩形使其可以自动填充的话, 一个圆的实现必然不合时宜.(可以想象以下你的未读消息小红点)
这个动画相对来说还是比较简单的, 可以分解为三个颜色相近的圆在一定范围内浮动. 整个流程中也都只需要这三个圆就行了.
实现静态动画
如果实现静态动画就仁者见仁智者见智了, 我一般习惯于使用Canvas绘制. 当然这个也和实际情况有关.
具体的内容可以参考后面的代码部分
实现动态动画
动态动画简单来说就是让动画动起来(字面意思), 换个说法, 让静态动画在指定的时间上绘制为指定的效果. 动画一般和时间时有关的.
我更倾向于通过ValueAnimator和Interpolator来完成, 一是系统提供的方法更加的稳定, 二是Interpolator也提供了大量的效果, 足以满足99%的开发需求.(对于我来说, 自定义Interpolator一次也没用过)
这个动画来说, 动态的效果可以控制每个圆的圆心在一定范围内移动即可.
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
| public class CustomSunView extends View {
public enum CustomColor{ Blue("#dbf3fb", "#cbebfb", "#bbebfb"), Yellow("#fbf3e3", "#faf2cb", "#fbebbb"), Red("#fbebeb", "#fbe3e3", "#fbd3d3"), Pink("#fbebfb", "#fbdbfb", "#fbd3fb");
private final String color1, color2, color3;
CustomColor(String color1, String color2, String color3) { this.color1 = color1; this.color2 = color2; this.color3 = color3; } }
private CustomColor customColor = CustomColor.Blue; private int width, height; private float offset_1_1, offset_1_2, offset_2_1, offset_2_2, offset_3_1, offset_3_2; boolean isStart = false;
public CustomSunView(Context context) { super(context); }
public CustomSunView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = View.MeasureSpec.getSize(widthMeasureSpec); height = View.MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(width, height); }
public void setCustomColor(CustomColor customColor) { this.customColor = customColor; }
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas);
Paint paint = new Paint(); paint.setAntiAlias(true); paint.setAlpha(50); paint.setStyle(Paint.Style.FILL);
float radius = height * 0.75f;
float baseRand = height / 32f;
paint.setColor(Color.parseColor(customColor.color1)); canvas.drawCircle(width + baseRand * offset_1_1, 0 + baseRand * offset_1_2, radius, paint);
paint.setColor(Color.parseColor(customColor.color2)); canvas.drawCircle(width - baseRand * 2 + baseRand * offset_2_1, 0 - baseRand * 2 + baseRand * offset_2_2, radius * 0.95f, paint);
paint.setColor(Color.parseColor(customColor.color3)); canvas.drawCircle(width + baseRand * 2 + baseRand * offset_3_1, 0 + baseRand * 2 + baseRand * offset_3_2, radius * 0.95f, paint);
if (!isStart){ isStart = true; startAnimator(); } }
private float getRandomWithBool(){ int random = getRandom(14, 6); if (getRandom(10, 0) > 5){ return -random / 1f / 10; }else { return random / 1f / 10; } }
private float getRandom(){ int random = getRandom(14, 6); return random / 1f / 10; }
private int getRandom(int MAX, int MIN){ Random random = new Random(); return random.nextInt(MAX - MIN + 1) + MIN; }
public void animators(int index){ ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, getRandomWithBool(), 0f , getRandomWithBool() , 0f); valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); valueAnimator.setDuration((long) (8 * 1000 * getRandom())); valueAnimator.setRepeatCount(1); valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); valueAnimator.setFloatValues(0f, getRandomWithBool(), 0f , getRandomWithBool() , 0f); valueAnimator.start(); } }); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { switch (index){ case 0: offset_1_1 = (float) animator.getAnimatedValue(); invalidate(); break; case 1: offset_1_2 = (float) animator.getAnimatedValue(); break; case 2: offset_2_1 = (float) animator.getAnimatedValue(); break; case 3: offset_2_2 = (float) animator.getAnimatedValue(); break; case 4: offset_3_1 = (float) animator.getAnimatedValue(); break; case 5: offset_3_2 = (float) animator.getAnimatedValue(); break; } } }); valueAnimator.start(); }
public void startAnimator() { for (int i = 0; i < 6; i++) { animators(i); } } }
|
最后
相关代码可以访问我的GitHub:https://github.com/clwater/AndroidAnimation/tree/master/androidanimation05