On the pits trampled by sliding conflicts and Solutions

This blog talks a lot about content, because there are many pits in learning customized View. Customized View also customizes ViewGroup, and stepped on sliding conflicts. So it's a simple one-time talk about customization, of course, focusing on conflict resolution.

First give Demo address My Blog

1,View
2,ViewGroup
3. Custom View and ViewGroup
4. Conflict Resolution

1,view

Official Interpretation: Public class View extends Object implements Drawable. Callback, KeyEvent. Callback, Accessibility Event Source
This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class for widgets, which are used to create interactive UI components (buttons, text fields, etc.). The ViewGroup subclass is the base class for layouts, which are invisible containers that hold other Views (or other ViewGroups) and define their layout properties.
This class represents the basic building blocks of user interface components. The view occupies a rectangular area on the screen and is responsible for drawing and event processing. Views are the base class of widgets that are used to create interactive UI components (buttons, text fields, etc.). ViewGroup subclasses are the base classes of layout. They are invisible containers that hold other views (or other view groups) and define their layout attributes. (Note: This translation is based on the reference of the Taoist Dictionary, and subsequent translations are as follows.)

Popularly speaking, view is our layout. View is what we see. When we customize view, we inherit the View class and implement some of its methods, such as the official explanation: To implement a custom view, you will usually begin by providing overrides for some of the standard methods that the framework calls on all views. You do not need to override all. In fact, you can start by just overriding onDraw(android.graphics.Canvas). To implement a custom view, you usually first need to provide coverage for some standard methods that the framework calls all views. You don't need to cover all of these methods. In fact, you can start by rewriting onDraw(android.graphics.Canvas).

2,ViewGroup

Official Interpretation: public abstract class ViewGroup extends View implements ViewParent, ViewManager.
A ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers. This class also defines the ViewGroup.LayoutParams class which serves as the base class for layouts parameters.
ViewGroup is a special view that can contain other views (called children). View groups are the base classes of layout and view containers. This class also defines ViewGroup. The LayoutParams class serves as the base class for layout parameters.

A brief look at the differences between view and view group:
ViewGroup inherits from View and is a special View that can install other Views (or other ViewGroups). ViewGroup is the parent class of layouts and views containers. Its direct subclasses are: FrameLayout, GridLayout, LinearLayout and so on. Example: Linera Layout
The View class represents the basic building blocks of UI components. a view occupies a square area of the screen and is responsible for drawing and event handling. View is the parent class of widgets used to create interactive UI components such as buttons, text boxes, and so on. Example:Button
The popular point is that a layout can contain other layouts and widgets.

3. Customization

First look at the final effect chart:

First of all, let's make clear what we want to do. Now let's imitate QQ drawer-style sideslip (which is based on Hongyang god's imitation of QQ5.0 sideslip). Because we want to achieve drawer-style sideslip, we can't use official sideslip, because there is no drawer-style animation. Of course, the first thing to consider is the Horizontal ScrollView horizontal scroll. After all, our drawer slides horizontally. The effect is as follows.

As you can see, although there is horizontal sliding, but there is no imaginary drawer type, only horizontal sliding, which requires our custom ViewGroup. Why is this a custom ViewGroup? You can take a look at the difference between View and ViewGroup. We have the outermost layout in this outermost drawer sliding, and then the sub-layout in it. So in this respect, the drawer layout is a ViewGroup. Of course, you can see from the code that we inherited Horizontal ScrollView directly, although here it is. We don't inherit ViewGroup directly, but Horizontal ScrollView inherits FrameLayout, and FrameLayout is the inherited ViewGroup. So fundamentally, our drawer layout is the inherited ViewGroup, so it's also a ViewGroup. Why not inherit ViewGroup directly? This is also possible. In order to write less code, I inherited Horizontal ScrollView directly, because many attributes in Horizontal ScrollView are directly needed, so we can not inherit ViewGroup, so let's first look at the custom ViewGroup.

Look directly at the code below:

package com.example.a14512.swipeconflictdemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;

/**
 * Created by 14512 on 2017/9/5.
 */

public class MyHorizontalScrollView extends HorizontalScrollView{

    private LinearLayout mWapper;
    private ViewGroup mMenu;  //Menu area
    private ViewGroup mContent;  //Content area
    private int mScreenWidth;
    private int mMenuWidth;

    private int mMenuRightPadding;

    private boolean once = false;
    private boolean isOpen;


    public MyHorizontalScrollView(Context context) {
        this(context, null);
    }

    /**
     * Called when a custom View is not used
     *
     * @param  context
     * @param attrs
     * */
    public MyHorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * Called only when using a custom View
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     * */
    public MyHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setHorizontalScrollBarEnabled(false);
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyleAttr, 0);
        int initSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, displayMetrics);
        mMenuRightPadding = a.getDimensionPixelSize(R.styleable.SlidingMenu_rightPadding, initSize);
        a.recycle();

        mScreenWidth = displayMetrics.widthPixels;

    }

    /**
     * Set your own width and height
     * Set the width and height of the child view
     * */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (!once) {
            mWapper = (LinearLayout) getChildAt(0);
            mMenu = (ViewGroup) mWapper.getChildAt(0);
            mContent = (ViewGroup) mWapper.getChildAt(1);
            mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;
            mContent.getLayoutParams().width = mScreenWidth;
            once = true;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /**
     * Hide menu by setting offset
     * */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        if (changed) {
            this.scrollTo(mMenuWidth, 0);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_UP:
                //Width hidden on the left
                int scrollX= getScrollX();
                if (scrollX >= mMenuWidth / 2) {
                    this.smoothScrollTo(mMenuWidth, 0);  //Hidden and sometimes animated
                    isOpen = false;
                } else {
                    this.smoothScrollTo(0, 0);
                    isOpen = true;
                }
                return true;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * Scroll Event Call
     * */
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

        float scale = l * 1.0f / mMenuWidth;  //1~0
        float leftAlpha = 0.6f + 0.4f * (1.0f - scale);  //transparency

        //Property animation TranslationX, default animation has a time limit, you need to set your own time
        mMenu.animate().translationX(mMenuWidth * scale * 0.8f).alpha(leftAlpha).setDuration(0).start();
    }

    /**
     * open a menu
     */
    public void openMenu() {
        if (isOpen) return;
        this.smoothScrollTo(0, 0);
        isOpen = true;
    }

    /**
     * Close menu
     * */
    public void closeMenu() {
        if (!isOpen) return;
        this.smoothScrollTo(mMenuWidth, 0);
        isOpen = false;
    }

    /**
     * Switch menu
     * */
    public void changeMenu() {
        if (isOpen) {
            closeMenu();
        } else {
            openMenu();
        }
    }

}

Anyone who has seen Android's exploratory art development knows what's going on. If there's anyone who doesn't know much about it, go and see Android's exploratory art development. Video of Hongyang Great God Some of the explanatory codes are clearly annotated, and I believe you can understand them. These are the basic usage of custom View Group, some of the simplest usage. At this time, under the guidance of the Great God of Hongyang, we have basically completed the drawer-like sliding, which is exactly as simple as that.

Let's take a look at the picture.

4. Conflict Resolution

Careful people will find that Recycler View has a lot of cross-flow added to the main layout on our right, but notice that no matter where I slide to the right in the main layout on the right, I will pull the side out. And when I want to slide Recycler View, I find that these Item s can't slide. When I slide, I just pull out the menu on the left side. That's the sliding conflict we're going to talk about next.

Whether it's Android's art explorations or various blogs on the Internet, there are not a few ways to solve sliding conflicts. There are several common types of sliding conflicts: conflicts in different directions, conflicts in the same direction, complex conflicts formed by the combination of the two. In Android's art explorations, there are some general solutions, external interception and internal interception. The book is also more detailed. The solution to the conflict is illustrated with examples. Of course, it can be concluded that simple external interception is more complicated than internal interception. Demo in books is used by many blogs on the Internet, but most blogs are about sliding conflicts caused by nesting of ListView and ScrollView in vertical direction. This is similar to our current one, but the specific situation is different. Face to face, we begin to solve the sliding conflict.

First, we try to use external interception, code into the following:

package com.example.a14512.swipeconflictdemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;

/**
 * Created by 14512 on 2017/9/5.
 */

public class MyHorizontalScrollView extends HorizontalScrollView{
    private OnGiveUpTouchEventListener mGiveUpTouchEventListener;

    private LinearLayout mWapper;
    private ViewGroup mMenu;  //Menu area
    private ViewGroup mContent;  //Content area
    private int mScreenWidth;
    private int mMenuWidth


    private int mMenuRightPadding;

    private boolean once = false;
    private boolean isOpen;


    public MyHorizontalScrollView(Context context) {
        this(context, null);
    }

    /**
     * Called when a custom View is not used
     *
     * @param  context
     * @param attrs
     * */
    public MyHorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * Called only when using a custom View
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     * */
    public MyHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setHorizontalScrollBarEnabled(false);
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyleAttr, 0);
        int initSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, displayMetrics);
        mMenuRightPadding = a.getDimensionPixelSize(R.styleable.SlidingMenu_rightPadding, initSize);
        a.recycle();

        mScreenWidth = displayMetrics.widthPixels;

    }

    /**
     * Set your own width and height
     * Set the width and height of the child view
     * */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (!once) {
            mWapper = (LinearLayout) getChildAt(0);
            mMenu = (ViewGroup) mWapper.getChildAt(0);
            mContent = (ViewGroup) mWapper.getChildAt(1);
            mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;
            mContent.getLayoutParams().width = mScreenWidth;
            once = true;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /**
     * Hide menu by setting offset
     * */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        if (changed) {
            this.scrollTo(mMenuWidth, 0);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_UP:
                //Width hidden on the left
                int scrollX= getScrollX();
                if (scrollX >= mMenuWidth / 2) {
                    this.smoothScrollTo(mMenuWidth, 0);  //Hidden and sometimes animated
                    isOpen = false;
                } else {
                    this.smoothScrollTo(0, 0);
                    isOpen = true;
                }
                return true;
        }
        return super.onTouchEvent(ev);
    }

   public void setOnGiveUpTouchEventListener(OnGiveUpTouchEventListener l) {
        mGiveUpTouchEventListener = l;
    }

    /**
     * Event Distribution, Conflict Handling, External Interception
     * */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                //processing logic
                if (mGiveUpTouchEventListener != null) {
                    if (mGiveUpTouchEventListener.giveUpTouchEvent(ev)) {
                        intercepted = true;
                    }
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
            default:
                break;
        }
        return intercepted;

    }

    /**
     * Scroll Event Call
     * */
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

        float scale = l * 1.0f / mMenuWidth;  //1~0
        float leftAlpha = 0.6f + 0.4f * (1.0f - scale);  //transparency

        //Property animation TranslationX, default animation has a time limit, you need to set your own time
        mMenu.animate().translationX(mMenuWidth * scale * 0.8f).alpha(leftAlpha).setDuration(0).start();
    }

    /**
     * open a menu
     */
    public void openMenu() {
        if (isOpen) return;
        this.smoothScrollTo(0, 0);
        isOpen = true;
    }

    /**
     * Close menu
     * */
    public void closeMenu() {
        if (!isOpen) return;
        this.smoothScrollTo(mMenuWidth, 0);
        isOpen = false;
    }

    /**
     * Switch menu
     * */
    public void changeMenu() {
        if (isOpen) {
            closeMenu();
        } else {
            openMenu();
        }
    }

    //Here's the interface for resolving sliding conflicts using external interception
    public interface OnGiveUpTouchEventListener {
        public boolean giveUpTouchEvent(MotionEvent event);
    }

}

Outer Activity implements interface code logic:

package com.example.a14512.swipeconflictdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.MotionEvent;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements MyHorizontalScrollView.OnGiveUpTouchEventListener {

    private RecyclerView in_theaters_recycler_view;
    private RecyclerView coming_soon_recycler_view;
    private RecyclerView us_box_recycler_view;
    private RecyclerView top250_recycler_view;
    private RecyclerView weekly_recycler_view;
    private RecyclerView new_movies_recycler_view;
    private List<String> strings = new ArrayList<String>();
    private MyHorizontalScrollView myHorizontalScrollView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
        initRecyclerView();
        setAdapter();
    }

    private void setAdapter() {
        Adapter adapter = new Adapter();
        adapter.setStrings(strings);
        adapter.notifyDataSetChanged();
        in_theaters_recycler_view.setAdapter(adapter);
        coming_soon_recycler_view.setAdapter(adapter);
        us_box_recycler_view.setAdapter(adapter);
        top250_recycler_view.setAdapter(adapter);
        weekly_recycler_view.setAdapter(adapter);
        new_movies_recycler_view.setAdapter(adapter);
    }

    private void initData() {
        for (int i = 0; i < 10; i++ ) {
            strings.add("I am a item" + i);
        }
    }

    private void initView() {
        in_theaters_recycler_view = (RecyclerView) findViewById(R.id.in_theaters_recycler_view);
        coming_soon_recycler_view = (RecyclerView) findViewById(R.id.coming_soon_recycler_view);
        us_box_recycler_view = (RecyclerView) findViewById(R.id.us_box_recycler_view);
        top250_recycler_view = (RecyclerView) findViewById(R.id.top250_recycler_view);
        weekly_recycler_view = (RecyclerView) findViewById(R.id.weekly_recycler_view);
        new_movies_recycler_view = (RecyclerView) findViewById(R.id.new_movies_recycler_view);
        myHorizontalScrollView.setOnGiveUpTouchEventListener(this);
    }

    private void initRecyclerView() {
        LinearLayoutManager layoutManager1 = new LinearLayoutManager(this);
        layoutManager1.setOrientation(LinearLayoutManager.HORIZONTAL);
        LinearLayoutManager layoutManager2 = new LinearLayoutManager(this);
        layoutManager2.setOrientation(LinearLayoutManager.HORIZONTAL);
        LinearLayoutManager layoutManager3 = new LinearLayoutManager(this);
        layoutManager3.setOrientation(LinearLayoutManager.HORIZONTAL);
        LinearLayoutManager layoutManager4 = new LinearLayoutManager(this);
        layoutManager4.setOrientation(LinearLayoutManager.HORIZONTAL);
        LinearLayoutManager layoutManager5 = new LinearLayoutManager(this);
        layoutManager5.setOrientation(LinearLayoutManager.HORIZONTAL);
        LinearLayoutManager layoutManager6 = new LinearLayoutManager(this);
        layoutManager6.setOrientation(LinearLayoutManager.HORIZONTAL);
        new_movies_recycler_view.setLayoutManager(layoutManager1);
        in_theaters_recycler_view.setLayoutManager(layoutManager2);
        coming_soon_recycler_view.setLayoutManager(layoutManager3);
        us_box_recycler_view.setLayoutManager(layoutManager4);
        top250_recycler_view.setLayoutManager(layoutManager5);
        weekly_recycler_view.setLayoutManager(layoutManager6);
    }

    @Override
    public boolean giveUpTouchEvent(MotionEvent event) {
        if (Interception event)  //Here is pseudocode
            return true;
        return false;
    }
}

From the code, we can know that when we need to intercept, we use the method given UpTouchEvent in the interface to determine whether we need to intercept, and we need to return true to intercept. At this time, we need to depend on whether the first Item in RecyclerView is the first one. If so, we intercept, then the sliding process is handled by the outer MyHorizontal ScrollView. When the last one arrives, it is also handled by MyHorizontal ScrollView. (Note: This is a big pit I've stepped on.) In dealing with this logic, we can easily find that it's easy to write, but in the process of running, it's easy to find that the program crashes, because we use Recycler View to determine the location of Item, but the reason for the program crash is that Recycler View is empty, which leads to it. So it's not easy to use this interception method here, so let's try internal interception next.

Internal interception requires rewriting RecyclerView, as follows:

 package com.example.a14512.swipeconflictdemo;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;

/**
 * Created by 14512 on 2017/9/10.
 */

public class MyRecyclerView extends RecyclerView {
    private float lastX;

    public MyRecyclerView(Context context) {
        this(context, null);
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            getParent().getParent().requestDisallowInterceptTouchEvent(true);
        } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            int x = (int) ev.getX();
            if (lastX > x) {
                // If it slides horizontally to the left and cannot slide, it is returned to the upper view for processing.
                if (!canScrollHorizontally(1)) {
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                }
            } else if (x > lastX) {
                // If it slides horizontally to the right and cannot slide, it is returned to the upper view for processing.
                if (!canScrollHorizontally(-1)) {
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                }
            }
        }
        lastX = ev.getX();
        return super.dispatchTouchEvent(ev);
    }
}

Then there are some changes in the outer layout, the code is as follows:

package com.example.a14512.swipeconflictdemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;

/**
 * Created by 14512 on 2017/9/5.
 */

public class MyHorizontalScrollView extends HorizontalScrollView{

    private LinearLayout mWapper;
    private ViewGroup mMenu;  //Menu area
    private ViewGroup mContent;  //Content area
    private int mScreenWidth;
    private int mMenuWidth;

    private int mMenuRightPadding;

    private boolean once = false;
    private boolean isOpen;


    public MyHorizontalScrollView(Context context) {
        this(context, null);
    }

    /**
     * Called when a custom View is not used
     *
     * @param  context
     * @param attrs
     * */
    public MyHorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * Called only when using a custom View
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     * */
    public MyHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setHorizontalScrollBarEnabled(false);
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyleAttr, 0);
        int initSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, displayMetrics);
        mMenuRightPadding = a.getDimensionPixelSize(R.styleable.SlidingMenu_rightPadding, initSize);
        a.recycle();

        mScreenWidth = displayMetrics.widthPixels;

    }

    /**
     * Set your own width and height
     * Set the width and height of the child view
     * */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (!once) {
            mWapper = (LinearLayout) getChildAt(0);
            mMenu = (ViewGroup) mWapper.getChildAt(0);
            mContent = (ViewGroup) mWapper.getChildAt(1);
            mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;
            mContent.getLayoutParams().width = mScreenWidth;
            once = true;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /**
     * Hide menu by setting offset
     * */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        if (changed) {
            this.scrollTo(mMenuWidth, 0);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_UP:
                //Width hidden on the left
                int scrollX= getScrollX();
                if (scrollX >= mMenuWidth / 2) {
                    this.smoothScrollTo(mMenuWidth, 0);  //Hidden and sometimes animated
                    isOpen = false;
                } else {
                    this.smoothScrollTo(0, 0);
                    isOpen = true;
                }
                return true;
        }
        return super.onTouchEvent(ev);
    }


    /**
     * Event Distribution, Conflict Handling, External Interception
     * */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
       //Here's what's needed for internal interception
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onTouchEvent(ev);
            return false;
        }
        return true;
    }

    /**
     * Scroll Event Call
     * */
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

        float scale = l * 1.0f / mMenuWidth;  //1~0
        float leftAlpha = 0.6f + 0.4f * (1.0f - scale);  //transparency

        //Property animation TranslationX, default animation has a time limit, you need to set your own time
        mMenu.animate().translationX(mMenuWidth * scale * 0.8f).alpha(leftAlpha).setDuration(0).start();
    }

    /**
     * open a menu
     */
    public void openMenu() {
        if (isOpen) return;
        this.smoothScrollTo(0, 0);
        isOpen = true;
    }

    /**
     * Close menu
     * */
    public void closeMenu() {
        if (!isOpen) return;
        this.smoothScrollTo(mMenuWidth, 0);
        isOpen = false;
    }

    /**
     * Switch menu
     * */
    public void changeMenu() {
        if (isOpen) {
            closeMenu();
        } else {
            openMenu();
        }
    }


}

It can be found that the outer layer only changes a few lines of code when intercepting inside, and then we will see the effect:

As you can see, the conflict of RecyclerView has been resolved, and it has been perfectly solved internally, but a new problem has arisen. The ScrollView of the main layout seems to be unable to slide. From the screenshot, it can be seen that the sliding up and down of ScrollView is completely invalid. Then we have to analyze it. Let's first look at MyRecyclerView, a custom View.

    if (lastX > x) {
                // If it slides horizontally to the left and cannot slide, it is returned to the upper view for processing.
                if (!canScrollHorizontally(1)) {
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                }
            } else if (x > lastX) {
                // If it slides horizontally to the right and cannot slide, it is returned to the upper view for processing.
                if (!canScrollHorizontally(-1)) {
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                }
            }
 ```

 //We can see that there is nothing wrong with the judgment logic here, which achieves the desired effect, but pay attention to this line of code.
 ` getParent().getParent().requestDisallowInterceptTouchEvent(false);`
 //This line of code represents returning to your parent layout View to handle events, so let's look at Layout's code again.

 ```
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="32dp"
                    android:gravity="center"
                    android:background="@android:color/white">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="16dp"
                        android:layout_centerVertical="true"
                        android:text="@string/in_theaters"
                        android:textSize="16sp"/>

                    <LinearLayout
                        android:id="@+id/more_in_theaters"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentRight="true"
                        android:layout_centerVertical="true"
                        android:layout_marginRight="16dp"
                        android:orientation="horizontal">

                        <TextView
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_gravity="center_vertical"
                            android:text="@string/tv_more"/>

                    </LinearLayout>

                </RelativeLayout>

                <com.example.a14512.swipeconflictdemo.MyRecyclerView
                    android:id="@+id/in_theaters_recycler_view"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="#f0f0f0"/>
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="32dp"
                    android:gravity="center"
                    android:background="@android:color/white">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="16dp"
                        android:layout_centerVertical="true"
                        android:text="@string/coming_soon"
                        android:textSize="16sp"/>

                    <LinearLayout
                        android:id="@+id/more_coming_soon"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentRight="true"
                        android:layout_centerVertical="true"
                        android:layout_marginRight="16dp"
                        android:orientation="horizontal">

                        <TextView
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_gravity="center_vertical"
                            android:text="@string/tv_more"/>

                    </LinearLayout>

                </RelativeLayout>

                <com.example.a14512.swipeconflictdemo.MyRecyclerView
                    android:id="@+id/coming_soon_recycler_view"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="#f0f0f0"/>
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="32dp"
                    android:gravity="center"
                    android:background="@android:color/white">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="16dp"
                        android:layout_centerVertical="true"
                        android:text="@string/us_box"
                        android:textSize="16sp"/>

                    <LinearLayout
                        android:id="@+id/more_us_box"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentRight="true"
                        android:layout_centerVertical="true"
                        android:layout_marginRight="16dp"
                        android:orientation="horizontal">

                        <TextView
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_gravity="center_vertical"
                            android:text="@string/tv_more"/>


                    </LinearLayout>

                </RelativeLayout>

                <com.example.a14512.swipeconflictdemo.MyRecyclerView
                    android:id="@+id/us_box_recycler_view"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="#f0f0f0"/>
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="32dp"
                    android:gravity="center"
                    android:background="@android:color/white">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="16dp"
                        android:layout_centerVertical="true"
                        android:text="@string/top250"
                        android:textSize="16sp"/>

                    <LinearLayout
                        android:id="@+id/more_top250"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentRight="true"
                        android:layout_centerVertical="true"
                        android:layout_marginRight="16dp"
                        android:orientation="horizontal">

                        <TextView
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_gravity="center_vertical"
                            android:text="@string/tv_more"/>

                    </LinearLayout>

                </RelativeLayout>

                <com.example.a14512.swipeconflictdemo.MyRecyclerView
                    android:id="@+id/top250_recycler_view"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="#f0f0f0"/>
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="32dp"
                    android:gravity="center"
                    android:background="@android:color/white">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="16dp"
                        android:layout_centerVertical="true"
                        android:text="@string/weekly"
                        android:textSize="16sp"/>

                    <LinearLayout
                        android:id="@+id/more_weekly"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentRight="true"
                        android:layout_centerVertical="true"
                        android:layout_marginRight="16dp"
                        android:orientation="horizontal">

                        <TextView
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_gravity="center_vertical"
                            android:text="@string/tv_more"/>


                    </LinearLayout>

                </RelativeLayout>

                <com.example.a14512.swipeconflictdemo.MyRecyclerView
                    android:id="@+id/weekly_recycler_view"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="#f0f0f0"/>
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="32dp"
                    android:gravity="center"
                    android:background="@android:color/white">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="16dp"
                        android:layout_centerVertical="true"
                        android:text="@string/new_movies"
                        android:textSize="16sp"/>

                    <LinearLayout
                        android:id="@+id/more_new_movies"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentRight="true"
                        android:layout_centerVertical="true"
                        android:layout_marginRight="16dp"
                        android:orientation="horizontal">

                        <TextView
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_gravity="center_vertical"
                            android:text="@string/tv_more"/>

                    </LinearLayout>

                </RelativeLayout>

                <com.example.a14512.swipeconflictdemo.MyRecyclerView
                    android:id="@+id/new_movies_recycler_view"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="#f0f0f0" />
            </LinearLayout>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="I am is a text1"
                android:textSize="24sp"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="I am is a text2"
                android:textSize="24sp"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="I am is a text3"
                android:textSize="24sp"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="I am is a text4"
                android:textSize="24sp"/>

        </LinearLayout>
    </ScrollView>
</LinearLayout>

As you can see, the parent layout of MyRecyclerView is ScrollView, so the event is returned to ScrollView, and ScrollView is not dealing with this event, so ScrollView is returned to its parent layout, so it returns layer by layer, eventually to MyHorizontal ScrollView, and then the event is processed, which solves the problem of sideslip, but why ScrollView? What happened to rollView? In fact, careful analysis, at this time, although the internal intercept, but the external ScrollView and MyHorizontal ScrollView from the conflict, this conflict is the I we mentioned at the beginning of a different conflict, so we need to solve this at this time. If we continue to use internal interception to rewrite a ScrollView, this will undoubtedly increase the amount of code. At this point, we can consider the external interception to deal directly with the external sliding up and down.
The code is as follows:

"`
package com.example.a14512.swipeconflictdemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;

/**
* Created by 14512 on 2017/9/5.
*/

public class MyHorizontalScrollView extends HorizontalScrollView{

private LinearLayout mWapper;
private ViewGroup mMenu;  //Menu area
private ViewGroup mContent;  //Content area
private int mScreenWidth;
private int mMenuWidth;

//Record the coordinates of the last slide (onInterceptTouchEvent)
private int lastXIntercept = 0;
private int lastYIntercept = 0;



private int mMenuRightPadding;

private boolean once = false;
private boolean isOpen;


public MyHorizontalScrollView(Context context) {
    this(context, null);
}

/**
 * Called when a custom View is not used
 *
 * @param  context
 * @param attrs
 * */
public MyHorizontalScrollView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

/**
 * Called only when using a custom View
 *
 * @param context
 * @param attrs
 * @param defStyleAttr
 * */
public MyHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    setHorizontalScrollBarEnabled(false);
    DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyleAttr, 0);
    int initSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, displayMetrics);
    mMenuRightPadding = a.getDimensionPixelSize(R.styleable.SlidingMenu_rightPadding, initSize);
    a.recycle();

     /*   WindowManager windowManager = (WindowManager) context.getSystemServiceName(Class.forName(Context.WINDOW_SERVICE));
    DisplayMetrics outMetrics = new DisplayMetrics();
    windowManager.getDefaultDisplay().getMetrics(outMetrics);*/

    mScreenWidth = displayMetrics.widthPixels;

}

/**
 * Set your own width and height
 * Set the width and height of the child view
 * */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (!once) {
        mWapper = (LinearLayout) getChildAt(0);
        mMenu = (ViewGroup) mWapper.getChildAt(0);
        mContent = (ViewGroup) mWapper.getChildAt(1);
        mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;
        mContent.getLayoutParams().width = mScreenWidth;
        once = true;
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

/**
 * Hide menu by setting offset
 * */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);

    if (changed) {
        this.scrollTo(mMenuWidth, 0);
    }
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    switch (action) {
        case MotionEvent.ACTION_UP:
            //Width hidden on the left
            int scrollX= getScrollX();
            if (scrollX >= mMenuWidth / 2) {
                this.smoothScrollTo(mMenuWidth, 0);  //Hidden and sometimes animated
                isOpen = false;
            } else {
                this.smoothScrollTo(0, 0);
                isOpen = true;
            }
            return true;
    }
    return super.onTouchEvent(ev);
}

/**
 * Event Distribution, Conflict Handling, External Interception
 * */
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean intercepted = false;
    int x = (int) ev.getX();
    int y = (int) ev.getY();
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            onTouchEvent(ev);  //When dealing with internal interception
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            //Externally intercepts ScrollView sliding up and down, and when ScrollView slides up and down, events are given to ScrollView
            if (Math.abs(x - lastXIntercept) > Math.abs(y - lastYIntercept)) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted = false;
            break;
        default:
            break;
    }
    lastXIntercept = x;
    lastYIntercept = y;
    return intercepted;
}

/**
 * Scroll Event Call
 * */
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    super.onScrollChanged(l, t, oldl, oldt);

    float scale = l * 1.0f / mMenuWidth;  //1~0
    float leftAlpha = 0.6f + 0.4f * (1.0f - scale);  //transparency

    //Property animation TranslationX, default animation has a time limit, you need to set your own time
    mMenu.animate().translationX(mMenuWidth * scale * 0.8f).alpha(leftAlpha).setDuration(0).start();
}

/**
 * open a menu
 */
public void openMenu() {
    if (isOpen) return;
    this.smoothScrollTo(0, 0);
    isOpen = true;
}

/**
 * Close menu
 * */
public void closeMenu() {
    if (!isOpen) return;
    this.smoothScrollTo(mMenuWidth, 0);
    isOpen = false;
}

/**
 * Switch menu
 * */
public void changeMenu() {
    if (isOpen) {
        closeMenu();
    } else {
        openMenu();
    }
}

}

"`
So use an external intercept, and then look at the rendering.

The result is obvious. It solves the sliding conflict and realizes the drawer side sliding.

Finally, to sum up, we used external interception and internal interception in this sliding conflict, and the logic of processing is not very difficult, so I line code, so don't be afraid of sliding conflicts in the future, carefully analyze, use external interception or internal interception to solve is, by the way, put it on. Demo address

Thank you for your reference:

Hongyang
Nuggets
Exploration of Android Development Art

Keywords: Android Java less xml

Added by matthewd on Thu, 23 May 2019 01:52:41 +0300