前言
在当初的App设计中,轮播根本成为了每个利用的“标配”,有了轮播,就天然须要有对应的指示器,代表以后轮播的进度,当初市面上指示器的款式大部分都是基于小圆点的模式,实现这个根本的成果网上也有很多轮子,本文次要是在实现根本成果的根底上,在切换圆点之间增加一个粘性过渡的动画成果。
成果预览
实现思路
绘制圆点
圆点的话基于画笔绘制,将控件宽度平分为N等份,且选中的圆点半径稍大。
圆点之间的联动滚动
反对设置最多显示N个圆点,当圆点总数超过N个时,临时不显示在控件可见范畴内,直到左/右滚动到凑近边界时,主动平移所有圆点,从而让最新选中的圆点再次回到居中的地位。应用属性动画联合横坐标偏移实现。
圆点过渡动画
圆点与圆点之间,如果单纯切换选中,会显得有些僵硬,所以要为这个过程增加一些过渡的动画成果,这里采纳当下常见的一种“粘性”成果,相似于咱们在QQ联系人列表长按拖动未读音讯数的成果:
这里基于贝塞尔曲线
来实现,通过计算筹备过渡的两个圆点的地位,以及它们之间的中心点,能够绘制出高低两条贝塞尔曲线,再闭合起来即可。而后联合属性动画进行挪动,实现最终的过渡成果。
实现步骤
1.计算控件宽高
依照设计的成果,控件的宽高取决于小圆点的排列:
控件宽度 = 屏幕中可见的所有小圆点的宽度 * 可见小圆点的数量 + 小圆点之间的间距 * (可见小圆点的数量 - 1)控件高度 = 最大的小圆点的高度
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = Math.min(totalCount, showCount); int width = (int) ((count - 1) * smallDotWidth + (count - 1) * dotPadding + bigDotWidth); int height = (int) bigDotWidth; final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); final int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); final int heightSpecSize = MeasureSpec.getMode(heightMeasureSpec); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(width, height); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(width, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, height); } else { setMeasuredDimension(width, height); }}
另外留神最大显示数量的管制,即 int count = Math.min(totalCount, showCount);
如果以后圆点总数超过屏幕可见数,则基于最大可见数来计算控件的宽度。
2.绘制小圆点
在晓得小圆点的数量之后,只须要遍历顺次绘制即可。思考到选中的圆点与其余圆点款式上的区别,因而针对以后选中圆点独自设置宽度 bigDotWidth
,独自设置色彩 selectColor
,如下:
@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); float startX = curX; float selectX = 0; for (int i = 0; i < totalCount; i++) { if (curIndex == i) { //绘制选中圆点 paint.setColor(selectColor); paint.setStyle(Paint.Style.FILL); selectRectF.left = startX; selectRectF.top = getHeight() / 2f - bigDotWidth / 2; selectRectF.right = startX + bigDotWidth; selectRectF.bottom = getHeight() / 2f + bigDotWidth / 2; canvas.drawCircle(startX + (bigDotWidth) / 2, bigDotWidth / 2, (bigDotWidth) / 2, paint); selectX = startX + bigDotWidth / 2; startX += (bigDotWidth + dotPadding); } else { //绘制其它圆点 paint.setColor(defaultColor); paint.setStyle(Paint.Style.FILL); startX += smallDotWidth / 2; canvas.drawCircle(startX, bigDotWidth / 2, (smallDotWidth) / 2, paint); startX += (smallDotWidth / 2 + dotPadding); } }}
3.左右平移动画
从效果图能够看出,指示器平移的触发机会在于每一次的左右切换,具体须要满足如下条件:
- 1.以后圆点总数超过最大可见数
- 2.以后筹备切换的下一个圆点在屏幕非两头的地位
第一个条件,圆点总数超过最大可见数才可平移,这个很好了解。第二个是切换的下一个圆点在屏幕非两头地位,这个是平移的一个规定,比方上面的例子:
上图在切换之前,选中的是3,筹备切换到4的过程中,因为以后总数为7个,超过最大可见数5个,满足第一个条件,同时因为在切换之前4是处在非屏幕两头的地位,因而满足第二个条件,须要整体向左平移一个单位,使得切换之后,4变成了屏幕核心的地位,逻辑如下:
public void setCurIndex(int index) { if (index == curIndex) { return; } //以后圆点总数超过最大可见数 if (totalCount > showCount) { if (index > curIndex) { //往左边滑动 int start = showCount % 2 == 0 ? showCount/2 - 1 : showCount / 2; int end = totalCount - showCount / 2; //判断是否须要先滚动 if (index > start && index < end) { startScrollAnim(Duration.LEFT, () -> invalidateIndex(index)); } else { invalidateIndex(index); } } else { //往右边滑动 int start = showCount / 2; int end = showCount % 2 == 0 ? totalCount - showCount / 2 + 1 : totalCount - showCount / 2; //判断是否须要先滚动 if (index > start - 1 && index < end - 1) { startScrollAnim(Duration.RIGHT, () -> invalidateIndex(index)); } else { invalidateIndex(index); } } } else { invalidateIndex(index); }}
4.圆点过渡动画
圆点之间的粘性动画,实质上是以前一个圆点作为基准地位,而后平移另外一个圆点的程度地位,使得它们之间的闭合曲线逐步变动,直到平移到与下一个圆点地位重合,如下:
由红色圆点切换到绿色圆点的过程中,以A点为起始点,连贯A点与C点绘制一条贝塞尔曲线,同样,底部B点和D点之间也绘制一条贝塞尔曲线,而后再把AB和CD也连接起来,四条门路造成一条闭合的曲线绘制进去,造成根本的形态。
而后再联合属性动画,使得C点和D点一直向右挪动,直到与绿色圆齐全重合。 如下:
设置粘性属性动画的起始和完结值:
//以后选中的圆点的程度核心 作为粘性动画起始点float startValues = getCurIndexX() + bigDotWidth / 2;//依据方向设置动画的完结值if (index > curIndex) { stickAnimator.setFloatValues(startValues, startValues + dotPadding + smallDotWidth);} else { stickAnimator.setFloatValues(startValues, startValues - dotPadding - smallDotWidth);}
监听动画一直刷新粘性过渡的动画值:
ValueAnimator stickAnimator = new ValueAnimator();stickAnimator.setDuration(animTime);stickAnimator.addUpdateListener(animation -> { stickAnimX = (float) animation.getAnimatedValue(); invalidate();});stickAnimator.removeAllListeners();stickAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { isSwitchFinish = true; invalidate(); }});stickAnimator.start();
绘制粘性曲线:
@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); if (isSwitchFinish) { //切换实现记得重置门路 stickPath.reset(); } else { paint.setColor(selectColor); //以以后选中的圆点为绘制起始点 float quadStartX = selectX; float quadStartY = getHeight() / 2f - bigDotWidth / 2; stickPath.reset(); //连贯4个点 stickPath.moveTo(quadStartX, quadStartY); stickPath.quadTo(quadStartX + (stickAnimX - quadStartX) / 2, bigDotWidth / 2, stickAnimX, quadStartY); stickPath.lineTo(stickAnimX, quadStartY + bigDotWidth); stickPath.quadTo(quadStartX + (stickAnimX - quadStartX) / 2, bigDotWidth / 2, quadStartX, quadStartY + bigDotWidth); //造成闭合曲线 stickPath.close(); //绘制过渡过程中的圆 canvas.drawCircle(stickAnimX, bigDotWidth / 2, (bigDotWidth) / 2, paint); canvas.drawPath(stickPath, paint); }}
结语
如果指定了最多显示多少个圆点,则当总数超过时会左右滚动,如果想要非滚动的模式也能够设置为最大圆点数。本控件次要还是通过贝塞尔曲线来制作粘性成果,让动画更为活泼,反对设置是否开启粘性成果、粘性动画时长、小圆点选中与非选中时的款式等,后续会再依据需要裁减其它细节。
github地址:https://github.com/GitHubZJY/...
原文链接:https://www.jianshu.com/p/19a...
文末
您的点赞珍藏就是对我最大的激励!
欢送关注我,分享Android干货,交换Android技术。
对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!