Android实践之ScrollView中滑动冲突处理

转载注明出处:http://blog.csdn.net/xiaohanluo/article/details/52130923

1. 前言

在Android开发中,假设是一些简单的布局。都非常easy搞定。可是一旦涉及到复杂的页面,特别是为了兼容小屏手机而使用了ScrollView以后,就会出现非常多点击事件的冲突。最经典的就是ScrollView中嵌套了ListView。

我想大部分刚開始接触Android的同学们都踩到过这个坑,这一篇文章就从近期做的一个项目讲起。然后在过程中提供一些解决冲突的思路。

2. 项目起始

项目有一个页面,涉及到了ViewPager。MapView。ListView,也就是说在一个页面中,会有这三个View,非常明显,屏幕无法全然显示,须要ScrollView来做一下支援,就引入了ScrollView作为外层的容器。

可是由于这个页面的数据展示须要做到用户手动下拉刷新,于是又引入了官方的SwipeRefreshLayout。

于是这个页面的布局就成了这样子。

例如以下图(细节布局忽略)。


图-1 布局图

增加了ScrollView和SwipeRefreshLayout之后引入了新的问题。就是各个控件之间的事件冲突,嵌套在ScrollView中的ViewPager、MapView、ListView都须要能够正确的处理点击事件,特别是ListView。需求要求它在ScrollView中能够滑动,两种滑动混淆在一起,不是特别优点理。

问题提出来了,以下直接看解决思路。

3. 解决滑动冲突的思路

在ViewGroup中有个方法叫requestDisallowInterceptTouchEvent(boolean disallowIntercept),这种方法能够用来控制该ViewGroup是否截断点击事件。我们解决滑动冲突的时候,事实上就是在某个时机去调用这种方法。让父布局不截断点击事件,将点击事件传递到子View。让相关的子View去处理。

以下就是关于在项目中处理各种点击事件冲突的一些样例和思考。处理的方法仅仅是提供一种思路,可能并非最优的方法,肯定存在其它思路的解决方式。

以下处理滑动冲突的方案都是在子View的OnTouchListener里面进行处理。并没有去复写控件的点击事件处理过程。在使用中还是比較方便的。

3.1 MapView地图页面滑动冲突

MapView与ScrollView的冲突主要在于,当用户点击到MapView地图并且滑动的时候。希望由地图Map去处理点击事件。并包含兴许的滑动事件、双手指缩放地图等等。

在ScrollView中,是会默认截断点击事件的,导致用户点击到地图后,地图基本是没有反应。更别谈双手指缩放地图了。

用户手指点击到地图,并且滑动的时候,非常难确定用户是要ScrollView上下滑动还是操控地图内容滑动,所以我简单的觉得,仅仅要用户手指点击到地图。就是要对地图进行操作。当用户手指抬起。就觉得用户不须要操作地图了。

解决思路也非常简单。就是在用户点击到地图或者滑动地图时候,让ScrollView不截断点击事件。并传递给子View处理。也就是地图去处理点击事件;当用户手指抬起的时候,将ScrollView的状态恢复至之前的状态。也就是ScrollView能够截断点击事件。

我使用的是百度地图,直接上代码,更easy理解。

mMapView.getChildAt(0).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if(event.getAction() == MotionEvent.ACTION_UP){
                    //同意ScrollView截断点击事件,ScrollView可滑动
                    mScrollView.requestDisallowInterceptTouchEvent(false);
                }else{
                    //不同意ScrollView截断点击事件,点击事件由子View处理
                    mScrollView.requestDisallowInterceptTouchEvent(true);
                }
                return false;
            }
        });

3.2 ViewPager滑动冲突解决

在这个项目中,ViewPager在页面最顶层。假设仅仅是ScrollView里面嵌套了ViewPager。由于这两个控件是不同方向的滑动事件,所以基本不会出现冲突。

可是由于引入了SwipeRefreshLayout,我发如今滑动ViewPager的时候。非常easy就触发了SwipeRefreshLayout的下来刷新,进而有可能阻断了ViewPager的左右滑动效果,体验非常不好。并且在滑动ViewPager的过程中,用户滑动肯定不是一直水平的,会有一定程度向上向下的滑动。

ViewPager处理冲突和地图处理冲突有些不同,由于当用户点击到ViewPager,在滑动过程中。基本就能够推測到用户是想左右滑动ViewPager还是上下滑动ScrollView(或者下拉刷新),这就不能像地图一样。在点击到ViewPager就禁止ScrollView截断点击事件(或者SwipeRefreshLayout下拉刷新功能),须要在滑动过程中做出推断。

解决思路就是,设定一个阈值,一旦用户在X轴也就是横向滑动距离超过这个阈值,我就觉得用户是要左右滑动ViewPager。就禁止ScrollView截断点击事件同一时候设置SwipeRefreshLayout不能下拉刷新。当用户抬起手指,就觉得用户对ViewPager的操作已经完成,将ScrollView和SwipeRefreshLayout状态恢复。

mViewPager.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int action = event.getAction();

        if(action == MotionEvent.ACTION_DOWN) {
            // 记录点击到ViewPager时候,手指的X坐标
            mLastX = event.getX();
        }
        if(action == MotionEvent.ACTION_MOVE) {
            // 超过阈值
            if(Math.abs(event.getX() - mLastX) > 60f) {
                mRefreshLayout.setEnabled(false);
                mScrollView.requestDisallowInterceptTouchEvent(true);
            }
        }
        if(action == MotionEvent.ACTION_UP) {
            // 用户抬起手指,恢复父布局状态
            mScrollView.requestDisallowInterceptTouchEvent(false);
            mRefreshLayout.setEnabled(true);
        }
        return false;
    }
});

用户点击到ViewPager时候,记录下点击位置的X坐标,当用户滑动过程中,假设在X轴上面的滑动超过阈值(我写的是60f,这个能够在实际使用中自行设置最佳的阈值)。就禁止ScrollView截断点击事件,同一时候设置不可下拉刷新。当用户手指离开屏幕。将ScrollView和SwipeRefreshLayout的状态恢复。

3.3 ListView滑动冲突解决

在ScrollView中嵌套ListView,会出现各种各样奇怪的问题。比方说ListView显示有问题,可能才一两个Item那么高。并没有全然的展开。

网上流传解决这种问题的方法会有两种。

  • 依据展示数据的个数乘以每个Item的高度。计算出ListView的整体高度,然后动态的设置ListView的高度
  • 复写ListView的onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,让ListView全然展开

这两种方法都能够解决ListView展示不全然的问题。并且也能够滑动(事实上是使用ScrollView的滑动效果),可是有一个最大的遗憾。就是ListView里面的View不能复用了。由于这两种方法都是算出了ListView的所有高度,然后将ListView控件的高度设置成这个高度。这种话,ListView就相当于一个LinearLayout的布局了。失去了复用View的优势。并且在某些场景可能还没有LinearLayout好用。更甚的是,假设有大量图片的话,非常easy就OOM了。这是在研发过程中最不希望看见的。

能够參考一下美团,美团的首页。就是一个ScrollView,下滑的时候会发现,并不能无限向下滑动,到了底部会提醒跳转到一个二级页面去查看所有的团购信息。这是处理ScrollView里面嵌套相似ListView列表布局的时候的一种解决方式。

可是在我遇见的这个项目里面,并不能这样处理。

上面的提到的两种解决思路非常明白,假设想要ListView正常展示就须要确定ListView的高度,这个非常重要。

所以首先。我须要在布局文件里设置ListView的高度。是一个明白的数值。设置高度之后。假设ListView中的数据的Item总高度超过ListView所设置的高度,就能够复用View了。可是这仅仅是攻克了ListView的显示问题,ListView与ScrollView的滑动冲突,并没有解决。

要解决滑动的冲突,最基本的是确定禁止ScrollView截断点击事件的时机,然后来分析有哪些时机。

  • ScrollView在未滑动究竟部时候,假设点击并滑动ListView时候。ListView是不能滑动的。不禁止。

  • 假设ScrollView滑动究竟部,且ListView已经到顶部,继续下拉ListView,事实上会拉动ScrollView,不禁止。

  • 假设ScrollView滑动究竟部,用户向上滑。ListView滑动,禁止ScrollView截断点击事件能力

非常明显,在推断禁止ScrollView截断点击事件时机的时候,须要知道ScrollView是否滑动到了底部。于是。重写了ScrollView的ScrollChanged()方法,来推断ScrollView是否滑动究竟部(SDK API 23版本号中ScrollView能够设置setOnScrollChangeListener()来监听滑动的变化,可是之前版本号不支持。为了兼容。自己须要重写)。

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt){
    super.onScrollChanged(l,t,oldl,oldt);
    // 滑动的距离加上本身的高度与子View的高度对照
    if(t + getHeight() >=  getChildAt(0).getMeasuredHeight()){
        // ScrollView滑动究竟部
        if(mOnScrollToBottomListener != null) {
            mOnScrollToBottomListener.onScrollToBottom();
        }
    } else {
        if(mOnScrollToBottomListener != null) {
            mOnScrollToBottomListener.onNotScrollToBottom();
        }
    }
}

public void setScrollToBottomListener(OnScrollToBottomListener listener) {
    this.mOnScrollToBottomListener = listener;
}

public interface OnScrollToBottomListener {
    void onScrollToBottom();
    void onNotScrollToBottom();
}

有了思路,并且ScrollView滑动究竟部的标识也能够拿到,以下就能够直接来解决滑动冲突了。直接看代码。

mScrollView.setScrollToBottomListener(new BottomScrollView.OnScrollToBottomListener() {
    @Override
    public void onScrollToBottom() {
        isSvToBottom = true;
    }

    @Override
    public void onNotScrollToBottom() {
        isSvToBottom = false;
    }
});

mListView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int action = event.getAction();

        if(action == MotionEvent.ACTION_DOWN) {
            mLastY = event.getY();
        }
        if(action == MotionEvent.ACTION_MOVE) {
            int top = mListView.getChildAt(0).getTop();
            float nowY = event.getY();
            if(!isSvToBottom) {
                // 同意scrollview拦截点击事件, scrollView滑动
                mScrollView.requestDisallowInterceptTouchEvent(false);
            } else if(top == 0 && nowY - mLastY > THRESHOLD_Y_LIST_VIEW) {
                // 同意scrollview拦截点击事件, scrollView滑动
                mScrollView.requestDisallowInterceptTouchEvent(false);
            } else {
                // 不同意scrollview拦截点击事件, listView滑动
                mScrollView.requestDisallowInterceptTouchEvent(true);
            }
        }
        return false;
    }
});

相对于其它的控件来说,ListView和ScrollView之间的滑动冲突更难解决,但事实上在实际使用中并不推荐ScrollView里面嵌套ListView,一旦业务复杂,非常easy出现各种UI和业务逻辑冲突的错误。

4. 执行效果

由于地图增加比較麻烦。所以在Demo中并没有引入地图。看一下执行效果。


图-2 执行效果

5. 总结

本篇文章仅仅是提供一种解决方法的思路。在详细的场景下,交互往往是贴合详细业务需求的。可是无论怎么样,找出点击事件截断和处理的时机是最重要的,环绕这个关键点,总能找出对应的解决方法。

附上Demoproject地址:Demoproject地址链接

原文地址:https://www.cnblogs.com/llguanli/p/8324704.html

时间: 2024-11-25 11:51:52

Android实践之ScrollView中滑动冲突处理的相关文章

Android 解决下拉刷新控件和ScrollVIew的滑动冲突问题。

最近项目要实现ScrollView中嵌套广告轮播图+RecyleView卡片布局,并且RecyleView按照header和内容的排列样式,因为RecyleView的可扩展性很强,所以我毫无疑问的选择了它,而且让RecyleView实现了可拖拽的效果, 最后我再加上了下拉刷新的效果(这里我用的下拉刷新控件是三方的SmartRefreshLayout).记得刚开始实现这个效果的时候还是十分的得心印手.可是当我测试的时候,发现RecyleView的子item的拖拽效果并不流畅,起初我以 为是由于Re

解决下拉刷新控件和ScrollVIew的滑动冲突问题。

最近项目要实现ScrollView中嵌套广告轮播图+RecyleView卡片布局,并且RecyleView按照header和内容的排列样式,因为RecyleView的可扩展性很强,所以我毫无疑问的选择了它,而且让RecyleView实现了可拖拽的效果, 最后我再加上了下拉刷新的效果(这里我用的下拉刷新控件是三方的SmartRefreshLayout).记得刚开始实现这个效果的时候还是十分的得心印手.可是当我测试的时候,发现RecyleView的子item的拖拽效果并不流畅,起初我以 为是由于Re

[转载]Android: 如何实现ScrollView中含有ListView?

原文地址:Android: 如何实现ScrollView中含有ListView?作者:mailofzxf ListView本身就含有ScrollView,因此把ListView放到ScrollView中会引起混乱(谁来响应滑动事件?)但有时又确有此需求,以实现ListView的内容连同其他内容的滚动. 要想把ListView嵌入ScrollView,有二个方法: 方法1:整体上使用一个ListView, 把不需滚动的部分放入ListView的Header或Footer中.注意: 一定要先添加He

Android 解决WebView和ScrollView滚轮滑动冲突

第一步,重写WebView的onGenericMotionEvent方法 /** * 滑轮处理 */ @Override public boolean onGenericMotionEvent(MotionEvent event) { if (callback != null) return callback.onGenericMotionEvent(event); return super.onGenericMotionEvent(event); } //定义一个接口,把滚动事件传递出去 pu

自定义双向seekbar中横向滑动和scrollview的滑动冲突

/** * Tries to claim the user's drag motion, and requests disallowing any * ancestors from stealing events in the drag. * * 试图告诉父view不要拦截子控件的drag */ private void attemptClaimDrag() { if (getParent() != null) { getParent().requestDisallowInterceptTouc

Android 监听ScrollView的滑动

我们需要监听ScroView的滑动情况,比如滑动了多少距离,是否滑到布局的顶部或者底部.可惜的是SDK并没有相应的方法,不过倒是提供了一个 protected void onScrollChanged(int l, int t, int oldl, int oldt) 显然这个方法是不能被外界调用的,因此就需要把它暴露出去,解决方式就是写一个接口 /** * Created by 刘楠 on 2016/8/21 0021.17:24 */ public interface ScrollViewL

android 布局文件 ScrollView 中的 listView item 显示不全解决方案

import android.content.Context;import android.util.AttributeSet;import android.widget.ListView; /** * Created by wanqi on 15-7-20. */public class ScrollListView extends ListView { public ScrollListView(Context context, AttributeSet attrs) { super(con

教你用两招就完美解决 Android 滑动冲突!

冲突情况 在 Android 开发中,滑动冲突总是我们一个无法避免的话题.而对于解决方案却是众说纷纭.比如 RecyclerView 嵌套 RecyclerView,直接通过相关方法禁掉内部 RecyclerView 的滑动:ScrollView 嵌套 RecyclerView 直接把 ScrollView 替换为 NestedScrollView 等等. 但我们今天要说的是在自定义 View 中遇到滑动冲突时,我们又应该如何处理呢?当然,今天的话题需要 View 的事件分发机制做理论前提. 1

ScrollView中ViewPager无法正常滑动问题

本文主要介绍如何解决ViewPager在ScrollView中滑动经常失效.无法正常滑动问题. 解决方法只需要在接近水平滚动时ScrollView不处理事件而交由其子View(即这里的ViewPager)处理即可,重写ScrollView的onInterceptTouchEvent函数,如下: package cc.newnews.view; import android.content.Context; import android.util.AttributeSet; import andr