Adhere to the original day, short and fast Android advanced series, please directly in the wechat public number search: Nanchen, direct attention and set as the star standard, wonderful can not be missed.

In Android development, sliding conflicts are an unavoidable topic. Opinions differ on the solutions. For example, RecyclerView nested RecyclerView, directly through the relevant method to prohibit the sliding of internal RecyclerView; ScrollView nested RecyclerView directly replace ScrollView with NestedScrollView and so on. But what we’re going to talk about today is how do we handle sliding conflicts in a custom View?

Of course, today’s topic needs the event distribution mechanism of View as a theoretical premise. If you are not familiar with the event distribution mechanism of View, you can refer to my previous article: Interview series: Talk about Android event distribution mechanism.

The event distribution mechanism of View is briefly introduced

Of course, it can also be mentioned briefly here that the basic flow is the pseudo-code below.

public boolean dispatchTouchEvent(MotionEvent ev) {
	boolean consume = false;
    if (onInterceptTouchEvent(ev)) {
        consume = onTouchEvent(ev);
    }else{
    	consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}
Copy the code

When a ViewGroup receives an event, it calls dispatchTouchEvent() to distribute the event. If onInterceptTouchEvent() returns true, the View intercepts the event. The onTouchEvent() method is directly called back for event handling. If not, the dispatchTouchEvent() method of the tune View is returned, and so on, all the way to the innermost child View.

When a click event is generated, it is passed in the following order: Activity => Window => View, that is, the event is always passed to the Activity, the Activity to the Window, and finally the Window to the top DecorView, and then to the innermost View as above.

Processing events are continuously sent back from the innermost View to its outer View, or directly back to the Activity if there is no View to process.

OnTouchEvent () returns true for processing itself.

With that said, I’d like to make some conclusions about Android development art:

  1. The same sequence of events is an indefinite number from the moment the finger touches the screen (ACTION_DOWN) to the moment the finger leaves the screen (ACTION_UP)ACTION_MOVEEvents.
  2. Once a View decides to intercept an event, it can only handle that sequence of events, and itsonInterceptTouchEvent()The method is no longer called. In other words, if an ACTION_DOWN event is intercepted from an Activity to a ViewGroup that has several child views, subsequent moves and UP events will not be passed to the child views.
  3. If a View once starts processing events, if it does not consume ACTION_DOWN events, i.eonTouchEvent()Return false, then other events in the same sequence will not be assigned to it and will be called directly from its parent ViewonTouchEvent().
  4. If the View consumes no events other than ACTION_DOWN, then the click event disappears, and the parent element’sonTouchEvent()It doesn’t get called, and of course the View can keep receiving subsequent events, and eventually those missing click events will be passed toActivityTo deal with.
  5. Viewgroups do not intercept events by default, views do notonInterceptTouchEvent()Method that is called as soon as an event is passed to itonTouchEvent()And the event is consumed by default. Unless it is unclickable (i.eclickablelongClickableAre allfalse). The View oflongClickableThe default isfalseAnd theclickableCase by case, for exampleButtonThe default istrue.TextViewThe default isfalse.
  6. The View ofenableAttributes do not affectonTouchEvent()The default return value ofViewdisableState of, as long as it’sclickableorlongClickableThere is a fortrueSo itsonTouchEvent()It will returntrue.
  7. requestDisallowInterceptTouchEvent()You can intervene in the event distribution of the parent element in the child element,But you can’t interfere with ACTION_DOWN events.
  8. Priority of events:setOnTouchListener()= >onTouchEvent()= >onClickListener()

Of course, these are the conclusions, specific can follow the interview series: talk about Android event distribution mechanism for the source flow discussion, you will find the above conclusions are easy to get.

Handles sliding conflicts in custom views

Handling sliding conflicts seems difficult for most Android development, but when you put it into practice, it seems easy, because there’s actually a formula for it. Basically there are two schemes: external interception && internal interception.

External interception

The so-called external interception method, as the name implies, is to directly intercept our sliding events in the parent container, so that it can not enter the child element, which seems to be a bit similar to our RecyclerView nested RecyclerView to disable internal RecyclerView sliding. It’s just a matter of dealing with it internally. But if you look at it carefully, the external interception method will make the internal element not receive the sliding event at all.

This approach clearly fits well with the event distribution mechanism we discussed above. The onInterceptTouchEvent() method returns true when the ACTION_MOVE event is received. The onInterceptTouchEvent() method returns true when the ACTION_MOVE event is received.

override fun onInterceptTouchEvent(ev: MotionEvent?).: Boolean{ ev? .run {if(Action == MotionEvent.ACTION_MOVE && the parent needs to click the event){return true}}return super.onInterceptTouchEvent(ev)
}
Copy the code

The code is very simple, we just need to process our logic during the ACTION_MOVE event, and when our logic is satisfied, we will intercept the ACTION_MOVE event for our own processing.

You can see why you don’t block ACTION_DOWN and ACTION_UP. If the ACTION_DOWN event is intercepted, the onInterceptTouchEvent() method will not be used for subsequent ACTION_MOVE, ACTION_UP, and other events. If we block ACTION_UP, the child element’s click event cannot be processed, because everyone knows that a click event starts with ACTION_DOWN and ends with ACTION_UP, both are necessary.

Internal interception method

Internal interception is more complex than external interception, so we generally recommend external interception. However, the internal interception method still has its very important status, the specific situation may encounter.

Internal intercept method, need to requestDisallowInterceptTouchEvent () method, this method is stem what of? As the name implies, the request does not allow interception of events, and it receives a Boolean parameter indicating whether interception is not allowed.

We directly override the dispatchTouchEvent() method of the child element, resulting in pseudocode as follows:

override fun dispatchTouchEvent(ev: MotionEvent?).: Boolean{ ev? .run {when(action){
            MotionEvent.ACTION_DOWN -> parent.requestDisallowInterceptTouchEvent(true)
            MotionEvent.ACTION_MOVE ->{
                if(meet need to let the external container intercept events) {parent. RequestDisallowInterceptTouchEvent (false)}}}}return super.dispatchTouchEvent(ev)
}
Copy the code

Presumably code is also very easy to understand, we give the parent container requestDisallowInterceptTouchEvent () parameters passed is representative does not allow its intercept events, when the parameter to true representative is not allowed to intercept, on behalf of the intercept to false. So it looks like the same thing with external interception.

This is not enough. We know from the previous theory that when our parent intercepts an ACTION_DOWN event, none of the events can be passed to the child and the dispatchTouchEvent() method we wrote above is not called. So we also need to override the parent onInterceptTouchEvent() method when using the internal intercepting method.

override fun onInterceptTouchEvent(ev: MotionEvent?).: Boolean{ ev? .run {if (action == MotionEvent.ACTION_DOWN){
            return false}}return super.onInterceptTouchEvent(ev)
}
Copy the code

So far, we have basically introduced two solutions to handle sliding conflicts, which can be easily combined with the actual scene when customizing the View.

In addition to sliding conflict, sliding processing is also a very interesting work, interested can refer to NestedScrollingParent2 and NestedScrollingChild2 hey.

Article from: Exploring the Art of Android Development