前些天,在IXUS上
看到一个很赞的动画,一下子就看对眼了,于是便决定用Android来实现一下,效果如下:
设计在这里:Jana,点赞超棒的设计师
用Android
实现的效果如下:
现在开始分析如何实现这个动画:
当我们看到一个动画要实现的时候,很多朋友可能就直接照着开始实现了,其实这样是很难实现。我们在接触到一个新动画的时候,首先要对动画进行分解。例如本动画,我们进行分析后,可以把动画分为以下几个部分:
1. 圆环放大缩小消失效果:
进行剖析后,我们可以发现这是一个圆环放大缩小的动画,有3个关键帧:
从关键帧1->2->3->圆环消失,那对我们来说就是使用属性动画进行绘制圆环,我是通过绘制两个圆形成圆环(黄色大圆在下面,白色小圆在上面)的效果。代码如下
private void drawZoomRing(Canvas canvas) {
mPaint.setShader(null);
mPaint.setStrokeWidth(0);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(ringColor);
canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,ringWidth/2,mPaint);//外圆大圆
mPaint.setColor(Color.WHITE);
canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,minRingCenterWidth/2,mPaint);//肉圆小圆
}
这里是通过控制大圆和小圆的半径大小,从而来控制圆环的位置和大小。然后只需要通过ValueAnimator
动态改变ringWidth
和minRingCenterWidth
的值然后invalidate()
,即可以实现第一部分的圆环放大缩小效果。
2. 圆弧转动缩小动画
这里其实就是处于不同圆下的两条圆弧,在做旋转和长度变化动画。
我们先来看一下圆弧的方法:
canvas.drawArc(圆弧所在的圆的正方形框,开始角度,跨越角度,是否要连接圆心,mPaint);
那么我们就可以通过动态改变开始角度和跨越角度来实现圆弧的移动和长度变化,这里的圆弧也是有3个关键帧,圆弧长度初始状态(外弧90度,内弧2度)->圆弧长度达到180度->0度消失。
实现代码如下:
private void drawArcLine(Canvas canvas) {
mPaint.setColor(ringColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);//线的两边圆头模式
mPaint.setStrokeWidth(getMeasuredWidth()/40);//通过画笔的宽度来控制圆弧的宽度
canvas.drawArc(mRectCenterArc, centerArcEndAngle-centerArcAngle,centerArcAngle,false,mPaint);//画内弧
mPaint.setStrokeWidth(getMeasuredWidth()/25);
canvas.drawArc(mRectOutSideArc,outSideArcStartAngle,outSideArcAngle,false,mPaint);//画外弧
}
细心的小伙伴可能已经发现了,画外弧和内弧的参数还像有点不太一样。因为内弧是顺时针旋转,外弧是逆时针旋转的,为了保证弧转动到某一点后不再移动,我们做了处理,内弧以逆向的思维来做。(可以编写代码试下这么做有什么好处)
3. 太阳出现以及旋转
我们先分析一下太阳出现以及旋转动画下太阳的组成成分,可以发现太阳由以下二部分组成:两个正方形和一个圆形
代码实现如下:
private void drawSun(Canvas canvas) {
mPaint.setStrokeWidth(0);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(ringColor);
mPaint.setShader(mFlowerLinearGradient);//设置颜色渐变
canvas.save();
canvas.rotate(sunRotateAngle,getMeasuredWidth()/2,getMeasuredHeight()/2);
canvas.drawRect(mRectFSunFlower,mPaint);
canvas.rotate(45,getMeasuredWidth()/2,getMeasuredHeight()/2);//第二个正方形比第一个正方形多旋转45度
mPaint.setShader(mFlowerRotateLinearGradient);
canvas.drawRect(mRectFSunFlower,mPaint);
canvas.restore();
mPaint.setShader(null);
canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,sunWidth/2,mPaint);//画圆
}
这里我们可以先画第一个绿色的正方形,然后画第二个红色的正文形,这里通过一个变量sunRotateAngle
来控制两个正方形的旋转,从而实现太阳的旋转,第二个红色的正方形旋转总是比第一个多45
度,从而错开,形成8
角太阳花边的效果。最后绘制圆形。
可以看到我们在上面绘制正方形的时候为Paint
画笔设置了shader
为LinearGradient
。为什么要设置这个呢?我们可以看到两个正方形并不是纯色的,而是一个渐变色。绿色正方形是从左上角渐变至右下角(这样↘),红色正方形是从上到下渐变。
通过分析,动画的实现就很明了。一开始的放大缩小动画,需要控制圆的半径和正方形的长宽,即可以实现。旋转动画控制sunRotateAngle
来旋转两个正方形。
4. 太阳阴影
太阳的阴影就很简单了,聪明的你一下子就可以猜到用椭圆就可以很轻松的实现。
private void drawSunShadow(Canvas canvas) {
mPaint.setColor(sunShadowColor);
mPaint.setStyle(Paint.Style.FILL);
mRectFSunShadow.set(getMeasuredWidth()/2-sunShadowWidth/2,getMeasuredHeight()- sunShadowHeight,
getMeasuredWidth()/2+sunShadowWidth/2,getMeasuredHeight());//设置椭圆范围
canvas.drawOval(mRectFSunShadow,mPaint);//绘制椭圆
}
给定一个矩形可以确定一个唯一的椭圆,因此,我们可以通过固定矩形的高度,通过变量改变矩形的宽度来控制椭圆的宽度,从而实现拉长收缩动画。
5. 白云动画
刚看到白云效果的时候,很多小伙伴都要晕了吧。这要怎么实现?这白云效果好恶心啊。对,如果你直接使用路径来画的话,是有点恶心,而且也很难实现移动放大动画,动的时候圆颜色还会变呢!但是如果我们换个思路呢?
我们通过观察动画可以发现,白云是由5个圆实现的,然后对5个圆的位置进行适当的摆放。然后从底部向上缓缓出现,并且半径逐渐变大。同时在绘制的时候进行截取黑色选框部分。就可以实现我们的白云效果了。
private void drawCloud(Canvas canvas) {
//CircleInfo用于记录每个圆的信息,圆心、半径、是否可见
mPath.reset();
mPaint.setShader(mCloudLinearGradient);
if (mCircleInfoBottomOne.isCanDraw())
mPath.addCircle(mCircleInfoBottomOne.getX(),mCircleInfoBottomOne.getY(),mCircleInfoBottomOne.getRadius(), Path.Direction.CW);//左下1
if (mCircleInfoBottomTwo.isCanDraw())
mPath.addCircle(mCircleInfoBottomTwo.getX(),mCircleInfoBottomTwo.getY(),mCircleInfoBottomTwo.getRadius(), Path.Direction.CW);//底部2
if (mCircleInfoBottomThree.isCanDraw())
mPath.addCircle(mCircleInfoBottomThree.getX(),mCircleInfoBottomThree.getY(),mCircleInfoBottomThree.getRadius(), Path.Direction.CW);//底3
if (mCircleInfoTopOne.isCanDraw())
mPath.addCircle(mCircleInfoTopOne.getX(),mCircleInfoTopOne.getY(),mCircleInfoTopOne.getRadius(), Path.Direction.CW);//顶1
if (mCircleInfoTopTwo.isCanDraw())
mPath.addCircle(mCircleInfoTopTwo.getX(),mCircleInfoTopTwo.getY(),mCircleInfoTopTwo.getRadius(), Path.Direction.CW);//顶2
canvas.save();
canvas.clipRect(0,0,getMeasuredWidth(),getMeasuredHeight()/2+getMeasuredWidth()/7f);//截取黑色框部分
canvas.drawPath(mPath,mPaint);
canvas.restore();
mPaint.setShader(null);
}
白云颜色变幻效果也是通过Shader
实现的。在这里通过Path
添加各个圆进路径,然后通过Shader
绘制整个路径。
6. 白云阴影
代码实现如下:
private void drawCloudShadow(Canvas canvas) {
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setColor(cloudShadowColor);
mPaint.setAlpha(cloudShadowAlpha);
canvas.save();
canvas.clipRect(0,getMeasuredHeight()/2+getMeasuredWidth()/7f,getMeasuredWidth(),getMeasuredHeight());//截取多余部分
mRectFCloudShadow.set(getMeasuredWidth()/2-finalSunWidth/2,getMeasuredHeight()/2-finalSunWidth/2,getMeasuredWidth()/2+finalSunWidth/2,getMeasuredHeight()/2+finalSunWidth/2);
mCloudShadowPath.reset();
mCloudShadowPath.moveTo(mCircleInfoBottomOne.getX(),getMeasuredHeight()/2+getMeasuredWidth()/7f);//白云底部一位置
mCloudShadowPath.arcTo(mRectFCloudShadow,15,45,false);
canvas.drawPath(mCloudShadowPath,mPaint);
canvas.restore();
mPaint.setAlpha(255);
}
白色阴影的实现其实不算复杂,我们需要通过太阳的圆勾画出一条圆弧(arcTo()
),然后与白云底部1/5
处的点连线形成一个伪扇形,然后截取掉超过白云的部分,渐变效果通过变量cloudShadowAlpha
改变画笔的透明度即可。
通过以上几个步骤的了解,我们已经掌握了如何对动画进行分解,动画的每个部分如何进行绘制(在绘制动画时,应先找出关键帧或者通过动画最终画面反推动画过程)。剩下的就是,加上属性动画,控制每个动画的播放时机。这就不作具体的讲解,大家可自行查看源码。
源码戳我:github