preface

The last article gave you an analysis of the Android event passing mechanism, which was purely theoretical. In this case, you can learn the basic usage of Canvas, Paint, touch feedback, Scroller and VelocityTracker.

Let's go to the next wave

1. Draw the scale

Drawing the scale is very simple, it is the basic Canvas, Paint, directly on the code:

Private fun drawScale(canvas: canvas) {//maxScale is the maximum scale (total scale). There seems to be a problem with the name...) for (index in 0.. StrokeWidth = (scaleWidth * 2).tofloat () val drawX = (scaleWidth * 2) DrawLine (drawX, 0f, drawX, scaleHeight. ToFloat () * 2, drawX, scaleHeight. ToFloat (), drawX, scaleHeight. DrawText ("$index", drawx-scaletextSize /2, scaleheight.tofloat () * 3, drawx-scaletextSize /2, scaleheight.tofloat () * 3, StrokeWidth = scaleWidth.tofloat () val drawX = (index * scaleInterval).tofloat () canvas.drawLine( drawX, 0f, drawX, scaleHeight.toFloat(), paint ) } } }Copy the code

Each of the ten scales has a thick and long scale with scale information underneath. After performing this step we get the following effect

The text seems a little crooked, just found, later adjust it ~~~

2. Drag the scale

To drag the scale, we need to use the touch event feedback described in the previous section, so we need to rewrite the onTouchEvent method as follows:

Private var lastX = 0 Override fun onTouchEvent(event: MotionEvent): Boolean {when (event.actionmasked) {motionEvent.action_down -> {lastX = event.x.toint () // Tells the parent View not to intercept the event when pressed parent.requestDisallowInterceptTouchEvent(true) } //2 MotionEvent.ACTION_MOVE -> { currentScrollX -= (event.x - lastX) If (currentScrollX > maxScrollX) {currentScrollX = maxScrollx.tofloat ()} minScrollX) { currentScrollX = minScrollX.toFloat() } scrollTo(currentScrollX.roundToInt(), LastX = event.x.toint () postInvalidate()} return true}Copy the code

Note: In this case we are dragging by modifying scrollX. Changing scrollX in a View that is not a ViewGroup actually changes the position of the canvas. Changing scrollX in a ViewGroup changes the position of all the child views.

The first step

Define a variable lastX record last move to the horizontal coordinate, to calculate after each move offset, and invoke the parent View requestDisallowInterceptTouchEvent notify him not to intercept events

The second step

LastX and Event. x were used to calculate the offset of this event and record it in the way of accumulation in currentScrollX. Then absolute offset movement was made through scrollTo. Meanwhile, we need to make a sliding range limit. Be sure to call postInvalidate or Invalidate to refresh the view.

At this point we have achieved something like this:

The problem is obvious. There is no inertial slip.

3. Increase inertia slip

What is a Scroller?

Generally speaking, View’s inertial sliding is realized through Scroller coordination. Scroller itself has nothing to do with View, but it just provides a set of transition algorithms, such as from 0.. 100, Scroller will calculate and give you a series of values between 0 and 100 in the specified time for smooth transition animation. You can also refer to property animations if you don’t know, they all do the same thing.

There are many ways to achieve the inertia sliding effect. Scroller and VelocityTracker are used to collect the sliding speed when the finger is lifted, and then Scroller Fing is used to achieve the inertia sliding. The code is as follows:

This code contains the calibration correction described in the next section and is posted here because I do not want to post duplicate code

private var lastX = 0 override fun onTouchEvent(event: MotionEvent): Boolean {// Start speed detection, create an if (velocityTracker == null) {velocityTracker = velocityTracker.obtain ()} velocityTracker? .addMovement(event) when (event.actionMasked) { ... . MotionEvent.ACTION_UP -> { velocityTracker? .computecurrentVelocity (1000, maxVelocity.tofloat ()) val velocityX = velocityTracker!! If (abs(velocityX) > minVelocity) {fling(-velocityx)} Else {correctScale()} VelocityTracker? .recycle() velocityTracker = null}} return true} /** * @param vX/private Fun fling(vX: Int) {scroll.fling (currentScrollx.toint (), 0, vX, 0, minScrollX, maxScrollX, 0, 0) invalidate()} Specialized users handle swiping. */ Override fun computeScroll() {// Roll unfinished complete, Has been completed, stop refresh interface if (scroller.com puteScrollOffset ()) {currentScrollX = scroller. CurrX. ToFloat () / / scroll view If (currentScrollx.toint (), 0); Scroller.com puteScrollOffset ()) {correctScale ()} / / change the current calibration changeScale ()} super.com puteScroll ()}Copy the code

prompt

The View’s draw method provides a hook method computeScroll that is called every time draw is triggered. In this case, a redraw indirect callback computeScroll is triggered by postInvalidate.

Events are continuously added to the velocityTracker as you move, which internally calculates the current sliding speed. At the moment when the finger is lifted, judge whether the speed at this time reaches the minimum speed to trigger inertial sliding (inertia sliding will not be triggered if the speed is too slow). If the fling of Scroller is called, the computeScroll method is indirectly called with postInvalidate during this process. Internal scroller is used to get the current position and then scrollTo is used to set the position. If scroller is done you need to stop calling postInvalidate otherwise it will recurse indefinitely.

4. Calculate indicator position and calibration correction

First calculate the position of the indicator

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onsizechanged (w, h, oldw, oldh) // Central coordinates, maximum visible scale / 2 * scale interval, IndicatorPointX = (width/scaleInterval) / 2 * scaleInterval // Minimum scrolling distance,- indicator x offset minScrollX = -indicatorPointX // Maximum rolling distance, scale * interval -width + width -indicatorPointX. MaxScrollX = maxScale * scaleInterval - indicatorPointX // Give an initial value changeScale()}Copy the code

The initial position of the scale will start at 0, and our indicator will be fixed in the middle. First calculate the current width of the maximum number of scale display, then divide by 2 and multiply the scale interval to get the position of the middle scale. As the middle scale is related to the width, I conducted the calculation process in onSizeChanged.

As for the drawing of the indicator, since the indicator needs to be fixed in the middle, adjusting ScrollX will move the canvas, and the indicator will float left and right even when it is fast sliding, so I put a layer of ViewGroup around the scale View, and the indicator is drawn in this ViewGroup. This part of the code is very simple, I will not post, the bottom of the article will post the source address, interested can download down to read.

4.1 Calibration Correction

One last question remains, how do I align the nearest scale with the indicator when the slide stops? If you understand the above inertial sliding I believe you will soon be inspired, yes also through the Scroller to achieve, on the code:

The codes about correction timing are uniformly posted in the last summary, please read them

Private fun correctScale() {// ValREMAINDER = (currentScrollX % scaleInterval).toint () // If the value is not set with the indicator (remainder ! Val correctScrollX = if (remainder > (scaleInterval / 2)) {scaleInterval} else { -remainder } scroller.startScroll(currentScrollX.toInt(), 0, correctScrollX, 0) } }Copy the code

Obtain the nearest scale of the indicator, calculate the travel value and then turn on Scroller for correction.

This step will be able to achieve our initial renderings. Because the fragmentary code is more, so did not post in the article one by one, interested can go to github.com/zskingking/… View the complete code in RulerModule.

The above warehouse is a custom View collection library, including custom View, Layout, LayoutManager and so on, almost all of the knowledge of custom View. If you can read a custom View and not write it, come to me, and I’ll stick to what I’ve always done, not write arcane code, and try to make every comment clear. The warehouse will continue to update, welcome to pay attention to.