android属性动画 呼吸,【MIUI动效】Android:会呼吸的悬浮气泡

原标题:【MIUI动效】Android:会呼吸的悬浮气泡

写在前面

这个标题看起来玄乎玄乎的,其实一张图就明白了:

ae82c409ed13dc0ee8a62b857bd88643.gif

悬浮气泡演示图

最早看到这个效果是 MIUI6系统升级界面,有很多五颜六色的气泡悬浮着,觉得很好看。可惜现在找不到动态图了。虽然 MIUI8更新界面也有类似的气泡,不过是静态的,不咋好看。

d3e56f8137633aa1118e85ba478c4ae4.png

MIUI8

再次见到这个效果是在 Pure天气这款软件中,可惜开发者不开源。不过万能的 Github上有类似的实现,于是果断把自定义 View部分抽出来学习学习。

170cc75f58351f74e98cb33134a9f4ba.png

Pure

怀着敬意放上原项目地址,很好看的一款天气 APP:

还是那句话,学习自定义 View没有什么捷径,就是看源码、模仿、动手。

具体实现先思考

在看源码之前,我自己想了一下该怎样去实现,思路如下:

自定义一个圆形 View,支持大小、颜色、位置等属性

浮动利用最简单的平移动画来实现

平移的范围通过自定义圆心的移动范围来确定

最后给动画一个循环就行了

虽然看起来比较简单,但是实现起来还是遇到不少坑。首先画圆一点问题都没有,问题出在动画上。动画看起来很迟钝,根本就不是呼吸效果,像哮喘一样。

所以不能用动画,就想到了不断重绘。于是仍然给圆心设置一个小圆,让圆心在小圆上移动,在这个过程中不断重绘,结果直接 Crash了,看了看 Log,发现是线程阻塞了,但是这里并没有开启子线程啊,一看,我去,主线程。

那这条路行不通,又想到用贝塞尔去做,结果突然想起来之前绘制阻塞了主线程,那开子线程绘制不就完了,Android View里面能开子线程绘制的不就是 SurfaceView。于是看了看作者源码,果然是自定义 SurfaceView。

400dbd698f2bcf209e58ef88bd430f16.png

早已看穿一切

关于 SurfaceView我只在以前学习的视频案例、撕MM衣服案例、还有手写板案例中遇到过,学的不是很深,加上本文它不是重点,所以就不详细说了,如果不了解这个或者想深入了解一下的话,可以点击文末的相关链接,这里只简单提一下比较重要的一点,也就是 SurfaceView跟 View的主要区别:

SurfaceView在一个新起的单独线程中重新绘制画面,而 View必须在 UI线程中更新画面。

这就决定了 SurfaceView的一些特定使用场景:

需要界面迅速更新;

对帧率要求较高的情况;

渲染 UI需要较长的时间。

所以综合来看,SurfaceView无疑是实现这类效果的最佳选择。

再分析

废话不多说,来分析一下思路。

1、首先光从界面上能看到就是圆,且是能浮动的圆,所以不管能不能动,先得把圆画出来。要是我的话,我直接就拿着 Paint在 Canvas上开画了。在源码中开发者单独抽取了绘制圆的类,但这个类的作用不仅仅是绘制圆,后面我们再说。

2、其次就是自定义 SurfaceView,我们需要把画出来的圆放到 SurfaceView中。而自定义 SurfaceView需要实现 SurfaceHolder.Callback接口,就是一些回调方法。同时需要开子线程去不断刷新界面,因为这些圆是需要动起来的.

3、另外重要的一点就是,SurfaceView在渲染过程中需要消耗大量资源,比如内存啊、CPU啊之类的,所以最好提供一个生命周期相关的方法,让它和 Activity的生命周期保持一致,尽量保证及时回收资源,减少消耗。

4、最后需要提一点的是,SurfaceView本身并不需要绘制内容,或者说在这里它的主要作用就是刷新界面就行了。就好像在放视频的时候,只需要刷新视频页面就行,它并不参与视频具体内容的绘制。

所以这样来说的话,我们最好定义一个绘制过程的中间者,主要作用就是把绘制出来的圆放在 SurfaceView上,同时也能做一些其他的工作,比如绘制背景、设置尺寸等。这样做的好处就是能让 SurfaceView专心的做一件事:不断刷新,这就够了。

OK,总结一下我们到底需要哪些东西:

专门绘制圆的类

刷新过程中的子线程

实现 SurfaceHolder.Callback接口方法

提供生命周期相关方法

一个绘制过程的中间对象

多提一句,最后的绘制中间者也可以不定义,全部封装到自定义 SurfaceView中,但是从我实践来看,我最后不得不单独抽取出来,因为 SurfaceView类看起来太乱了,这也是源码中的实现方式。

7fde3dd80f432374159035ccdf7b2213.png

23333

后动手

Talk is cheap , Show me the code .

1、画圆

既然要画圆,我们肯定要设置一些圆的基本属性:

圆心坐标

圆的半径

圆的颜色

由于需要圆动起来,也就是说它会偏移,所以要确定一个范围。范围确定了,就需要指定它该怎么变化,因为我们要求它缓慢而顺畅的呼吸,不能瞬间大喘气,也就是它不能瞬间移动偏移量那么多,所以最好指定它每一步变化多少,那就需要下面这两样东西:

圆心偏移范围

每一帧的变化量

额外的,因为移动是每次都需要变的,下一次变化时不能重新开始,所以我们要记录当前已经偏移的距离,然后根据一个标志位不断呼气...吐气...呼气...吐气,所以需要:

当前帧变化量

标志位

好了,看构造函数吧:

e6e4423b0f4a49d34d538471c8ec8488.png

好了,构造好了圆就要开始绘制圆了。之前说到,这个类的作用不仅仅是绘制圆,还要不断更新圆的位置,也就是不断重绘圆。更直接地说,我们需要绘制出不断偏移的每一帧的圆。

步骤如下:

确定当前帧偏移位置

根据当前帧偏移位置计算圆心坐标

设置圆的颜色透明度等属性

真正的开始绘制圆

代码如下,结合上面的步骤和代码中的注释应该很容易看懂:

26d31603f375b13b9e3400808e9785a6.png

其中的 convertAlphaColor()方法是个工具方法,作用就是转化一下颜色,不必深究:

429061dfaf6c04e62230d36600b8210f.png

到此,画每一帧圆的工作我们就完成了。

2、绘制中间者对象

现在来说这个特殊的中间者对象,前文说了,单独抽取这个类不是必须的。但最好抽取一下,让 SurfaceView专心做自己的事情。在这个中间者对象中我们做两件事情:

绘制背景

绘制悬浮气泡

先来看绘制背景。为什么需要绘制背景呢,因为 SurfaceView 本身其实是个黑色,从我们日常看视频的软件中也能发现,视频播放时周围都是黑色的。有人问为什么不能直接在布局中设置呢?当然可以直接设置啊,不过要记得添加一句 setZOrderOnTop(true),不然会把之后绘制的悬浮气泡遮挡住。

在这里就来绘制一下吧,因为源码中给出了一个渐变色的绘制,我觉得挺好玩,学一学。直接看代码吧,都是模板代码,没啥好解释的,简单的 get/set再画一下就好了:

66c1c5c55ce4e0d159694b8781157126.png

上面代码就一点需要注意,渐变最少需要两种颜色,不然没法渐变,这个很好理解吧,不再多解释了。现在我们来画气泡,步骤如下:

设置一下圆的范围,一般都为全屏

根据圆的构造方法添加多个圆

绘制添加的这些圆

直接来看代码,其实也很简单:

2f214535adffdb7c85605dddca0bf7b9.png

从代码中看出,已经将所有添加的圆放到集合里,然后遍历集合去画,这就不用添加一个画一个了,只需统一添加再统一绘制即可。

既然背景绘制好了,气泡也绘制好了,那就到了最后一步,需要提供方法让 SurfaceView 去添加背景和气泡:

00dfbb2e4fdd51cc6cb793d8ee91ed6f.png

到此,这个绘制中间者对象就完成了。

3、自定义 SurfaceView

终于到了重要的 SurfaceView部分了,这部分不太好描述,因为最好的解释方式就是看代码。

首先自定义 FloatBubbleView继承于 SurfaceView,看一下简单的变量定义、构造方法:

67dd5d98fbe1325ce167547d60328810.png

这里其他的内容都比较好理解,重点提两个变量:

c7bcb9b3ffdde697efd588b78f45f14a.png

这是什么意思呢,开始我也不太理解,那换个思路,大家还记得 ListView中的ViewHolder么,这个 ViewHolder其实就是用来复用的。那 SurfaceView中也有个SurfaceHolder,作用可以看做是相同的,就是用来不断复用不断刷新界面的。

那这里的这两个变量是干什么的呢?就是相当于 当前刷新的中间者对象和 上一次刷新的中间者对象。

那获得这两个对象有什么用呢?注意看,还有个 curDrawerAlpha变量,顾名思义,当前的透明度。

三者结合在一起,再加上一个这样的小循环:

692e6dc4fae341a8306ffcd8525f034f.png

那这又有什么作用呢,别急,先看下面两张对比图,分别设置 curDrawerAlpha += 0.2f和 curDrawerAlpha += 0.8f:

模拟器太卡,将就着看

f4ab34348f62c6ef10ce0f2f608c3837.gif

0.2f

再看 0.8f ,从暗到明显然快了点:

94d630ff60b7383393a3c281ad66e0fe.gif

0.8f

现在知道作用了么,就是实现界面从暗到明的效果。那为什么需要这样的效果呢,我尝试过去掉这个,发现绘制的时候会偶尔出现闪黑屏的现象,黑色刚好是 SurfaceView的本身颜色,加上这个效果就不会出现了。

好,接下来看重中之重的绘制线程方法,为了方便我单独抽取了线程类,并将 run方法按照不同的功能分成好几个方法,注释写的很清晰:

94eab23d0dca66dc345f32c68e589880.png

15d57bb2d50accf33588f3786371357f.png

知道看这种代码很枯燥,但不能急。首先这里有三种状态:正在绘制、活动、退出。其中活动是一种中间状态,指既没有活动又没有被销毁。在回调类中需要根据这种状态进行绘制线程的控制。

那就来看回调方法:

877967e73ffd6139985587f46634962a.png

可以看到,在销毁的时候绘制线程是在等待状态。

然后就是一些生命周期相关方法了,也很简单,就是设置相关状态:

eb7ffa470da69a1b063fc79dfc5d55b5.png

最后就是提供方法,给这个自定义的 SurfaceView 设置中间绘制者对象了:

22afb95bab74d94382653ec7ed482914.png

到此,自定义 FloatBubbleView就完成了,代码很长,建议直接看文末的源码。

看结果

好了, 现在只要在 Activity 中这样:

216c37d2073d34306972a9c03fb0ca88.png

这样就大功告成了!效果图再贴一下吧,颜色大小位置都可以定义:

14a6db83aaca5b7442d877b388baee1b.gif

悬浮气泡演示图

后话

虽然效果实现了,但是我并没有将设置气泡的方法暴露出来,只写死在 BubbleDrawer 中:

ee6b816a6e4f69d3d4c3abab06f940c3.png

开始我确实抽取了方法,提供给 Activity,结果发现 Activity中的代码太难看。另一方面因为 SurfaceView消耗资源太多,我们应该不会在主要界面大量使用它,所以我觉得写死就够了,必要的时候动一动写死的数据就行了。

还有一点就是,虽然效果很好看,但是确实消耗资源很大,有时候会很卡,不知道还有没有可以优化的地方,建议只在简单的页面,比如关于软件的页面用这样的效果,其他的主页面还是算了吧。

参考资料

项目源码

责任编辑:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值