Original article, reproduced please indicate the source, juejin.cn/post/684490…
0 x0 preface
A design network often have a lot of good beautiful interaction design work, one day, encounter such effect, smooth animation, interactive nature, so in its anatomy of these elements, a open source control, ten days have one hundred star, feel very welcome, wrote this scribble a few pen today, share the crime after, hope to help peers.
0 x1 preparation
rendering
Effect analysis
The curve part
1, default state: straight line, beginning and end annotation, default value annotation.
2. Finger press down: the curve animation is executed, and the small circle is enlarged regularly. The marking value moves upward at the same speed according to the relative position of the curve crest.
3. Dragging effect of finger: curve, small circle and annotation move with touch point and update annotation value at the same time.
4, finger off the screen: curve back animation execution, marking the small circle rule shrinks to the default state, the selected value follows the wave peak to sink to the default state, marking the background gradually disappear, the end of animation.
Circular indicator section
There are three parts to disassemble the state: default state, in-process state and post-touch state. By default, the indicator is a small circle with an agreed distance below the horizontal curve. When pressed, the coordinates at the bottom of the circle remain unchanged, the diameter of the circle gradually increases, and the distance between the dome and the curve remains unchanged until the end of the animation.
Functional analysis
1, elements in the control: ruler, annotation with small circle, selected value, can be configured with their own color,
2. Range of configurable values,
3. The default value can be configured.
4. The selected value can be monitored in real time.
5, can display the unit.
Technical analysis
The curve part
Static screenshots show that the main elements of this control for touch triggered curve and its expansion effect. After disassembling, the touch part is a six-order Bezier curve, with five reference points and four control points. We can divide it into two third-order curves. Among them, the Y coordinate of the first and last reference points is fixed, the X coordinate moves relative to the touch position, the X coordinate of the remaining reference points is relatively fixed, and the Y coordinate rises and falls according to the animation law. As for the control point, in order to ensure that the curve part is horizontal by default, the Y coordinate of the first and the last two control points is fixed, and the X coordinate is relatively fixed. The Y coordinate of the two control points in the middle is consistent with the reference point in the middle, and X is fixed relative to the reference point in the middle. (Through the above disassembly, the curve can be a horizontal line by default, and in the down state, with the horizontal position, the crest position, can have a more natural excessive arc, not so stiff.)
Circular indicator section
Normal round, relative to the bottom of its own larger up, reduced down reduction.
The animation part
Press down the animation using ordinary ValueAnimator, uniform LinearInterpolator. There is also a background change of the selected value. Change the brush Alpha value according to the animation progress. // Write some animation code here.
0x2 code implementation
Make a cup of tea and roll up your sleeves!
Inheriting the View:
public class BezierSeekBar extends View {
public BezierSeekBar(Context context) {
super(context);
init(context, null);
}
public BezierSeekBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public BezierSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public BezierSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
this.context = context; }}Copy the code
First draw the curve effect:
// The first third-order curve
bezierPath.moveTo(this.fingerX - circleRadiusMax * 2 * 3, (float) 2 * height / 3);
bezierPath.cubicTo(this.fingerX - circleRadiusMax * 2 * 2, (float) 2 * height / 3.this.fingerX - circleRadiusMax * 2 * 1, (float) 2 * height / 3 - bezierHeight, this.fingerX, (float) 2 * height / 3 - bezierHeight);
// Second third order curve
bezierPath.moveTo(this.fingerX, (float) 2 * height / 3 - bezierHeight);
bezierPath.cubicTo(this.fingerX + circleRadiusMax * 2, (float) 2 * height / 3 - bezierHeight, this.fingerX + circleRadiusMax * 2 * 2, (float) 2 * height / 3.this.fingerX + circleRadiusMax * 2 * 3, (float) 2 * height / 3);
Copy the code
Change its Y coordinate to restore the curve to its default state: draw a full line:
//line1
bezierPath.reset();
bezierPath.moveTo(0, (float) 2 * height / 3);
bezierPath.lineTo(this.fingerX - circleRadiusMax * 2 * 3, (float) 2 * height / 3);
//bezier1
bezierPath.moveTo(this.fingerX - circleRadiusMax * 2 * 3, (float) 2 * height / 3);
bezierPath.cubicTo(this.fingerX - circleRadiusMax * 2 * 2, (float) 2 * height / 3.this.fingerX - circleRadiusMax * 2 * 1, (float) 2 * height / 3 - bezierHeight, this.fingerX, (float) 2 * height / 3 - bezierHeight);
//bezier2
bezierPath.moveTo(this.fingerX, (float) 2 * height / 3 - bezierHeight);
bezierPath.cubicTo(this.fingerX + circleRadiusMax * 2, (float) 2 * height / 3 - bezierHeight, this.fingerX + circleRadiusMax * 2 * 2, (float) 2 * height / 3.this.fingerX + circleRadiusMax * 2 * 3, (float) 2 * height / 3);
//line2
bezierPath.lineTo(width, (float) 2 * height / 3);
canvas.drawPath(bezierPath, bezierPaint);
Copy the code
Add Touch event blocking, display curve when pressed:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
fingerX = event.getX();
if (fingerX < 0F) fingerX = 0F;
if (fingerX > width) fingerX = width;
// Touch the media into the control to begin the animation transition
this.animatorFingerIn.start();
break;
case MotionEvent.ACTION_MOVE:
fingerX = event.getX();
if (fingerX < 0F) fingerX = 0F;
if (fingerX > width) fingerX = width;
postInvalidate();
break;
case MotionEvent.ACTION_UP:
// Touch the media away from the control to perform the animation
this.animatorFingerOut.start();
break;
}
valueSelected = Integer.valueOf(decimalFormat.format(valueMin + (valueMax - valueMin) * fingerX / width));
if(selectedListener ! =null) {
selectedListener.onSelected(valueSelected);
}
return true;
}
Copy the code
Add animation effects:
this.animatorFingerIn = ValueAnimator.ofFloat(0f, 1f);
this.animatorFingerIn.setDuration(200L);
this.animatorFingerIn.setInterpolator(new LinearInterpolator());
this.animatorFingerOut = ValueAnimator.ofFloat(1f, 0f);
this.animatorFingerOut.setDuration(200L);
this.animatorFingerOut.setInterpolator(new LinearInterpolator());
this.animatorFingerOut.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float progress = (float) animation.getAnimatedValue(); AnimInFinshed = (progress >= 0.15f); TxtSelectedBgPaint. SetAlpha ((int) (255 * (progress to 0.15 F)));if (progress >= 0.95F) {
textPaint.setColor(colorValueSelected);
} else {
textPaint.setColor(colorValue);
}
bezierHeight = circleRadiusMax * 1.5F * progress;
circleRadius = circleRadiusMin + (circleRadiusMax - circleRadiusMin) * progress;
spaceToLine = circleRadiusMin* 2 * (1F - progress); postInvalidate(); }});Copy the code
Draw a circular indicator, changing its size according to the animation progress:
canvas.drawCircle(this.fingerX, (float) 2 * height / 3 + spaceToLine + circleRadius, circleRadius, ballPaint);
Copy the code
After adding the other helper elements, configure the common attributes and throw the public methods:
<declare-styleable name="BezierSeekBar">// Curve color<attr name="bsBar_color_line" format="reference|color" />// Circular indicator color<attr name="bsBar_color_ball" format="reference|color" />// The text color of the threshold<attr name="bsBar_color_value" format="reference|color" />// Select the text color of the value<attr name="bsBar_color_value_selected" format="reference|color" />// Select the text color background of the value<attr name="bsBar_color_bg_selected" format="reference|color" />// Minimum threshold<attr name="bsBar_value_min" format="integer" />// The threshold is maximum<attr name="bsBar_value_max" format="integer" />// The default value is selected<attr name="bsBar_value_selected" format="integer" />/ / unit<attr name="bsBar_unit" format="reference|string" />
</declare-styleable>
Copy the code
private void initAttr(Context context, AttributeSet attrs) {
if(attrs ! =null) {
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.BezierSeekBar);
this.colorBall = attributes.getColor(R.styleable.BezierSeekBar_bsBar_color_ball, Color.BLACK);
this.colorLine = attributes.getColor(R.styleable.BezierSeekBar_bsBar_color_line, Color.BLACK);
this.colorValue = attributes.getColor(R.styleable.BezierSeekBar_bsBar_color_value, Color.BLACK);
this.colorValueSelected = attributes.getColor(R.styleable.BezierSeekBar_bsBar_color_value_selected, Color.WHITE);
this.colorBgSelected = attributes.getColor(R.styleable.BezierSeekBar_bsBar_color_bg_selected, Color.BLACK);
this.valueMin = attributes.getInteger(R.styleable.BezierSeekBar_bsBar_value_min, 30);
this.valueMax = attributes.getInteger(R.styleable.BezierSeekBar_bsBar_value_max, 150);
this.valueSelected = attributes.getInteger(R.styleable.BezierSeekBar_bsBar_value_selected, 65);
this.unit = attributes.getString(R.styleable.BezierSeekBar_bsBar_unit) + ""; attributes.recycle(); }}Copy the code
Finally, test this out:
<tech.nicesky.bezierseekbar.BezierSeekBar
android:id="@+id/bsBar_test"
app:bsBar_color_ball="@android:color/white"
app:bsBar_color_bg_selected="@android:color/white"
app:bsBar_color_line="@android:color/white"
app:bsBar_color_value="@android:color/white"
app:bsBar_color_value_selected="#ef5350"
app:bsBar_value_min="30"
app:bsBar_value_max="120"
app:bsBar_value_selected="65"
app:bsBar_unit="kg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
Copy the code
Perfect:)! Attached is the Demo APK:
Summary:
This control mainly involves the basic understanding and application of Bessel curve, the basic application of animation, the routine process of custom control, the focus is still skilled in the analysis and disassembly of various UI effects and ideas.
Welcome to Star, open source address:
https://github.com/fairytale110/BezierSeekBar
Copy the code