实现效果
360手机桌面的页面切换效果看上去好像蛮酷炫的,大致效果如下(细节略有些不同)
于是研究了一下怎么在安卓上实现。
大致思路
这玩意儿的难点在于,如何用平滑曲线,把两个圆连接起来。
网上有一些文章,介绍了一些方法,比如这里,但是看上去不是很平滑,视觉效果貌似差一些。
个人认为更加好看一点的平滑方法是这样:
- 取两圆圆心连线上的一点A,此点离两圆心距离与两圆各自半径成正比。
- 找到此点到两圆的外切点。
- 以两圆圆心连线一侧的两个切点(分别在两个圆上的)为端点,以步骤1中的点A为控制点,做二阶贝塞尔曲线。
二阶贝塞尔曲线,画起来很麻烦吧?。。。No no, 安卓sdk里面画贝塞尔曲线的函数(Path类下quadTo())都写好了,调用一下就行。
下一步就是要确定两个圆的位置和大小。ViewPager的 OnPageChangeListener的回调 onPageScrolled(int position, float positionOffset, int positionOffsetPixels) 给出了当前的position(位置的index)和positionOffset(滑动切换的进度,在0到1之间),所以我们需要根据这两个参数来确定两个圆的位置。
背景上不变的有几个圆,等间距分布,它们的半径就是最大半径R_max。很显然:背景圆的圆心,和这两个园的圆心都应该在一条水平线上。一下简称第一个圆形C1(circle1),第二个圆形 C2.
1 当positionOffset是0的时候,C1应该和第position个背景圆重和(半径=R_max),C2半径是0,位置可以放在C1的圆周上。
2 当positionOffset是0.5的时候,C1的圆心在第position个背景圆的圆心上,C2的圆心在第position+1个背景圆的圆心上,同时两圆半径都是R_max/2.
3 当positionOffset是0到0.5之间的时候,两圆的圆心和半径分别根据positionOffset做线性变化,使得 1,2满足。也就是C1圆心一直不变,半径变小;C2在水平移动,同时半径变大。
4 当positionOffset是0.1的时候,C1半径为0,位置在C2圆周上,C2和第position+1个背景圆重和(半径=R_max)。
这样就基本实现了从一个位置到下一个位置的滑动:
show me the code
源码地址: https://github.com/nanyi5452/viewpagerDotIndicator
或者使用gradle直接引入:
allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
}
compile 'com.github.nanyi5452:viewpagerDotIndicator:4c0a7bcf83'
简单解释:
PointF p1,p2; 存储C1,C2的圆心位置。
float r1,r2; 存储C1,C2的半径。
Path path; 存储连接C1,C2的平滑曲线。
PointF[] fixedLabels; 存储背景圆的位置
update(final int position, final float progress) 函数根据 position, 和 positionOffset来计算C1,C2以及平滑曲线。注意这些计算放到后台线程执行,计算完毕后通知postInvalid()来绘制。
如何使用:
VpIndicator indicator= (VpIndicator) findViewById(R.id.vp_indicator);
indicator.resetTotalCounts(4); // 设置页面个数
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
indicator.update(position,positionOffset);
}
...
});