炫酷ViewPager指示器效果(全面解析)
作者博客
https://keep2iron.github.io/
目录
前言
准备
效果展示
布局分析
ViewPager的效果实现
ViewPager布局设置
ViewPager代码设置
BeizerIndicator
Beizer知识讲解
绘制
小球的面向对象分析
小球的状态分析
点击产生的涟漪效果
点击产生的位移动画
总结
1
前言
本文的源起是在有一天在网上看到的一个挺不错的一个效果而产生的一个想法,正好因为这段时间公司闲了下来,因此想着练习一下中定义view。
本文以尽可能通俗的语言,让大家理解整个的绘制过程,尽量不粘贴代码(因为我认为思路往往比代码更重要)。还有就是可能对数学无感的人和不太友好。
这篇文章你将会学到什么?
学到一些自定义绘制中的一些技巧。
学习Bezier的一些相关知识。
利用面向对象更好的去解决一些复杂的问题。
2
准备
效果展示
布局分析
界面由 ViewPager + 自定义指示器
ViewPager的间隔效果.
小球能够和ViewPager联动不断变化
ViewPager效果实现
我们看到上面的是一个可以滑动的ViewPager,但是默认的ViewPager是一页只能显示一个Item的,因此经过多方查找,我找到了以下方法可以实现这个效果:
ViewPager的布局
android:clipToPadding=”false”
就是说控件的绘制区域是否在padding里面的,true的情况下绘制的区域不包括了Padding占据的那部分
蓝线的部分即为我们绘制的区域,因为设置了true,而且默认是true,而我们想要把绘制区域在padding中那么就要将这个属性设置为false了。
android:overScrollMode=”never”
我们滑动的时候经常可以看到谷歌很多滑动的原生控件上都具有一个明显的特征(5.0以上)
有一个阴影对吧,这个效果默认是有的,这个效果的含义就是滑动的时候可以滑出区域外,有一个简单的回弹效果,如果不想要这个阴影,也就是这个回弹,那么可以将这个属性设置成never即可。
再来就是设置padding值。但是还有没结束。
ViewPager的代码设置
设置viewpager缓存页数,因为默认ViewPager只加载一页,因此这里设置成三个,让其全部加载。
设置setPageMargin是为了控制每一页Page之间的大小。产生一个间隔的效果。这里和padding的不同在于Padding是设置了边界,也就是第一页左边的那个大小,因此这里是设置每一页之间的大小的。
经过了上面的配置,我们的ViewPager就可以完成了下面所示的效果
BezierIndicator
上面我们一步一步实现了ViewPager的效果,接下来的重头戏就是如何去实现小球?
细心的我们可以发现这个球一共经历了5个状态,并且动画是通过形状的变化加上画布的位移而产生的
下面我们来一步一步来进行Beizer的绘制工作
Beizer知识讲解
在开始画之前我们先来看一下这个Beizer的相关api,关于Bezier的数学原理在这里不会详细阐述(网络上有大量的说明,如果有兴趣可以自行查阅)。
在具体的讲解之前,我们来看看如果利用Bezier画圆。
下面这个图利用了PS中的钢笔工具进行了绘制,也就是说这个圆是由4条Bezier曲线组成的。
利用如下代码即可完成绘制
mPath.cubicTo(x1,y1,x2,y2,x3,y3);//x1,x2都是控制点,x3是终点
P1 P2 P3 P4分别是圆上的4个端点,它们连线的圆点即为该点的控制点。M为控制点到圆点的距离,那么M值的大小直接影响了曲线变化!
代码如下:
代码中,先将Path移动到p1点
mPath.moveTo(p1.x, p1.y)
然后利用p1右边的控制点,p2下方的控制点,以及p2点的坐标即可画出p1到p2的曲线
mPath.cubicTo(p1.rightX, p1.rightY, p2.bottomX, p2.bottomY, p2.x, p2.y);
其他的跟上面的写法是一样的,就不再赘述。
p1,p3的控制点由于是在水平方向上的,于是控制点的计算使用了如下的代码:
根据资料查询的结果: M = 0.5522847498时,曲线的绘制就是一个圆弧。
http://spencermortensen.com/articles/bezier-circle/有一篇文章专门讲解了M点计算的原理。
3
绘制
小球的面向对象分析
首先将小球进行了抽象,抽取成一个单独的类BerizerCircle,然后画出小球的时候需要一些控制点的坐标。在这里我们分为了水平端点(HorizontalPoint)和垂直端点坐标(VerticalPoint)。
那么问题来了什么是水平端点和垂直端点呢?哈哈哈,其实这里我只是自己这么称呼的而已,不要介意。
水平端点即为在水平方向上具有控制点的点,对应了我们刚才图上所示的P1,P3的两个端点,那么垂直端点也就是P2、P4了
下面来看一下其中的HorizontalPoint.java的构造函数
这个构造函数的意思就是,通过设置端点的坐标(x,y),以及端点到控制点的距离(M),即可得到端点坐标和两个控制点的坐标。那么VerticalPoint类的构造函数的思路也就不用多说了。
那么构造端点方法可以通过如下的方式进行了
如果上述不是看的很懂那么再一个图来理解一下
那么利用R,M我们可以把图中所有的端点和控制点进行了计算。
绘制流程
下面我们来看一下BezierCircle下面这个核心的函数,思路就是通过滑动的百分比,来控制图形的形态。
上图就是一个小球变化的一个趋势图,整体的一个绘制思路和流程在上图可以进行了完整的体现。
那么下面我们来具体分析一下,小球在各个滑动区间中具体是如何变化的吧!
平移距离在(0,0.2]的范围内时
p2点在这个状态的中从(R,0)变成了(9/5 R,0),*positionOffset / 0.2f为这个区间(0,0.2]的变化率。
x = R + 4 / 5.0f R P,P是变化率也就是positionOffset / 0.2f
下面是代码:
如果对上面的公式或者注释你还是无法有一个直观的理解,可以结合下面的图来进行进一步的加深
在buildCircle1中我们做的就是将P2点的坐标不断进行水平移动,从而让小球从状态1变化到了状态2了。
平移距离在(0.2,0.5]的范围内时
在这个过程中我们需要将小球进行变化成一个椭圆
通过下图我们可以看到如果单纯将P2点垂直方向上的控制点的距离进行增大,图形的右边就更加的椭圆了,因为要对称,所以同时改变P2点和p4的垂直方向上M的距离,就可以让这两边的曲线更加接近椭圆。
但是还有一个问题,图形目前是不对称的,在上一个状态中我们移动了p2点的坐标R->9/5R,因此要让P2和p4对称,因此p1和p3也要同时进行移动,让其两点的x轴的坐标移动到新的中心点坐标。
新的中心点在哪里?
我们重新来看下坐标
P1(0,R),P2(5/9R,0),P3(0,-R),P4(-R,0)
新的中心点的x轴的坐标为
x = (p4.x + (p2.x - p4.x) / 2) * P
我们可以看到公式中有一个除2的操作,为什么要除2呢?
可以看到正方形,通过将右边的左边的点进行平移1个单位后,中心点从(0,0)变化至(0.5,0)。通过这个公式p4.x + (p2.x - p4.x) / 2,这里我们将p2.x = 2,p4.x = -1,通过计算得出了0.5,因此这就是为什么要这么写的原因了。
下面是代码:
平移距离在(0.5,0.8]的范围内时
在这个过程中我们需要将椭圆变成如我们状态2那样子的有一头比较尖的圆形。
p2.x从9/5R 变化至 R
p4.x从-R 变化至 -9/5R
p1.x从((R + 9/5R) / 2.f - R)/2 变化至 0
p3.x从((R + 9/5R) / 2.f - R)/2 变化至 0
恢复成圆形(0.8,0.9]
p4.x 从-9/5R 变化至 -R。并且重置一下p1、p2、p3的坐标
让小球进行回弹起来吧[0.9,1.0]
为社么这里使用了sin函数呢?
在sin函数中x在[0,π/2]y轴的变化过程是[0,1],x在[π/2,π]之间y轴的变化过程是[1,0],这个y轴的变化过程正好满足我们这里回弹过程的变化率!!!因此使用sin作为这里控制回弹效果的产生是再合适不过的了。
让小球进行平移
前面我们都是通过改变p1,p2,p3,p4的坐标来改变小球的形状,然而还要进行添加位移,这样才能形成一个完整的动画,我在这里有两个思路
通过drawPath方法,不断地改变小球的x,y的位置来进行,然而这个的代价就是所有的坐标点都需要进行变化加上平移距离,得出最后的点的坐标
通过Canvas的translate方法,移动画布来达到我们这里平移动画的产生效果,显然,这一种方法我们不需要进行坐标转变即可完成动画效果,因为本控件也是采用了这一种方案。
mStartPoint.x是计算的小球的起始位置。
mStartPoint = new Point(span + R, h / 2);
translateMoveSize是小球移动的一个单位的距离,mPercent是移动的百分比,mViewPagerPosition当前Page的位置。mStartPoint.x 先让小球平移到初始位置,然后加上 (span + 2 R) mViewPagerPosition当前页滑动页的距离,然后加上滑动的百分比在滑动距离上的比值,计算出最后的平移距离。
span的计算是通过下面的公式进行等分的计算出来的距离。
绑定ViewPager进行联动
监听ViewPager的滑动事件自然是通过addOnPageChanageListener进行滑动的监听。在这里我们使用了如下的方法:
在这里我们先来看一下ViewPager数值的变化规律:
这是从左向右滑动的时候,从第0页向第一页滑动position从0变化到1,positionOffset:0.0到0.999最后在完成翻页了之后变成了0.0.
这是从右向左滑动的时候
这里是颜色的相关计算,mCurrentPosition是当前页的位置。
0到1页时,颜色值从a-b进行变化,变化率从0.0到0.9
1到0页时,颜色值从b-a进行变化,变化率从0.9到0.0,因此需要进行1 - positionOffset让其从0.0到0.9进行变化,才满足我们变化的颜色公式。
下面是颜色变化的计算函数
点击产生的涟漪效果
实际的原理是通过属性动画进行改变画笔画圆的半径,然后通过设置画笔的粗细程度来完成这一效果的实现。
在onDraw方法画出点击的产生的圆
在onTouchEvent进行点击效果的触发
通过两点之间的距离公式,判断是否在点击的区域范围内,然后通过startWave()方法进行显示点击的涟漪效果,通过startMoveBezierCircleByTouch方法进行从当前位置,跳转的指定的位置的平移变换。
通过改变R的半径以及画笔的粗细程度,即可完成了这一效果的绘制过程
点击产生的位移
上面我们看到点击后通过属性动画完成涟漪效果的显示,同样我们可以利用属性动画,让其模拟viewPager的参数的变化过程,这样之前的ViewPager函数就可以进行调用就行了。
因为ViewPager在0页到1页的过程中position是0,positionOffset是0-0.9直到当滑动完成后变成了0,1页到0页的过程中positionOffset是从0.9 - 0,position是0,因此这里就是模拟这样子的参数而编写代码。
4
总结
现在看一下之前的问题,看看心里有没有数
本篇的分析就到此为止了,后面我会抽时间不断的完善这个demo。
源码地址
https://github.com/keep2iron/BezierIndicator
在编写代码的过程中也并不是一帆风顺的,再次感谢广大的博主和开源作者,Android社区的繁荣离不开你们辛勤的劳动。下面是参考的博客地址和项目地址
http://blog.****.net/zanelove/article/details/50337623
http://www.jianshu.com/p/791d3a791ec2
https://github.com/Ulez/DropIndicator
今日推荐
Google推荐的图片加载库Glide:最新版使用指南(含新特性)
点赞与转发就是对我最大的支持!