preface

In the App now design, shuffling basic became the “standard” for each application, a wheel, it needs to have a corresponding indicator, represent the current round of progress, the style of the indicator on the market now is mostly based on the form of dots, to implement the basic online also has a lot of wheels, the effect of this article is mainly on the basis of basic effect in the implementation, Add a sticky transition animation between switch dots.

Results the preview

Implementation approach

Draw the dot

Dot words based on the brush drawing, the control width is divided into N equal parts, and the selected dot radius is slightly larger.

The linkage between the dots

The maximum number of dots can be set to N. When the total number of dots exceeds N, they are not displayed in the visible range of the control temporarily. When the left/right scroll to the edge, all dots will be automatically translated, so that the newly selected dots will return to the middle position again. Implemented using property animation combined with abscissa offset.

Dot transition animation

If you simply switch between dots and dots, it will appear a little rigid, so we need to add some transitional animation effects for this process. Here we use a common “sticky” effect, similar to the effect of dragging the number of unread messages in the QQ contact list:

This is based on bezier curves. By calculating the position of the two dots preparing for the transition and the center point between them, the upper and lower Bezier curves can be drawn and then closed. Then move with the property animation to complete the final transition effect.

Implementation steps

1. Calculate the width and height of the control

The width and height of the control depends on the arrangement of the dots:

Control width = width of all dots visible on the screen * Number of dots visible + spacing between dots * (number of dots visible - 1) Control height = height of the largest dotCopy the code
@Override protected 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); }}Copy the code

Int count = math.min (totalCount, showCount) If the current total number of dots exceeds the number of visible dots on the screen, the control’s width is calculated based on the maximum number of visible dots.

2. Draw small dots

Once you know the number of dots, you just need to walk through the drawing sequence. In consideration of the difference between the selected dot and other dot styles, we set the width bigDotWidth and the color selectColor separately for the currently selected dot as follows:

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); float startX = curX; float selectX = 0; for (int i = 0; i < totalCount; I ++) {if (curIndex == I) {// Draw the selected dot 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 {// Draw other dots paint. SetColor (defaultColor); paint.setStyle(Paint.Style.FILL); startX += smallDotWidth / 2; canvas.drawCircle(startX, bigDotWidth / 2, (smallDotWidth) / 2, paint); startX += (smallDotWidth / 2 + dotPadding); }}}Copy the code
3. Pan animations left and right

It can be seen from the renderings that the trigger time of the indicator translation lies in the left and right switching of each time, which needs to meet the following conditions:

  • 1. The current number of dots exceeds the maximum number of visible dots

  • 2. The next dot to be switched is not in the middle of the screen

The first condition, the total number of dots is greater than the maximum number of visible dots that can be shifted, and that makes sense. The second is to switch the next dot to a non-middle position on the screen. This is a rule for panning, as shown in the following example:

Above before switching, the selected is 3, ready to switch to the four process, because the current total of 7, more than most visible number five, satisfy the first condition, at the same time as before to switch 4 is in the screen in the middle position, thus to meet the second condition, need translation left a whole unit, made after the switch, 4 became the center of the screen, The logic is as follows:

public void setCurIndex(int index) { if (index == curIndex) { return; If (totalCount > showCount) {if (index > curIndex) {// Swipe left 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 {// swipe right 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); }}Copy the code
4. Dot transition animation

The viscous animation between dots is essentially that one previous dot is used as the reference position, and then the horizontal position of another dot is shifted, so that the closed curve between them gradually changes until it is shifted to coincide with the position of the next dot, as follows:

By A red dot, switch to the green dot in the process, to A point as the starting point, the connection point A and C point draw A bezier curve, also, the bottom between B and D also draw A bezier curve, and then connect the AB and CD, four forms A closed curve path mapped, form the basic shape.

Then, in combination with the property animation, points C and D continue to move to the right until they completely coincide with the green circle. As follows:

Set the start and end values for the sticky properties animation:

Float startValues = getCurIndexX() + bigDotWidth / 2; // Float startValues = getCurIndexX() + bigDotWidth / 2; / / setting the end of the animation value according to the direction the if (index > curIndex) {stickAnimator. SetFloatValues (startValues, startValues + dotPadding + smallDotWidth); } else { stickAnimator.setFloatValues(startValues, startValues - dotPadding - smallDotWidth); }Copy the code

Listen to the animation constantly refresh the viscosity transition animation value:

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();Copy the code

Draw viscosity curve:

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); If (isSwitchFinish) {// Remember to reset path stickPath.reset(); } else { paint.setColor(selectColor); Float quadStartX = selectX; float quadStartX = selectX; float quadStartY = getHeight() / 2f - bigDotWidth / 2; stickPath.reset(); // Join 4 dots stickpath. moveTo(quadStartX, quadStartY); // Join 4 dots 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); // Form a closed curve stickpath.close (); DrawCircle (stickAnimX, bigDotWidth / 2, (bigDotWidth) / 2, paint); canvas.drawPath(stickPath, paint); }}Copy the code

conclusion

If you specify the maximum number of dots to display, scroll left and right when the total number exceeds, or set to the maximum number of circles if you want non-scroll. This control is mainly through the Bezier curve to produce viscous effect, so that animation is more vivid, support to set whether to open viscous effect, viscous animation duration, small dots selected and not selected when the style, etc., will later expand other details according to demand.

Github address: github.com/GitHubZJY/Z… The original link: www.jianshu.com/p/19a559377…

At the end of the article

Your likes collection is the biggest encouragement to me! Welcome to follow me, share Android dry goods, exchange Android technology. If you have any comments or technical questions about this article, please leave a comment in the comments section.