看到ios版上qq刷新效果像水滴,然后自己也想着去实现这样的效果,这篇文章暂时没有介绍下拉刷新的效果,只是单独用一个控件来实现这样的水滴效果。
效果图如下:
一、总体思路
1、画两个圆形,其中一个就是上面的大圆,还有一个是下面的小圆,大圆和小圆不断变小,大圆的位置保持不变,小圆的位置不断向下移动,即圆心不断下移。
2、画两边的曲线,这时候用到贝塞尔曲线去画。
3、用属性动画实现动态的效果。
二、代码实现
1、找出画曲线的几个关键点。
其实我是在第一张图的基础上,再在上面分别画两个圆,就可以得到第二张图了。关键是画出第一张图。
(1)在这里,p1,p2,p3,p4,这4个点分别对应两个圆的两边的点,即p1到p2就是圆的直径。p3和p4同理,那么就很容易确定这四个点的坐标了。
(2)然后c1和c2是分别控制p1到p3、p2到p4的曲线,是贝塞尔曲线的控制点。它们的横坐标对应的是p3,p4的横坐标(相等),纵坐标取两个圆心距离的一半。这样画出这个静态的图片就不难了。
(3)画上下两个圆进去,就会变成第二张图的效果。
2、在构造方法中调用init()初始化一些基本的变量
1
2
3
4
5
6
7
8
9
10
|
private void init(context context, attributeset attrs) { drawfilter = new paintflagsdrawfilter( 0 , paint.anti_alias_flag | paint.filter_bitmap_flag); paint = new paint(); paint.setcolor(fillcolor); // 转换为像素单位 bigradius = dip2px(context, bigradius); smallradius = dip2px(context, smallradius); distance = dip2px(context, distance); } |
3、在ondraw()方法中画水滴效果
要注意的是path需要重新new, 贝塞尔曲线的绘制,用到是path.quadto这方法。具体可以看代码。
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
|
@override protected void ondraw(canvas canvas) { // 必须重新new,不然路径会重复,我之前就是这样 path = new path(); // 把画布移到中心 canvas.translate(width / 2 , height / 2 ); // 从canvas层面去除绘制时锯齿 canvas.setdrawfilter(drawfilter); // 当前的两个圆心的距离 currentdis = distance * fraction; // 计算当前大圆的半径 float bigradius = this .bigradius - currentdis / bigpercent; float smallradius = 0 ; if (currentdis > 5 ) { // 距离大于5才改变小圆的半径 smallradius = this .smallradius - currentdis / smallpercent; } // 大圆两边的两个点坐标 leftx = -bigradius; // 大圆当前的半径 lefty = righty = 0 ; rightx = bigradius; // 大圆当前的半径 // 小圆两边的两个点坐标 leftx2 = -smallradius; // 小圆当前的半径 lefty2 = righty2 = currentdis; rightx2 = -leftx2; // 小圆当前的半径 // 两个控制点 controlx1 = -smallradius; // x坐标取小圆当前的半径大小 controly1 = currentdis / 2 ; // y坐标取两个圆距离的一半 controlx2 = smallradius; // x坐标取小圆当前的半径大小 controly2 = currentdis / 2 ; // y坐标取两个圆距离的一半 path.moveto(leftx, lefty); path.lineto(rightx, righty); // 用二阶贝塞尔曲线画右边的曲线,参数的第一个点是右边的一个控制点 path.quadto(controlx2, controly2, rightx2, righty2); path.lineto(leftx2, lefty2); // 用二阶贝塞尔曲线画左边边的曲线,参数的第一个点是左边的一个控制点 path.quadto(controlx1, controly1, leftx, lefty); // 画大圆 canvas.drawcircle( 0 , 0 , bigradius, paint); // 画小圆 canvas.drawcircle( 0 , currentdis, smallradius, paint); // 画path canvas.drawpath(path, paint); } |
4、用属性动画,实现动态的效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/*** 执行属性动画,实现水滴的效果 */ public void perforanim() { valueanimator valanimator = objectanimator.offloat( 0 , 1 ); valanimator.addupdatelistener( new animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { fraction = ( float ) animation.getanimatedvalue(); postinvalidate(); } }); valanimator.setduration(duration); valanimator.start(); } |
5、重写onmeasure()方法,处理wrap_content情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@override protected void onmeasure( int widthmeasurespec, int heightmeasurespec) { super .onmeasure(widthmeasurespec, heightmeasurespec); /* * 处理为wrap_content情况,那么它的specmode是at_most模式,在这种模式下它的宽/高 * 等于spectsize,这种情况下view的spectsize是parentsize,而parentsize是 * 父容器目前可以使用大小,就是父容器当前剩余的空间大小, 就相当于使用match_parent一样 的效果,因此我们可以设置一个默认的值 */ int widthspectmode = measurespec.getmode(widthmeasurespec); int widthspectsize = measurespec.getsize(widthmeasurespec); int heightspectmode = measurespec.getmode(heightmeasurespec); int heightspectsize = measurespec.getsize(heightmeasurespec); if (widthspectmode == measurespec.at_most && heightspectmode == measurespec.at_most) { setmeasureddimension(width, height); } else if (widthspectmode == measurespec.at_most) { setmeasureddimension(width, heightspectsize); } else if (heightspectmode == measurespec.at_most) { setmeasureddimension(widthspectsize, height); } } |
6、其它的一些方法实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@override protected void onlayout( boolean changed, int left, int top, int right, int bottom) { super .onlayout(changed, left, top, right, bottom); if (changed) { width = right - left; height = bottom - top; } } /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) */ public int dip2px(context context, float dpvalue) { final float scale = context.getresources().getdisplaymetrics().density; return ( int ) (dpvalue * scale + 0 .5f); } |
7、字段的定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private final int fillcolor = 0xff999999 ; // 填充颜色 private paint paint; private int width = 100 , height = 300 ; // 默认宽高 /* 两个圆心的最大距离 / private int distance = 60; /* 当前两个圆心的距离 / private float currentdis = 0; private float bigradius = 20;// 大圆半径 private float smallradius = 10;// 小圆半径 private float controlx1, controlx2, controly1, controly2;// 两个控制点的坐标 private float leftx, lefty, rightx, righty;// 大圆两边的两个点的坐标 private float leftx2, lefty2, rightx2, righty2; // 小圆两边的两个坐标 drawfilter drawfilter; path path; /* 由属性动画控制,范围为0-1 */ float fraction = 0 ; // 比例值 /* 大圆半径变化的比例 / private final int bigpercent = 8 ; /* 小圆半径变化的比例 / private final int smallpercent = 20 ; // 动画的执行时间 private long duration = 3000 ; |
三、总结
一种动画效果,应该先分析它的静态的实现,然后添加动态的效果,这样就比较容易实现它的动画效果了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/LHavoc/article/details/54603197