要如何实现这个效果呢???
我们要从以下几个方面考虑,一步一步来:
1.考虑如何绘制6个等间距的颜色圆圈。
2.如何实现旋转动画效果。
3.如何实现小圆逃逸后在聚合。
4.如何实现水波纹扩散。
我们知道实现思路,接下来我按着我们的思路一步一步的实现:
一.初始化数据
private void init(Context context) { mCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors); //设置取消锯齿 mPaint.setAntiAlias(true); mPaintBackground.setAntiAlias(true); //设置边框样式 mPaintBackground.setStyle(Style.STROKE); mPaintBackground.setColor(mSplashBgColor); } |
以上代码:获取我们要绘制的颜色及设置Paint属性。
二.绘制等间距的小圆
/** * 画多个小圆 * * @param canvas */ public void drawCircle(Canvas canvas) { //每个小圆的间隔角度 float rotationAngle = (float) (Math.PI * 2 / mCircleColors.length); for (int i = 0; i < mCircleColors.length; i++) { //每个小圆i*间隔角度+旋转角度=当前小圆的真实角度 double angle = mCurrentRotationAngle + i * rotationAngle; 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); } } |
三.实现旋转动画
public RotationState() { //小圆半径,需要大圆半径和它旋转的角度 //估值器--它使用的是弧度0~2PI mAnimator = ValueAnimator.ofFloat(0, (float) Math.PI * 2); //线性插值器,会平滑地计算;这样在每完成一个周期时,它不会卡顿 mAnimator.setInterpolator(new LinearInterpolator()); //设置旋转时间 mAnimator.setDuration(mRotationDuration); mAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { //这个mCurrentRotationAngle这个值的变化,就是造成小圆旋转的原因;若 //这个值不变了,则小圆们就不会旋转 mCurrentRotationAngle = (Float) animation.getAnimatedValue(); //提醒view重绘 invalidate(); } }); //设置旋转次数--无穷次数;因为它不知道什么时候进入下一个动画,所以把它设置为重复次数为无穷 mAnimator.setRepeatCount(ValueAnimator.INFINITE); mAnimator.start(); } |
到此你会有个疑问,旋转动画是怎么实现的,接着我们在这里发现了ValueAnimator.ofFloate()这个方法,这个方法干了什么,带着疑问继续往下看。
/** * Constructs and returns a ValueAnimator that animates between float values. A single * value implies that that value is the one being animated to. However, this is not typically * useful in a ValueAnimator object because there is no way for the object to determine the * starting value for the animation (unlike ObjectAnimator, which can derive that value * from the target object and property being animated). Therefore, there should typically * be two or more values. * * @param values A set of values that the animation will animate between over time. * @return A ValueAnimator object that is set up to animate between the given values. */ public static ValueAnimator ofFloat(float... values) { ValueAnimator anim = new ValueAnimator(); anim.setFloatValues(values); return anim; } |
上面代码:构造并返回一个ValueAnimator,它在浮动值之间进行动画。也就是我们上面传入的参数0~Math.PI*2 ==0~2π,也就是一个圆周长之间进行动画。浮动值之间进行动画是什么意思???接下来继续分析:
/** * Sets float values that will be animated between. A single * value implies that that value is the one being animated to. However, this is not typically * useful in a ValueAnimator object because there is no way for the object to determine the * starting value for the animation (unlike ObjectAnimator, which can derive that value * from the target object and property being animated). Therefore, there should typically * be two or more values. *设置浮动值,在动画之间。一个单一的数值意味着这个值是一个动画。然而,这并不是典型的 在ValueAnimator对象中有用,因为对象无法确定 动画的起始值(不像ObjectAnimator,它可以派生出这个值 从目标对象和属性被激活)。因此,ValueAnimator应该一般是两个或更多的值。 * <p>If there are already multiple sets of values defined for this ValueAnimator via more * than one PropertyValuesHolder object, this method will set the values for the first * of those objects.</p> * * @param values A set of values that the animation will animate between over time. */ public void setFloatValues(float... values) { if (values == null || values.length == 0) { return; } if (mValues == null || mValues.length == 0) { setValues(PropertyValuesHolder.ofFloat("", values)); } else { PropertyValuesHolder valuesHolder = mValues[0]; valuesHolder.setFloatValues(values); } // New property/values/target should cause re-initialization prior to starting mInitialized = false; } |
在这里我们发现了浮动值,在动画之间一个值对应一个动画。在这里我们看注释也发现了ValueAnimator与ObjectAnimator的区别。
四.实现小圆逃逸后在聚合
mAnimator = ValueAnimator.ofFloat(0, mRotationRadius); //插值器-弹射效果的,tension表示弹射的范围长度 mAnimator.setInterpolator(new OvershootInterpolator(60f)); //设置动画时间 mAnimator.setDuration(mSplashDuration / 3); mAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurrentRotationRadius = (Float) animation.getAnimatedValue();//1 //提醒view重绘 invalidate(); } }); //监听动画执行完毕状态 mAnimator.addListener(new AnimatorListenerAdapter() { //2 @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); //进入下一个动画 mState = new ExpandingState(); } }); //反向调用的点向后播放 mAnimator.reverse(); |
上面代码:setInterpolator(new OvershootInterpolator(60f));表示向外弹射的范围,
上面注释1获取所有的动画值,通过invalidate()方法不断绘制也就是收缩。注释2监听动画状态,
如果动画执行完毕就接着做后续工作,也就是我们接下来要说的扩散动画。
五.扩散动画
public ExpandingState() { //估值器 --空心圆的半径:0到对角线的一半 mAnimator = ValueAnimator.ofFloat(0, mDiagonalDist); //设置动画时间 mAnimator.setDuration(mSplashDuration / 3); mAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { //空心圆半径 mHoleRadius = (Float) animation.getAnimatedValue(); //提醒view重绘 invalidate(); } }); mAnimator.start(); } @Override public void drawState(Canvas canvas) { //绘制空心圆效果 if (mHoleRadius > 0f) { //画笔的宽度:对角线的一半 - 空心圆半径 float storkeWidth = mDiagonalDist - mHoleRadius; mPaintBackground.setStrokeWidth(storkeWidth); //为了看的清楚动画扩散,storkeWidth设置为10 // mPaintBackground.setStrokeWidth(10); float circleRadius = mHoleRadius + mDiagonalDist / 2f; //绘制扩散圆的效果 canvas.drawCircle(mCenterX, mCenterY, circleRadius, mPaintBackground); } } |
代码链接地址:https://github.com/yangxiansheng123/PropertyAnimation
参考文章:
1.https://blog.youkuaiyun.com/NaoiAyato/article/details/78290359
2.https://blog.youkuaiyun.com/qq_18983205/article/details/78154900
Project中用到的其他动画库:
3.一个带渐变层叠动画的左右滑动效果(类似于探探、tinder)
5.一款加载动画,不同的物体加速下落砸到了文字上,文字逞阻尼效果,向下弯曲并像橡皮筋一样弹动回去的效果
8.下拉刷新和上拉加载更多的RecyclerView,具有下拉和刷新动画。