关于滑动冲突踩的那些坑以及解决办法

这次博客讲的内容比较多,因为学习自定义View时踩的坑比较多,自定义View同时也自定义了ViewGroup,并踩了滑动冲突,所以一次性简单的说一下自定义,当然重点在冲突解决。

先给出Demo地址,我的博客

1、View
2、ViewGroup
3、自定义View和ViewGroup
4、冲突解决

1、view

官方解释: public class View extends Object implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource
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.
这个类代表了用户界面组件的基本构建块。视图在屏幕上占据一个矩形区域,负责绘图和事件处理。视图是小部件的基类,它用于创建交互式UI组件(按钮、文本字段等)。ViewGroup子类是布局的基类,它们是保存其他视图(或其他视图组)的无形容器,并定义它们的布局属性。(注: 此翻译根据有道词典参考,后续的翻译均是

说得通俗点,view就是我们的布局,view就是我们所看到的,而自定义view时,我们继承View类,实现其中的一些方法,比如官方解释: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 of these methods. In fact, you can start by just overriding onDraw(android.graphics.Canvas).要实现自定义视图,通常首先要为框架调用所有视图的一些标准方法提供覆盖。您不需要覆盖所有这些方法。实际上,您可以从重写onDraw(android.graphics.Canvas)开始。

2、ViewGroup

官方解释: 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是可以包含其他视图(称为children)的特殊视图。视图组是布局和视图容器的基类。这个类还定义了ViewGroup。LayoutParams类作为布局参数的基类。

简单了解一下view和viewGroup的区别:
ViewGroup继承自View,是一种特殊的View,它可以装其他的Views(或其他的ViewGroup)。ViewGroup是布局(layouts)和views containers的父类。它的直接子类有: FrameLayout, GridLayout, LinearLayout等等。Example: LineraLayout
View类代表的是UI components的基本的构建块。a view 占据屏幕的一块方形区域,负责绘制和事件处理。View是用来创建交互性的UI组件(如:按钮,文本框等等)的 widgets的父类。Example:Button
说的通俗点就是一个布局可以包含其他的布局和一些widgets。

3、自定义

先看最终的效果图:

《关于滑动冲突踩的那些坑以及解决办法》

首先先明确一下我们要干什么,现在我们模仿一下QQ的抽屉式侧滑(此处就是根据鸿洋大神的仿QQ5.0侧滑来做的),这里我们因为要实现抽屉式的侧滑,我们就不能用官方的侧滑,因为那个没有抽屉式的动画。当然首先考虑到的就是HorizontalScrollView这个横向的滚动,毕竟我们的抽屉式就是横向滑动的,效果图如下

《关于滑动冲突踩的那些坑以及解决办法》

,可以看到,虽然的却有横向的滑动,但是并没有想象中的那种抽屉式,就只有横向的滑动,这就需要用到我们的自定义ViewGroup了。为什么这里说是自定义ViewGroup呢?你可以再看一看之前简单的说了一下View和ViewGroup的区别,我们这次最外层的这个抽屉式滑动时最外层的布局,然后里面放了子布局,所以从这一方面来说,这个抽屉式的布局是一个ViewGroup,当然,你也可以从代码中看见,我们直接继承了HorizontalScrollView,虽然这里我们没有直接继承ViewGroup,但是HorizontalScrollView是继承了FrameLayout,而FrameLayout才是继承的ViewGroup,所以从根本上来将,我们的抽屉式布局就是继承的ViewGroup,所以它也算一个ViewGroup,至于为什么不直接继承ViewGroup呢?这个也是可以的,这里为了少写点代码量,我就直接继承了HorizontalScrollView,因为HorizontalScrollView中很多属性是我们直接需要的,所以可以不用去继承ViewGroup,那么我们接下来就先看一下自定义的ViewGroup吧,

下面直接看代码:

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;  //菜单区
    private ViewGroup mContent;  //内容区
    private int mScreenWidth;
    private int mMenuWidth;

    private int mMenuRightPadding;

    private boolean once = false;
    private boolean isOpen;


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

    /**
     * 未使用自定义View时调用
     *
     * @param  context
     * @param attrs
     * */
    public MyHorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * 使用自定义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;

    }

    /**
     * 设置自己的宽和高
     * 设置子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);
    }

    /**
     * 通过设置偏移量来将menu隐藏
     * */
    @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:
                //隐藏在左边的宽度
                int scrollX= getScrollX();
                if (scrollX >= mMenuWidth / 2) {
                    this.smoothScrollTo(mMenuWidth, 0);  //隐藏时有动画
                    isOpen = false;
                } else {
                    this.smoothScrollTo(0, 0);
                    isOpen = true;
                }
                return true;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 滚动事调用
     * */
    @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);  //透明度

        //属性动画TranslationX,默认动画有时间限制,需要自己去设置时间
        mMenu.animate().translationX(mMenuWidth * scale * 0.8f).alpha(leftAlpha).setDuration(0).start();
    }

    /**
     * 打开菜单
     */
    public void openMenu() {
        if (isOpen) return;
        this.smoothScrollTo(0, 0);
        isOpen = true;
    }

    /**
     * 关闭菜单
     * */
    public void closeMenu() {
        if (!isOpen) return;
        this.smoothScrollTo(mMenuWidth, 0);
        isOpen = false;
    }

    /**
     * 切换菜单
     * */
    public void changeMenu() {
        if (isOpen) {
            closeMenu();
        } else {
            openMenu();
        }
    }

}

相信看过Android开发艺术探索的人都知道这些事怎么回事了,如果还有不太了解的人,就去看看Android开发艺术探索鸿洋大神的视频,一些解释代码里面注释得也很清楚了,相信大家都看得懂,这些是自定义ViewGroup(简单说就是自定义View)的基本用法,一些最简单的用法。这个时候,在鸿洋大神的指导下,这个抽屉式的滑动我们基本就完成了,没错就是这么简单。

来看一下效果图:

《关于滑动冲突踩的那些坑以及解决办法》 抽屉式完成

4、冲突解决

细心的人都应该会发现,我们右侧的主布局中添加了很多横向流的RecyclerView,但是会注意到一点,不管我在右侧主布局中哪里向右滑动,都会把侧滑拉出来,而且当我想去滑动RecyclerView时,却发现,这些Item都不能滑动,滑动的时候就只是把左侧的菜单拉出来了,这就是我们接下来要讲的滑动冲突了。

不管是Android开发艺术探索还是网上的各种博客,关于解决滑动冲突的不在少数,而滑动冲突常见的就那么几种:不同向的冲突、同向的冲突、两者的结合形成的复合型冲突,在Android开艺术探索中提供一些通用的解决方法,外部拦截和内部拦截,书中也比较详细的用例子说明了冲突的解决方法,当然,可以总结出,简单的是外部拦截,内部拦截比较复杂,书上的Demo是网上很多博客使用到的,但博客大多数都是关于垂直方向上的ListView和ScrollView的嵌套产生的滑动冲突,这就跟我们本次的很类似,但是具体情况又有所不同,下面我们开始解决滑动冲突。

首先我们尝试使用外部拦截,代码入下:

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;  //菜单区
    private ViewGroup mContent;  //内容区
    private int mScreenWidth;
    private int mMenuWidth


    private int mMenuRightPadding;

    private boolean once = false;
    private boolean isOpen;

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

    /**
     * 未使用自定义View时调用
     *
     * @param  context
     * @param attrs
     * */
    public MyHorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * 使用自定义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;

    }

    /**
     * 设置自己的宽和高
     * 设置子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);
    }

    /**
     * 通过设置偏移量来将menu隐藏
     * */
    @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:
                //隐藏在左边的宽度
                int scrollX= getScrollX();
                if (scrollX >= mMenuWidth / 2) {
                    this.smoothScrollTo(mMenuWidth, 0);  //隐藏时有动画
                    isOpen = false;
                } else {
                    this.smoothScrollTo(0, 0);
                    isOpen = true;
                }
                return true;
        }
        return super.onTouchEvent(ev);
    }

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

    /**
     * 事件分发,冲突处理,外部拦截
     * */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                //处理逻辑
                if (mGiveUpTouchEventListener != null) {
                    if (mGiveUpTouchEventListener.giveUpTouchEvent(ev)) {
                        intercepted = true;
                    }
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
            default:
                break;
        }
        return intercepted;
        
    }

    /**
     * 滚动事调用
     * */
    @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);  //透明度

        //属性动画TranslationX,默认动画有时间限制,需要自己去设置时间
        mMenu.animate().translationX(mMenuWidth * scale * 0.8f).alpha(leftAlpha).setDuration(0).start();
    }

    /**
     * 打开菜单
     */
    public void openMenu() {
        if (isOpen) return;
        this.smoothScrollTo(0, 0);
        isOpen = true;
    }

    /**
     * 关闭菜单
     * */
    public void closeMenu() {
        if (!isOpen) return;
        this.smoothScrollTo(mMenuWidth, 0);
        isOpen = false;
    }

    /**
     * 切换菜单
     * */
    public void changeMenu() {
        if (isOpen) {
            closeMenu();
        } else {
            openMenu();
        }
    }

    //这里是为了使用外部拦截解决滑动冲突的接口
    public interface OnGiveUpTouchEventListener {
        public boolean giveUpTouchEvent(MotionEvent event);
    }

}

外层的Activity去实现接口代码逻辑:

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 (拦截事件)  //此处为伪代码
            return true;
        return false;
    }
}

从代码中我们可以知道,当我们需要拦截的时候,我们就在接口中的方法giveUpTouchEvent去判断是否需要拦截,需要接返回true去拦截,这个时候我们要根据RecyclerView中的第一个Item是否在第一个,如果在,我们就拦截,此时的滑动处理就由外层MyHorizontalScrollView去处理,当到达了最后一个时,也交给MyHorizontalScrollView去处理。(注意:这里是我踩的一个很大的坑)在处理这个逻辑过程中,我们会很容易的发现,这个逻辑是很容易就写出来的,但是,运行过程中,会很轻易的发现程序崩溃了,因为我们去判断Item的位置时会用到RecyclerView,但是程序崩溃的原因就是RecyclerView为空,这里目前我没找到具体的原因,有知道的大神望指教,这也就导致了这里不太方便使用这种拦截方式,所以接下来我们试一下内部拦截。

内部拦截需要重写RecyclerView,代码如下:

 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) {
                // 如果是水平向左滑动,且不能滑动了,则返回给上一层view处理
                if (!canScrollHorizontally(1)) {
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                }
            } else if (x > lastX) {
                // 如果是水平向右滑动,且不能滑动了,则返回给上一层view处理
                if (!canScrollHorizontally(-1)) {
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                }
            }
        }
        lastX = ev.getX();
        return super.dispatchTouchEvent(ev);
    }
}

接着外层布局也有相应的一些改变,代码如下:

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;  //菜单区
    private ViewGroup mContent;  //内容区
    private int mScreenWidth;
    private int mMenuWidth;

    private int mMenuRightPadding;

    private boolean once = false;
    private boolean isOpen;


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

    /**
     * 未使用自定义View时调用
     *
     * @param  context
     * @param attrs
     * */
    public MyHorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * 使用自定义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;

    }

    /**
     * 设置自己的宽和高
     * 设置子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);
    }

    /**
     * 通过设置偏移量来将menu隐藏
     * */
    @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:
                //隐藏在左边的宽度
                int scrollX= getScrollX();
                if (scrollX >= mMenuWidth / 2) {
                    this.smoothScrollTo(mMenuWidth, 0);  //隐藏时有动画
                    isOpen = false;
                } else {
                    this.smoothScrollTo(0, 0);
                    isOpen = true;
                }
                return true;
        }
        return super.onTouchEvent(ev);
    }


    /**
     * 事件分发,冲突处理,外部拦截
     * */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
       //此处为内部拦截时需要
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onTouchEvent(ev);
            return false;
        }
        return true;
    }

    /**
     * 滚动事调用
     * */
    @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);  //透明度

        //属性动画TranslationX,默认动画有时间限制,需要自己去设置时间
        mMenu.animate().translationX(mMenuWidth * scale * 0.8f).alpha(leftAlpha).setDuration(0).start();
    }

    /**
     * 打开菜单
     */
    public void openMenu() {
        if (isOpen) return;
        this.smoothScrollTo(0, 0);
        isOpen = true;
    }

    /**
     * 关闭菜单
     * */
    public void closeMenu() {
        if (!isOpen) return;
        this.smoothScrollTo(mMenuWidth, 0);
        isOpen = false;
    }

    /**
     * 切换菜单
     * */
    public void changeMenu() {
        if (isOpen) {
            closeMenu();
        } else {
            openMenu();
        }
    }


}

可以发现,内部拦截时,外层就只改变了几行代码,接下来我们看效果:

《关于滑动冲突踩的那些坑以及解决办法》

可以看到,RecyclerView的冲突解决了,从内部完美的解决了,判断逻辑中canScrollHorizontally(1)这个函数,它的命名就跟它的用途是一样的,去判断是否还能滑动,传进去的参数是方向,>0向左,<0向右,(跟坐标轴类似),但是又出现了一个新的问题,主布局的ScrollView好像不能滑动了,从截图中可以看出来,ScrollView的上下滑动完全失效了,这时候我们就得去分析了,我们先看MyRecyclerView这个自定义的View,

    if (lastX > x) {
                // 如果是水平向左滑动,且不能滑动了,则返回给上一层view处理
                if (!canScrollHorizontally(1)) {
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                }
            } else if (x > lastX) {
                // 如果是水平向右滑动,且不能滑动了,则返回给上一层view处理
                if (!canScrollHorizontally(-1)) {
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                }
            }

我们可以看到,这里的判断逻辑没啥问题,达到了我们预期的效果,但是注意看这一行代码
getParent().getParent().requestDisallowInterceptTouchEvent(false);
这行代码表示的返回给自己的父布局的父布局View处理事件,那我们又去看看Layout的代码

<?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>

可以发现,MyRecyclerView的父布局是ScrollView,那么事件就返回给了ScrollView,而ScrollView我们并没有去处理这个事件,所以ScrollView又返回给了它的父布局,就这样一层一层的返回,最终到了MyHorizontalScrollView,然后事件被处理,这就解决了们可以侧滑 问题,但是为什么ScrollView失效了呢?其实仔细分析,这时候虽然内部拦截了,但外部的ScrollView与MyHorizontalScrollView又冲突了,这个冲突又是我们开头提到的另一种不同向的冲突,所以这个时候我们又需要解决这个。如果我们继续采用内部拦截去重写一个ScrollView,这无疑是增加代码量的。这时候我们可以考虑一下外部拦截,去直接处理外部的上下滑动,
代码如下:

 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;  //菜单区
    private ViewGroup mContent;  //内容区
    private int mScreenWidth;
    private int mMenuWidth;

    //分别记录上次滑动的坐标(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);
    }

    /**
     * 未使用自定义View时调用
     *
     * @param  context
     * @param attrs
     * */
    public MyHorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * 使用自定义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;

    }

    /**
     * 设置自己的宽和高
     * 设置子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);
    }

    /**
     * 通过设置偏移量来将menu隐藏
     * */
    @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:
                //隐藏在左边的宽度
                int scrollX= getScrollX();
                if (scrollX >= mMenuWidth / 2) {
                    this.smoothScrollTo(mMenuWidth, 0);  //隐藏时有动画
                    isOpen = false;
                } else {
                    this.smoothScrollTo(0, 0);
                    isOpen = true;
                }
                return true;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 事件分发,冲突处理,外部拦截
     * */
    @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);  //处理内部拦截时
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                //外部拦截ScrollView的上下滑动,当ScrollView上下滑动时,事件给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;
    }

    /**
     * 滚动事调用
     * */
    @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);  //透明度

        //属性动画TranslationX,默认动画有时间限制,需要自己去设置时间
        mMenu.animate().translationX(mMenuWidth * scale * 0.8f).alpha(leftAlpha).setDuration(0).start();
    }

    /**
     * 打开菜单
     */
    public void openMenu() {
        if (isOpen) return;
        this.smoothScrollTo(0, 0);
        isOpen = true;
    }

    /**
     * 关闭菜单
     * */
    public void closeMenu() {
        if (!isOpen) return;
        this.smoothScrollTo(mMenuWidth, 0);
        isOpen = false;
    }

    /**
     * 切换菜单
     * */
    public void changeMenu() {
        if (isOpen) {
            closeMenu();
        } else {
            openMenu();
        }
    }

}

就这样使用一个外部拦截,接下来看效果图

《关于滑动冲突踩的那些坑以及解决办法》

结果很明显,解决了滑动冲突,而且也实现了抽屉式的侧滑。这里外部拦截时去判断了滑动方向,就解决了不同向的冲突。

最后,总结一下,这次的滑动冲突我们及使用了外部拦截又使用了内部拦截,而且处理的逻辑并不是很难,就那么I行代码,所以以后遇到滑动冲突千万别怕,仔细分析一下,使用外部拦截或者内部拦截去解决就是,顺便放上Demo地址

感谢各位大神给的参考:

鸿洋
掘金
Android开发艺术探索

    原文作者:SoledadOne
    原文地址: https://www.jianshu.com/p/6033392585b6
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞