###效果演示
华为手机上的时钟
ViewPagerIndicator
###简单描述
-
灵感来自于华为手机上的多款自带APP,都有这种圆点指示器(如图1的时钟),感觉很6,就仿造弄了个库出来。
-
配合ViewPager使用,每滑到另一页并停止时,随机改变圆点颜色,内置了5种颜色。
-
如果页面没变,停止时颜色不会改变,圆点的颜色也可以自行设置.
###技术分析
####ViewPager滑动过程分析
既然是配合ViewPager
使用的,首先得清楚滑动时都发生了啥,那我们就添加如下的打印代码:
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Log.d("onPageScrolled", "position =" + position + " \n offset =" + positionOffset);
}
@Override
public void onPageSelected(int position) {
Log.d("onPageSelected", String.valueOf(position));
}
@Override
public void onPageScrollStateChanged(int state) {
switch (state) {
case ViewPager.SCROLL_STATE_DRAGGING:
Log.d("onPageScrollStateChanged", "SCROLL_STATE_DRAGGING");
break;
case ViewPager.SCROLL_STATE_IDLE:
Log.d("onPageScrollStateChanged", "SCROLL_STATE_IDLE");
break;
case ViewPager.SCROLL_STATE_SETTLING:
Log.d("onPageScrollStateChanged", "SCROLL_STATE_SETTLING");
break;
default:
break;
}
}
});
接着各种滑动,看看都输出了啥
先是滑到右边的页面
#####第1页滑到第2页
12-23 21:17:45.360 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_DRAGGING
12-23 21:17:45.360 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.05277778
12-23 21:17:45.370 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.11111111
12-23 21:17:45.390 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.18472221
12-23 21:17:45.410 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.27222222
12-23 21:17:45.420 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.37222221
12-23 21:17:45.430 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.37777779
12-23 21:17:45.430 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_SETTLING
12-23 21:17:45.430 12033-12033/sbingo.com.viewpagerindicator D/onPageSelected: 1
12-23 21:17:45.440 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.4513889
12-23 21:17:45.460 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.57916665
12-23 21:17:45.470 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.6805556
12-23 21:17:45.490 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.76666665
12-23 21:17:45.500 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.82916665
12-23 21:17:45.520 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.88055557
12-23 21:17:45.540 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.92083335
12-23 21:17:45.560 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.9458333
12-23 21:17:45.570 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.9638889
12-23 21:17:45.590 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.9777778
12-23 21:17:45.600 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.9875
12-23 21:17:45.620 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.9930556
12-23 21:17:45.640 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.99583334
12-23 21:17:45.650 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.9986111
12-23 21:17:45.670 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.0
12-23 21:17:45.780 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_IDLE
#####第2页滑到第3页
12-23 21:19:13.630 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_DRAGGING
12-23 21:19:13.630 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.018055558
12-23 21:19:13.650 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.049999952
12-23 21:19:13.660 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.0916667
12-23 21:19:13.680 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.14444447
12-23 21:19:13.700 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.20277774
12-23 21:19:13.710 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.27222228
12-23 21:19:13.730 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.35416663
12-23 21:19:13.750 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.44861114
12-23 21:19:13.750 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_SETTLING
12-23 21:19:13.750 12033-12033/sbingo.com.viewpagerindicator D/onPageSelected: 2
12-23 21:19:13.750 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.45694447
12-23 21:19:13.760 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.5569445
12-23 21:19:13.780 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.6541667
12-23 21:19:13.800 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.73333335
12-23 21:19:13.810 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.79999995
12-23 21:19:13.830 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.8527777
12-23 21:19:13.840 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.89305556
12-23 21:19:13.860 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.92361116
12-23 21:19:13.880 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.94722223
12-23 21:19:13.890 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.9638889
12-23 21:19:13.910 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.97638893
12-23 21:19:13.930 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.98472226
12-23 21:19:13.940 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.9916667
12-23 21:19:13.960 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.9944445
12-23 21:19:13.980 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.9972222
12-23 21:19:13.990 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.9986111
12-23 21:19:14.010 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =2
offset =0.0
12-23 21:19:14.140 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_IDLE
接着往回滑到左边的页面
#####第3页滑到第2页
12-23 21:21:43.460 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_DRAGGING
12-23 21:21:43.460 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.9708333
12-23 21:21:43.480 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.9319445
12-23 21:21:43.490 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.8833333
12-23 21:21:43.510 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.8152778
12-23 21:21:43.520 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.7513889
12-23 21:21:43.520 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_SETTLING
12-23 21:21:43.520 12033-12033/sbingo.com.viewpagerindicator D/onPageSelected: 1
12-23 21:21:43.530 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.73472226
12-23 21:21:43.540 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.60833335
12-23 21:21:43.560 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.4944445
12-23 21:21:43.570 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.4027778
12-23 21:21:43.590 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.32222223
12-23 21:21:43.610 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.25833333
12-23 21:21:43.620 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.20138884
12-23 21:21:43.640 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.1569444
12-23 21:21:43.660 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.122222185
12-23 21:21:43.670 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.093055606
12-23 21:21:43.690 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.06805551
12-23 21:21:43.700 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.049999952
12-23 21:21:43.720 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.036111116
12-23 21:21:43.740 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.024999976
12-23 21:21:43.750 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.01666665
12-23 21:21:43.770 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.01111114
12-23 21:21:43.790 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.006944418
12-23 21:21:43.800 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.0041667223
12-23 21:21:43.820 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.0027778149
12-23 21:21:43.840 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.0013889074
12-23 21:21:43.850 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =1
offset =0.0
12-23 21:21:44.000 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_IDLE
#####第2页滑到第1页
12-23 21:22:44.870 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_DRAGGING
12-23 21:22:44.870 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.9791667
12-23 21:22:44.880 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.9513889
12-23 21:22:44.900 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.8972222
12-23 21:22:44.910 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.80277777
12-23 21:22:44.930 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.6875
12-23 21:22:44.930 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_SETTLING
12-23 21:22:44.930 12033-12033/sbingo.com.viewpagerindicator D/onPageSelected: 0
12-23 21:22:44.950 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.53333336
12-23 21:22:44.960 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.40138888
12-23 21:22:44.980 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.29722223
12-23 21:22:45.000 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.21111111
12-23 21:22:45.010 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.15
12-23 21:22:45.030 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.09861111
12-23 21:22:45.050 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.06666667
12-23 21:22:45.060 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.041666668
12-23 21:22:45.080 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.02638889
12-23 21:22:45.090 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.015277778
12-23 21:22:45.110 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.008333334
12-23 21:22:45.130 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.004166667
12-23 21:22:45.140 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.0013888889
12-23 21:22:45.160 12033-12033/sbingo.com.viewpagerindicator D/onPageScrolled: position =0
offset =0.0
12-23 21:22:45.270 12033-12033/sbingo.com.viewpagerindicator D/onPageScrollStateChanged: SCROLL_STATE_IDLE
#####滑动结果分析
看了以上的输出结果后,我们可以得出以下的滑动流程:
-
手指开始滑动的瞬间回调
onPageScrollStateChanged:SCROLL_STATE_DRAGGING
,"ViewPager"说:我状态变了,要开始滑动啦~! -
接着在滑动中一直回调
onPageScrolled
,其中的position
是当前两个可见页面中左边一页的位置值,offset
是当前滑动位置距离position
的距离,取值范围在0-1之间。 -
在滑动中的某个瞬间回调
onPageScrollStateChanged: SCROLL_STATE_SETTLING
,"ViewPager"说:我要设置新的页面啦!话音刚落,又回调了`onPageSelected`,设置好了它预想的新页面。But,如果又滑到原始的页面并不会再回调此处,这种情况下最终的页面就会和`onPageSelected`中的页面不一致。也就是说这里只会回调一次! -
接着继续在滑动中回调
onPageScrolled
,和2中一样了。 -
最后一次回到
onPageScrolled
,此时position
为新页面的位置值,offset
为0。 -
最终滑到新的一页,回调
onPageScrollStateChanged: SCROLL_STATE_IDLE
,"ViewPager"说:滑得好累啊,终于到目的地啦~我要停下来歇息一会儿了!
以上就是一次完整的滑动过程。
圆点x坐标分解
现在我们来分析怎样绘制圆点,很简单,知道圆心坐标+半径就可以了。
半径我取view高度的1/3,圆心y坐标必然是view高度1/2,重点就在于圆心的x坐标了。
看下面我画的圆点位置图来分析下x坐标:
假设共有n个可滑动的页面,不滑动时圆点应该在图中1、2、……n其中一个位置上,即标题的正中间。
两个相邻位置的距离为width/n
。
滑动时可能在1-2之间的X位置,2-3之间的Y位置。
这里我将x坐标的计算分为两部分:currentStartX
和offsetX
,由它们相加可以得到x坐标。
currentStartX
以1、2、……等位置为计算依据,停止滑动时,offsetX
为0。
初始位置1为startX
。
####代码实现
先看一下要用到的一些变量:
/**
* 绘图中心X坐标的百分比值
*/
private float mPercentX = 0f;
/**
* 画笔颜色数组
*/
private int[] colors = {Color.BLUE, Color.BLACK, Color.GREEN, Color.YELLOW, Color.RED};
/**
* 随机生成的画笔颜色序号
*/
private int colorIndex = 0;
/**
* 当前画笔颜色序号
*/
private int currentColorIndex = 0;
/**
* 页数
*/
private int pageCount;
/**
* 初始计算起始点
*/
private float startX;
/**
* 当前计算起始点
*/
private float currentStartX;
/**
* X方向偏移量
*/
private float offsetX;
/**
* 某次滑动是否停止
*/
private boolean isIdle;
/**
* 当前页码
*/
private int currentPageIndex;
提供一个方法用于设置页数,知道了页数才能开心地计算嘛~:
public void setPageCount(int pageCount) {
this.pageCount = pageCount;
}
初始化起始位置:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
startX = currentStartX = getWidth() / (2f * pageCount);
}
定义滑动时在onPageScrolled
中调用的方法,传入的percentX
就是之前分析过的offset
,注意计算基准要实时更新,当position < currentPageIndex
时说明在往回滑动,计算基准currentStartX
要左移一个单位(圆点停止时的相邻位置距离),其他处理也都写了注释,相信不难理解。
public void drawPoint(int position, float percentX) {
//实时更新计算基准
//往回滑动时,计算基准减一个单位
if (position < currentPageIndex) {
currentStartX = startX + (currentPageIndex - 1) * getWidth() / pageCount;
} else { //往后滑动时,计算基准不变
currentStartX = startX + currentPageIndex* getWidth() / pageCount;
}
//防止滑动停止时出现绘制在起点的问题
if (mPercentX > 0.9 && percentX == 0) {
return;
}
//防止过度绘制,尤其是在边界处
if (mPercentX == percentX) {
return;
}
this.mPercentX = percentX;
invalidate();
}
当滑动停止时,上面的方法不会起作用。我们再提供一个方法,用于在SCROLL_STATE_IDLE
时使用。
public void setCurrentPageIndex(int index) {
//只在滑到另一页时绘制
if (currentPageIndex == index) return;
currentPageIndex = index;
currentStartX = startX + index * getWidth() / pageCount;
isIdle = true;
changeColor();
invalidate();
}
停止时更新当前计算基准currentStartX
,同时换色,更换颜色的方法如下:
private void changeColor() {
//保证换一个颜色
while (currentColorIndex == colorIndex) {
colorIndex = new Random().nextInt(colors.length);
}
currentColorIndex = colorIndex;
if (currentColorIndex <= colors.length - 1) {
mPaint.setColor(colors[currentColorIndex]);
}
}
最后来看一下绘制代码:
@Override
protected void onDraw(Canvas canvas) {
if (pageCount < 1) {
throw new RuntimeException("ViewPagerIndicator_非法页数!");
}
if (isIdle) {
offsetX = 0f;
isIdle = false;
} else {
offsetX = mPercentX * getWidth() / pageCount;
}
canvas.drawCircle(currentStartX + offsetX, getHeight() / 2, getHeight() / 3, mPaint);
}
正如之前的坐标分解,停止时将offsetX
置0,滑动时根据滑动百分比计算offsetX
,再通过currentStartX + offsetX
得到当前的x坐标。
以上,就实现了我们预期的功能~
下面是这个库的使用方法
###库使用方法
- 在项目根 build.gradle 添加:
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
- 在要使用的模块中添加依赖:
compile 'com.github.Sbingo:ViewPagerIndicator:2.0.0'
- 在布局中使用:
<sbingo.com.mylibrary.ViewPagerIndicator
android:id="@+id/vp_indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/radio_group"/>
- 在代码中控制:
private boolean fromRadioGroup;
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.item1:
if (viewPager.getCurrentItem() != 0) {
fromRadioGroup = true; //防止直接点击tab时,会一路调用"onPageScrolled",造成绘制效果不好,目前只能这样控制……
viewPager.setCurrentItem(0);
}
break;
......
......
......
default:
}
}
})
int[] colors = {Color.CYAN, Color.LTGRAY};
vpIndicator.setColors(colors); //可以设置喜欢的颜色,但每次也是随机的:
vpIndicator.setPageCount(4); //第一步:设置ViewPager的页数
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (!fromRadioGroup) {
vpIndicator.drawPoint(position,positionOffset); //第二步:滑动时绘制圆点
}
}
@Override
public void onPageSelected(int position) {
((RadioButton) radioGroup.getChildAt(position)).setChecked(true);
currentIndex = position;
}
@Override
public void onPageScrollStateChanged(int state) {
switch (state) {
case ViewPager.SCROLL_STATE_IDLE:
vpIndicator.setCurrentPageIndex(currentIndex); //第三步:停止时绘制圆点
fromRadioGroup = false;
break;
default:
}
}
});
如果觉得有用,不妨顺手点个Star吧,谢啦~