Touch feedback for ViewGroup

  • In addition to overwriting onTouchEvent(), you also need to overwrite onInterceptTouchEvent()
  • In onInterceptTouchEvent(), the ACTION_DOWN event does almost or exactly the same thing as onTouchEvent()
    • ACTION_DOWN is used as a start log in most gestures (for example, a finger drop). OnTouchEvent () is not necessarily called when onInterceptTouchEvent() is called. Therefore, you need to pass this recording responsibility to onInterceptTouchEvent().
    • Sometimes ACTION_DOWN events are passed to their own onTouchEvent() after the onInterceptTouchEvent() (for example, when no child View has been touched or the child View has not consumed the event). Therefore, you need to make sure that the program state is fine when onInterceptTouchEvent() and onTouchEvent() are both called.
  • In onInterceptTouchEvent(), ACTION_MOVE is generally used to confirm sliding. When the user moves a certain distance (touch SLOP) in a certain direction, the ViewGroup confirms to its child and parent views that it will consume the event.
    • Confirm the slide way: Math. Abs (event. The getX () – downX) > ViewConfiguration. GetXxxSlop ()

    • Return true in onInterceptTouchEvent(), the child View will receive the ACTION_CANCEL event, and no further events will be sent to the child View

    • Inform the parent View: call getParent (). RequestDisallowInterceptTouchEvent (true). This method recursively tells each parent View not to try to intercept events through onInterceptTouchEvent() in subsequent events. This notification is valid only for the current sequence of events, that is, after this set of events ends (that is, after the user raises his hand), the parent View automatically enables interception for subsequent new sequences of events

VelocityTracker

  • If the GestureDetector does not meet the requirements, or if the GestureDetector is too complex, you can handle the onTouchEvent() event yourself. However, you need to use the VelocityTracker to calculate finger movement speed.
  • Usage:
    • At the beginning of each sequence of events (that is, when the ACTION_DOWN event arrives), either create an instance with velocityTracker.obtain () or reset a previous instance with velocityTracker.clear()
    • For each event (including ACTION_DOWN events), the use of velocityTracker. AddMovement (event) add events into velocityTracker
    • In need of speed (for example to calculate whether or not to fling in ACTION_UP speed), use velocityTracker.com puteCurrentVelocity (1000, maxVelocity) to calculate the real speed, GetXVelocity ()/getYVelocity() is used to obtain the calculated velocity
      • 1000 in the method parameter refers to the length of calculation time in ms. For example, if you put 1000 here, getXVelocity() returns the number of pixels your finger moves per 1000ms (one second)
      • The second parameter is the speed upper limit, beyond which the calculated speed will fall back. For example, if 200 is written here and the real-time speed is 300, the actual return speed will be 200
        • MaxVelocity can viewConfiguration. GetScaledMaximumFlingVelocity () to obtain

ScrollTo/scrollBy and computeScroll()

  • ScrollTo ()/scrollBy() sets the offset for drawing, usually used for sliding controls
  • Scroll represents the initial offset of the drawing action from the content inside the control (like: I’m drawing from the 300th pixel of the content), so when the parameter in scrollTo() is positive, the content will move negatively
  • ScrollTo () is instantaneous and does not automatically use animation. If you want to animate, you need to use the View.computeScroll() method
    • ComputeScroll () is automatically called when the View is redrawn
    • Usage:
    // onTouchEvent() : overscroll. startScroll(startX, startY, dx, dy); postInvalidateOnAnimation(); . / / onTouchEvent () : Override fun computeScroll () {if (overScroller.com puteScrollOffset ()) {/ / computing real-time position scrollTo(overScroller.currX.toFloat(), overScroller.currY.toFloat()); / / update interface postInvalidateOnAnimation (); // Next frame continues}}Copy the code

Handwritten viewPager

class TwoPager(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs) { private var downX = 0f private var downY = 0f private var downScrollX = 0f private var scrolling = false private val overScroller: OverScroller = OverScroller(context) private val viewConfiguration: ViewConfiguration = ViewConfiguration.get(context) private val velocityTracker = VelocityTracker.obtain() private var minVelocity = viewConfiguration.scaledMinimumFlingVelocity private var maxVelocity = viewConfiguration.scaledMaximumFlingVelocity private var pagingSlop = viewConfiguration.scaledPagingTouchSlop override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { measureChildren(widthMeasureSpec, heightMeasureSpec) super.onMeasure(widthMeasureSpec, heightMeasureSpec) } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { var childLeft = 0 val childTop = 0 var childRight = width val childBottom = height val count: Int = getChildCount() for (index in 0 until count) { val child = getChildAt(index) child.layout(childLeft, childTop, childRight, childBottom) childLeft += width childRight += width } } override fun onInterceptTouchEvent(event: MotionEvent): Boolean { if (event.actionMasked == MotionEvent.ACTION_DOWN) { velocityTracker.clear() } velocityTracker.addMovement(event) var result = false when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { scrolling  = false downX = event.x downY = event.y downScrollX = scrollX.toFloat() } MotionEvent.ACTION_MOVE -> if (! scrolling) { val dx = downX - event.x if (abs(dx) > pagingSlop) { scrolling = true parent.requestDisallowInterceptTouchEvent(true) result = true } } } return result } override fun onTouchEvent(event: MotionEvent): Boolean { if (event.actionMasked == MotionEvent.ACTION_DOWN) { velocityTracker.clear() } velocityTracker.addMovement(event) when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { downX = event.x downY = event.y downScrollX = scrollX.toFloat() } MotionEvent.ACTION_MOVE -> { val dx = (downX - event.x + downScrollX).toInt() .coerceAtLeast(0) .coerceAtMost(width) scrollTo(dx, 0) } MotionEvent.ACTION_UP -> { velocityTracker.computeCurrentVelocity(1000, maxVelocity.toFloat()) // 5m/s 5km/h val vx = velocityTracker.xVelocity val scrollX = scrollX val targetPage = if (abs(vx) < minVelocity) { if (scrollX > width / 2) 1 else 0 } else { if (vx < 0) 1 else 0 } val scrollDistance = if (targetPage == 1) width - scrollX else -scrollX overScroller.startScroll(getScrollX(), 0, scrollDistance, 0) postInvalidateOnAnimation() } } return true } override fun computeScroll() { if (overScroller.computeScrollOffset()) { scrollTo(overScroller.currX, overScroller.currY) postInvalidateOnAnimation() } } }Copy the code
<? The XML version = "1.0" encoding = "utf-8"? > <com.dsh.txlessons.viewgrouptouch.TwoPager xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.dsh.txlessons.viewgrouptouch.ViewGroupTouchActivity" > <View android:layout_width="match_parent" android:layout_height="match_parent" android:background="#795548" /> <View android:layout_width="match_parent" android:layout_height="match_parent" android:background="#388E3C" /> </com.dsh.txlessons.viewgrouptouch.TwoPager>Copy the code