Nonsense not to say, there is a picture to read

Control scales with magnetic suction effect. At the same time, the finger movement speed during user interaction will be monitored. After the finger is lifted, it will continue to keep the inertia movement for a period of time, as shown in the figure below

Note that the control supports gradient background color! As long as the colors are bold, the UI style can be replaced at will. Of course, don’t try those shitty gradients, or you’ll be a design geek. Let’s disassemble this control one by one

First, design ideas

“Next door products are greedy cry” series of articles is to design some and the market is not quite the same control, give a person a sense of fresh and new. Not only should it be designed, but also include the implementation of all the details of the control, and finally do good to use. Throughout the market some similar scale controls, color matching is more monotonous, if the color matching is improper, the scale of the right Angle will appear more cut eyes. To try to solve this kind of problem, a gradient control with a more rounded UI style was born.

Second, implementation method

1. UI disassembly

1.1 Shape Composition

As can be seen from the design drawing, the whole control is composed of a longitudinal scale bar, scale numbers, the top of the scale reading text and the indicating point below the scale reading text. Therefore, we can disassemble the entire control into the following correspondence:

Longitudinal scale bar: rounded rectangle, divided into three types: long, medium and short

Graduated number: text

Indicator: round

Scale reading: text with starting and ending values

1.2 Drawing Process

With shape correspondence, the whole drawing idea is gradually clear, we will divide the scale into three types of long, medium and short, and define their height and width respectively

/** * int maxLineHeight, midLineHeight, minLineHeight; /** * int lineWidth = 24;Copy the code

Then define the width and height of the calibration reading text, easy to center calculation in the drawing, but also declare the start and end of the calibration value and the interval between the calibration

/** * textHeight */ float textHeight; /** * int startNum = 0, endNum = 40; /** * int unitNum = 1; Int lineSpacing = 3 * lineWidth; /** * int indicatorRadius = lineWidth / 2; /** * indicatorRadius = lineWidth / 2;Copy the code

Shape properties are customized, in order to facilitate the control color and color value calculation, and then define the color of each shape

/ / @colorint int startColor = color.parsecolor ("# ff3415B0 "); / / @colorint int startColor = color.parsecolor ("# ff3415B0 "); */ @colorint int endColor = color.parsecolor ("#ffcd0074"); /** */ @colorint int indicatorColor = startColor;Copy the code

2. UI drawing

After the UI is disassembled, the UI drawing logic becomes very clear, and all the drawing keys are in the onDraw method

2.1 Draw the scale bar

With enough property declarations, the key is the coordinate calculation logic. Where ((endNum – startNum)/unitNum) + 1 represents the total number of ticks in the control, which is drawn in the for loop. (Width / 2) – (lineWidth / 2) + (I * lineSpacing), ignore offsetStart and movedX attributes in the code, these two values are mainly used for user interaction logic. Finally, call canvas drawRoundRect to draw.

For (int I = 0; i < ((endNum - startNum) / unitNum) + 1; i++) { int lineHeight = minLineHeight; if (i % 10 == 0) { lineHeight = maxLineHeight; } else if (i % 5 == 0) { lineHeight = midLineHeight; } float lineLeft = offsetStart + movedX + (width / 2) - (lineWidth / 2) + (i * lineSpacing); float lineRight = lineLeft + lineWidth; RectF rectF = new RectF(lineLeft, 4 * indicatorRadius, lineRight, lineHeight); canvas.drawRoundRect(rectF, lineWidth / 2, lineWidth / 2, paint); }Copy the code

2.2 Draw scale text

To draw the text, consider that the text is centered with the scale line. The calculation logic is as follows

If (I % 10 == 0) {textPaint. SetColor (ColorUtils. GetColor (startColor, endColor); (float) i / (float) ((endNum - startNum) / unitNum))); canvas.drawText(i + "", lineLeft + lineWidth / 2 - textPaint.measureText("" + i) / 2, lineHeight + 20 + textHeight, textPaint); }Copy the code

LineLeft + lineWidth / 2 – textpaint.measureText (“” + I) / 2 Textpaint.measuretext (“” + I) is called to dynamically calculate the width

2.3 Drawing indicators

Indicator is relatively simple, draw a circle, where width is the width of the entire control, by int indicatorX = width / 2 calculation, you can draw the indicator at the center of the control position

//draw indicator
int indicatorX = width / 2;
int indicatorY = indicatorRadius;
canvas.drawCircle(indicatorX, indicatorY, indicatorRadius, paint);
Copy the code

3. Gradient background

Gets the gradient background color and sets it to the corresponding scale bar. According to the previous attribute declaration, there are startColor and endColor. The key of this step is to calculate the color value corresponding to each scale and encapsulate a ColorUtils tool method for calculation. A color gradient corresponding to the code is nothing more than a bunch of ints. Therefore, we can decompose the color into red, blue, and green ints and use the ratio parameter to obtain a color value in the gradient color band

/** * Calculate the middle color of the gradient color ** @param startColor * @param endColor * @param ratio percentage, Public static int getColor(int startColor, int endColor, float ratio) { int redStart = Color.red(startColor); int blueStart = Color.blue(startColor); int greenStart = Color.green(startColor); int redEnd = Color.red(endColor); int blueEnd = Color.blue(endColor); int greenEnd = Color.green(endColor); Int red = (int) (redStart + ((redEnd - redStart) * radio + 0.5)); Int Greed = (int) (greenStart + ((greenEnd - greenStart) * radio + 0.5)); Int blue = (int) (blueStart + ((blueStart) * radio + 0.5)); return Color.argb(255, red, greed, blue); }Copy the code

Once you have a color, you can assign values to the corresponding text, dials, and indicators

// Set the scale color, The ratio is calculated from (float) I/(float) ((endNum - startNum)/unitNum)) (float) i / (float) ((endNum - startNum) / unitNum))); TextPaint. SetColor (ColorUtils. GetColor (startColor, endColor, (float) i / (float) ((endNum - startNum) / unitNum))); IndicatorColor = ColorUtils. GetColor (startColor, endColor, Math.abs((float) (offsetStart + movedX) / (float) (lineSpacing * ((endNum - startNum) / unitNum))));Copy the code

4. Realize interactive logic

Through the previous three steps, the skin of the control has been developed. Skin, then heart. The interaction logic must be handled primarily in onTouchEvent. Special attention is needed to strictly control the boundary settlement, drag the position can not exceed the scope of the scale, otherwise the final effect must be pulled across. OffsetStart represents the offset of the current scale reading relative to the starting scale before the user starts the operation, and moveX represents the distance moved by the user’s finger

@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: scroller.forceFinished(true); downX = event.getX(); movedX = 0; break; case MotionEvent.ACTION_MOVE: movedX = event.getX() - downX; Log.i(TAG, "offsetStart==>" + offsetStart); Log.i(TAG, "movedX==>" + movedX); Log.i(TAG, "offsetStart + movedX==>" + (offsetStart + movedX)); If (movedX > 0) {movedX = 0; offsetStart = 0; } else if (offsetStart + movedX < -((endNum - startNum) / unitNum) * lineSpacing) { offsetStart = -((endNum - startNum) / unitNum) * lineSpacing; movedX = 0; } if (listener ! = null) { Log.i(TAG, "getSelectedNum()==>" + getSelectedNum()); listener.onNumSelect(getSelectedNum()); } postInvalidate(); break; ACTION_UP: //todo: calculate the sliding rate postInvalidate(); // Todo: calculate the sliding rate postInvalidate(); break; } return true; }Copy the code

5, achieve magnetic absorption effect

In the process of implementing interactive logic, there is a small detail, is to consider when the user finger sliding control is completed, the current sliding distance is not on the scale. So we also need to implement a magnetic effect, by simulating the int strength of the calculated result, offsetStart is a float

If (offsetStart + movedX <= 0 && offsetStart + movedX >= -((endNum - startNum)/unitNum) * lineSpacing)  offsetStart = offsetStart + movedX; movedX = 0; offsetStart = ((int) (offsetStart / lineSpacing)) * lineSpacing; }Copy the code

6. Achieve inertia

To achieve inertia, you need to use two important helper classes, one is Scroller and the other is VelocityTracker.

6.1 Scroller profile

Scroller, as the name suggests, has nothing to do with The Elder Scrolls. The user can use the scroll logic to calculate the scrolling process if the finger is not moving fast enough. It has several core approaches:

GetCurX () : Gets the x-coordinate of the current scroll position

GetCurY () : Gets the y-coordinate of the current scroll position

Fling () : The inertia of a roll is calculated automatically, given a speed and a limit of speed

StartScroll () : Starts scrolling according to the specified parameters

ComputeScrollOffset () : Determines whether scrolling has ended

Note that using Scroller requires rewriting the View’s computeScroll method

6.2 VelocityTracker profile

VelocityTracker is responsible for obtaining the speed of the user’s finger swiping, the core method is as follows;

AddMovement () : Adds movement events to monitor the user’s finger speed

GetXVelocity () : Gets the movement velocity in the X direction

6.3 Combination of Scroller and VelocityTracker

// create VelocityTracker VelocityTracker = VelocityTracker. Obtain (); / / add event monitoring @ Override public Boolean onTouchEvent (MotionEvent event) {velocityTracker. AddMovement (event); } / / computing speed and obtain velocityTracker.com puteCurrentVelocity (500); float velocityX = velocityTracker.getXVelocity(); // Scroller. Fling (0, 0, (int) velocityX, 0, integer.min_value, integer.max_value, 0, 0); @override public void computeScroll() {super.computeScroll(); If (scroller.com puteScrollOffset ()) {if (scroller. GetCurrX () = = scroller. GetFinalX ()) {/ / todo: magnetic effect and boundary control} else { MovedX = scroll.getCurrx () -scroll.getStarty (); If (offsetStart + movedX >= 0) {offsetStart = 0; movedX = 0; } } if (listener ! = null) { listener.onNumSelect(getSelectedNum()); } postInvalidate(); }Copy the code

7. Other details

Add the current reading callback method to external callers, expose methods that can get indicator colors so callers can configure their OWN UI style, and so on. Let’s revisit the final implementation.

Three, afterword.

“Next door products are greedy cry” series of articles is to design some and the market is not quite the same control, give a person a sense of fresh and new. No matter what UI control development, as long as the gradual analysis and disassembly, the big problem into small problems, the big demand into small structure, layer by layer, and finally the water will come naturally.

The control is placed on Gitee and the address is

I am a address

Now that I see this, it’s time to flash my official account. Ah, I don’t have one yet. Need a swipe of this control plus me VX :pengyeah888