自定义view实现超萌动感天气小太阳

前些天,在IXUS上看到一个很赞的动画,一下子就看对眼了,于是便决定用Android来实现一下,效果如下:

原图

设计在这里:Jana,点赞超棒的设计师

Android实现的效果如下:

android实现

现在开始分析如何实现这个动画:

image.png

当我们看到一个动画要实现的时候,很多朋友可能就直接照着开始实现了,其实这样是很难实现。我们在接触到一个新动画的时候,首先要对动画进行分解。例如本动画,我们进行分析后,可以把动画分为以下几个部分:

1. 圆环放大缩小消失效果:
圆环放大缩小效果

进行剖析后,我们可以发现这是一个圆环放大缩小的动画,有3个关键帧

关键帧1
关键帧2
关键帧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动态改变ringWidthminRingCenterWidth的值然后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. 太阳出现以及旋转
太阳出现以及旋转

我们先分析一下太阳出现以及旋转动画下太阳的组成成分,可以发现太阳由以下二部分组成:两个正方形和一个圆形

image.png

代码实现如下:

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画笔设置了shaderLinearGradient。为什么要设置这个呢?我们可以看到两个正方形并不是纯色的,而是一个渐变色。绿色正方形是从左上角渐变至右下角(这样↘),红色正方形是从上到下渐变。

通过分析,动画的实现就很明了。一开始的放大缩小动画,需要控制圆的半径和正方形的长宽,即可以实现。旋转动画控制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. 白云动画
白云动画

刚看到白云效果的时候,很多小伙伴都要晕了吧。这要怎么实现?这白云效果好恶心啊。对,如果你直接使用路径来画的话,是有点恶心,而且也很难实现移动放大动画,动的时候圆颜色还会变呢!但是如果我们换个思路呢?

a.gif

我们通过观察动画可以发现,白云是由5个圆实现的,然后对5个圆的位置进行适当的摆放。然后从底部向上缓缓出现,并且半径逐渐变大。同时在绘制的时候进行截取黑色选框部分。就可以实现我们的白云效果了。

image.png
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改变画笔的透明度即可。

image.png

通过以上几个步骤的了解,我们已经掌握了如何对动画进行分解,动画的每个部分如何进行绘制(在绘制动画时,应先找出关键帧或者通过动画最终画面反推动画过程)。剩下的就是,加上属性动画,控制每个动画的播放时机。这就不作具体的讲解,大家可自行查看源码。

源码戳我:github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容