两种方法,教你解决 ViewPager 嵌套 ViewPager滑动冲突(二)

简介: 两种方法,教你解决 ViewPager 嵌套 ViewPager滑动冲突

ScrollView 里面嵌套ViewPager导致的滑动冲突


外部解决法


如上面所述,从 父View ScrollView着手,重写 OnInterceptTouchEvent方法,在上下滑动的时候拦截事件,在左右滑动的时候不拦截事件,返回 false,这样确保子View 的dispatchTouchEvent方法会被调用,代码 如下


/**
 * @ explain:这个ScrlloView不拦截水平滑动事件,
 * 是用来解决 ScrollView里面嵌套ViewPager使用的
 * @ author:xujun on 2016/10/25 15:28
 * @ email:gdutxiaoxu@163.com
 */
public class VerticalScrollView extends ScrollView {
    public VerticalScrollView(Context context) {
        super(context);
    }
    public VerticalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public VerticalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @TargetApi(21)
    public VerticalScrollView(Context context, AttributeSet attrs, int defStyleAttr, int
            defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    private float mDownPosX = 0;
    private float mDownPosY = 0;
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final float x = ev.getX();
        final float y = ev.getY();
        final int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mDownPosX = x;
                mDownPosY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                final float deltaX = Math.abs(x - mDownPosX);
                final float deltaY = Math.abs(y - mDownPosY);
                // 这里是否拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截
                if (deltaX > deltaY) {// 左右滑动不拦截
                    return false;
                }
        }
        return super.onInterceptTouchEvent(ev);
    }
}


内部解决法


如上面上述,通过requestDisallowInterceptTouchEvent(true)方法来影响父View是否拦截事件,我们通过重写ViewPager的 dispatchTouchEvent()方法,在左右滑动的时候请求父View ScrollView不要拦截事件,其他的时候由子View 拦截事件


/**
 * @ explain:这个 ViewPager是用来解决ScrollView里面嵌套ViewPager的 内部解决法的
 * @ author:xujun on 2016/10/25 16:38
 * @ email:gdutxiaoxu@163.com
 */
public class MyViewPager extends ViewPager {
    private static final String TAG = "xujun";
    int lastX = -1;
    int lastY = -1;
    public MyViewPager(Context context) {
        super(context);
    }
    public MyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int) ev.getRawX();
        int y = (int) ev.getRawY();
        int dealtX = 0;
        int dealtY = 0;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                dealtX = 0;
                dealtY = 0;
                // 保证子View能够接收到Action_move事件
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                dealtX += Math.abs(x - lastX);
                dealtY += Math.abs(y - lastY);
                Log.i(TAG, "dealtX:=" + dealtX);
                Log.i(TAG, "dealtY:=" + dealtY);
                // 这里是否拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截
                if (dealtX >= dealtY) { // 左右滑动请求父 View 不要拦截
                    getParent().requestDisallowInterceptTouchEvent(true);
                } else {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
}


注意事项(坑)


当我们 ScrollView 的最上层的 Layout 里面多多个孩子的时候,当下面一个孩子是 RecyclerView 或者ListView 的时候,往往会自动滑动到 ListView 或者 RecyclerView 的第一个 item,导致进入界面的时候会导致 RecyclerView 上面的 View 被滑动到界面之外,看不见,这时候的用户体验是比较差的


即结构如下面的时候


5c637b6a673a1156f4ff5d4618b0aa19_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dkdXR4aWFveHU=,size_16,color_FFFFFF,t_70.png


在Activity中的相关解决方法



于是我查找了相关的资料,在Activity中完美解决,主要要一下两种方法


第一种方法,重写Activity的onWindowFocusChanged()方法,在里面调用mNoHorizontalScrollView.scrollTo(0,0);方法,滑动到顶部,因为onWindowFocusChanged是在所有View绘制完毕的时候才会回调的,不熟悉的话建议先回去看一下Activity的生命周期的相关介绍



private void scroll() {
    mNoHorizontalScrollView.scrollTo(0,0);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if(hasFocus  && first){
        first=false;
        scroll();
    }
}


第二种解决方法,调用RecyclerView上面的View的一下方法,让其获取焦点


view.setFocusable(true);  
view.setFocusableInTouchMode(true);  
view.requestFocus();


这段代码在初始化的时候就让该界面的顶部的某一个控件获得焦点,滚动条自然就显示到顶部了。


在Fragment中的相关解决方法

同样是调用第二种方法,调用RecyclerView上面的View的一下方法,让其获取焦点


view.setFocusable(true);  
view.setFocusableInTouchMode(true);  
view.requestFocus();


这段代码在初始化的时候就让该界面的顶部的某一个控件获得焦点,滚动条自然就显示到顶部了。但是该方法存在缺点,就是当我们上面的view如果滑动到一半的时候,切换到下一个Fragment,在切换回来的时候,RecyclerView的第一个item会自动滑动到顶部。目前我还没有找到相对比较好的解决这个问题的方法,大家知道相关解决方法的话也欢迎联系我,可以加我 微信或者在留言区评论,谢谢。


网友提供的解决方案


关于 ViewPagerActivity 在Fragment页面切换的时候,RecyclerView抢占焦点的问题已经解决,特别 感谢Jianqiu,他的博客地址:http://niorgai.github.io/


在 ViewPagerActivity 里面的 Fragment的 代码中加入以下代码,可以阻止 RecyclerView 的子 View 获得焦点,从而阻止 RecyclerView 抢占位置。


// 是为了确保mNoHorizontalScrollView他的子孙不能获得焦点
mNoHorizontalScrollView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);


详细代码见项目中的ListFragement


个人疑点

借鉴于解决Activity的方法,目前我还没有找到一个方法是在Fragemnt界面完全绘制完毕以后回调的方法,如果大家知道怎样处理的 话,欢迎大家提出来


ViewPager里面嵌套ViewPager导致的滑动冲突


内部解决法


从子View ViewPager着手,重写 子View的 dispatchTouchEvent方法,在子 View需要拦截的时候进行拦截,否则交给父View处理,代码如下


public class ChildViewPager extends ViewPager {
    private static final String TAG = "xujun";
    public ChildViewPager(Context context) {
        super(context);
    }
    public ChildViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int curPosition;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                curPosition = this.getCurrentItem();
                int count = this.getAdapter().getCount();
                Log.i(TAG, "curPosition:=" +curPosition);
                // 当当前页面在最后一页和第0页的时候,由父亲拦截触摸事件
                if (curPosition == count - 1|| curPosition==0) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                } else {//其他情况,由孩子拦截触摸事件
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
        }
        return super.dispatchTouchEvent(ev);
    }
}


外部解决法


这个如果要采用外部解决法来解决的话想,相对很麻烦,我提一下自己的个人思路,我们可以先测量子View在哪个区域,然后我们在根据我们按下的点是否在区域以内,如果是的话,在根据子View时候需要拦截进行处理


讨论

c8e13eb0eb4402d30e2d9a6c97ec5b16_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dkdXR4aWFveHU=,size_16,color_FFFFFF,t_70.png


对于这种效果,上面是轮播图的,下面是RecyclerView或者ListView的,一般有一下几种实现方式


  • 使用我们上述提高的ScrollView里面嵌套ViewPager和RecyclerView,这种实现方式需要自己解决View滑动事件的冲突,同时还有我在上述提高的在Fragment中存在的问题
  • 使用listView的addHeaderView来实现,或者是通过多种不同的item来实现
  • 使用RecyclerView添加headerView来实现,或者复用多种不同的item来实现。关于RecyclerView如何添加headerView可以参考鸿洋大神的这一篇博客 Android 优雅的为RecyclerView添加HeaderView和FooterView
  • 使用SupportLibrary中的CoordinatorLayout等控件


其布局文件如下,Activity代码见项目中的SixActivity


<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/background_light"
    android:fitsSystemWindows="true"
>
    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    >
        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|snap">
            <android.support.v4.view.ViewPager
                android:id="@+id/viewPager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
            >
            </android.support.v4.view.ViewPager>
            <TextView
                android:id="@+id/tv_page"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:gravity="right"
                android:text="1/10"
                android:textColor="#000"/>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
    </android.support.v7.widget.RecyclerView>
</android.support.design.widget.CoordinatorLayout>


关于CoordinatorLayout的更多用法,可以参考我的这一篇博客使用CoordinatorLayout打造各种炫酷的效果


总结


  • 当我们滑动方向不同的时候,采用外部解决法和内部解决法,复杂度差不多。
  • 当我们滑动的方向相同的话,建议采用内部解决法来解决,因为采用外部解决法复杂度比较高。而且有时候我们是采用别人的开源控件,这时候去修改别人的源码可能会发生一些意想不到的bug。


题外话


  • 在这篇博客的最后提高的实现轮播图+list列表的几种实现形式,刚开始是不想写的,后面因为ScrollView里面嵌套ViewPager和RecyclerView在fragment中RecyclerView抢占焦点,在某些情况下用户体验不好,才写出来的,跟这篇博客要讲解的View滑动事件冲突没有多大关系,只是给读者提供多种思路而已
  • 至于CoordinatorLayout,是google IO 2015中提出来的,功能很强大,可以说是专门为了解决嵌套导滑动而产生的,极大地方便了开发者,对于初学者,可以暂时不必掌握它,先把其他的基础学好就好
  • 同时卖一下广告,欢迎大家到我的github上面star或者fork,谢谢


参考文章:图解 Android 事件分发机制


文章首发地址CSDN:http://blog.csdn.net/gdutxiaoxu/article/details/52939127


源码下载地址:https://github.com/gdutxiaoxu/TouchDemo.git


相关文章
|
Android开发
两种方法,教你解决 ViewPager 嵌套 ViewPager滑动冲突(一)
两种方法,教你解决 ViewPager 嵌套 ViewPager滑动冲突
|
XML Java 数据格式
ViewPager基本用法
用法:数据ImageView+自定义适配器+ViewPager控件
102 0
|
XML Java 数据格式
ViewPager和Fragment使用(附源码)
1 初级版 ViewPager通过滑动来切换Fragment,无底部导航栏。 用法:数据List<Fragment>+适配器FragmentPagerAdapter+ViewPager 通过FragmentPagerAdapter连接两者的桥梁,里面需要重写两个方法getItem,getCount。 第一个方法是获取一个Fragment,从数据源获取。 第二个方法是获取数据源的大小,也就是有几页。
169 0
|
安全
Fragment里面有ViewPager,ViewPager里面又嵌套Fragment,刚遇到的一个坑
Fragment里面有ViewPager,ViewPager里面又嵌套Fragment,刚遇到的一个坑
fragment嵌套viewpager不显示
fragment嵌套viewpager不显示
191 0
ViewPager(通过反射修改viewpager切换速度)
(创建于2016/11/17) import java.lang.reflect.Field; import android.content.
1122 0
|
Android开发
Android ViewPager嵌套Fragment出现白页面
解决ViewPager嵌套Fragment页面白屏 viewPager.setOffscreenPageLimit(Fragment的数量);
1562 0