Android 解决ScrollView与父视图滑动冲突问题

最近在解决一个比较棘手的问题,就是ScrollView与父视图之间滑动冲突的问题。

这里所说的滑动冲突,是指包裹ScrollView的父视图本身就是一个可以滑动的组件,比如说ScrollView嵌套ScrollView的情况(当然,这在实际应用中是没有意义的,因为ScrollView只能包含一个子组件,这里只是举一个栗子)。


问题定义

说要解决这样一个问题,肯定会有点摸不到头脑,但我们可以用分解法,来“大事化小”。

遇到这种滑动事件冲突的嵌套首先要明白一个孰先孰后的问题,就是先让父组件滑动还是先让子组件滑动。按照一般的场景,我们一般会选择让子组件先滑动,因为这样会比较好处理一点。

那么问题就来了,为了先让子组件滑动,我们需要做哪些事情呢?

  1. 监听子组件滑动到顶部
  2. 监听子组件滑动到底部
  3. 在子组件滑动完成后将滑动事件传递回父组件
  4. 可能的卡顿及优化

好了,根据这几个问题,让我们一个个来解决他们。

解决步骤

1. 子组件滑动到顶部与底部的监听

其实上面的问题1和问题2是可以合并为一个监听子组件滑动靠岸的问题,因为无论是靠顶部还是靠底部其实都是滑动已经完成的信号。

这里我们需要自定义一个自己的ScrollView:


import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ScrollView;

/**
 * Created by lemondoor on 15/8/8.
 */
public class MyScrollView extends ScrollView {
    private View contentView;  //ScrollView包含的子组件

    private OnBorderListener onBorderListener;

    public MyScrollView(Context context) {
        super(context);
    }

    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /* 在滑动发生时监听滑动靠岸 */
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        doOnBorderListener();
    }

    /**
     * 关键函数,判断什么时候滑动靠岸
     * 因为ScrollView只包含一个子组件,因此子组件的原始测量高度就是ScrollView的高度,
     * 当纵向滑动值(滑动出屏幕的高度)加上屏幕上显示的高度大于或等于ScrollView的高度时,就说明到底了,
     * 当纵向滑动值为0的时候,是从顶部开始显示的,因此说明处于顶部位置
     */
    private void doOnBorderListener() {
        if (contentView != null && contentView.getMeasuredHeight() <= getScrollY() + getHeight()) {
            if (onBorderListener != null) {
                onBorderListener.onBottom();
            }
        } else if (getScrollY() == 0) {
            if (onBorderListener != null) {
                onBorderListener.onTop();
            }
        }
    }

    public void setOnBorderListener(final OnBorderListener onBorderListener) {
        if(onBorderListener == null) {
            this.onBorderTouchListener = null;
            return;
        } else {
            this.onBorderListener = onBorderListener;
        }

        if(contentView == null) {
            contentView = getChildAt(0);  //因为ScrollView只能包含一个子组件,因此可以直接通过索引‘0’获取子组件
        }
    }

    /**
     * OnborderListener, called when scroll to top or bottom
     */
    public static interface OnBorderListener {
        /**
         * Called when scroll to bottom
         */
        public void onBottom();

        /**
         * Called when scroll to top
         */
        public void onTop();
    }
}

好了,通过上面的代码,我们已经可以检测到子组件滑动完成的状态了。

2. 在检测到子组件滑动完成后将滑动事件传递给父组件

前面拿到了子组件滑动靠岸的信息,这个时候就需要将事件还给父组件了,毕竟人家已经眼巴巴地让小辈先玩了那么久。

这一步,我们需要在实例化的MyScrollView的类中进行相应地响应处理。

这就用到了requestDisallowInterceptTouchEvent方法:

mScrollView.setOnBorderListener(new MyScrollView.OnBorderListener() {
            @Override
            public void onBottom() {
               //设为false表示已经不关心该事件了,允许父组件进行事件拦截
               mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
            }

            @Override
            public void onTop() {
                //设为false表示已经不关心该事件了,允许父组件进行事件拦截
                mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
            }
        });

3. 优化

上面的两步其实已经将问题解决了,但如果试着做一下就会发现,在刚进入页面的时候,子组件处于顶部的判断是没有进行处理的,而且有的时候必须回拉一下才能响应到靠岸的事件。

根据上面的问题,我们制定了两步的优化处理步骤:

3.1 处理子组件初始靠岸状态

其实滑动还是需要手指去与屏幕交互的,我们没有必要在看到页面的状态就判断子组件是否靠岸,只需要在滑动的时候进行判断就好了。

回到我们的MyScrollView类,进行如下修改:

private OnTouchListener  onBorderTouchListener;

public void setOnBorderListener(final OnBorderListener onBorderListener) {
        if(onBorderListener == null) {
            this.onBorderTouchListener = null;
            return;
        } else {
            this.onBorderListener = onBorderListener;
        }

        if(contentView == null) {
            contentView = getChildAt(0);  //因为ScrollView只能包含一个子组件,因此可以直接通过索引‘0’获取子组件
        }

        /* 其实这是另一种处理监听滑动靠岸的方法,是在touch事件发生时进行滑动靠岸监听 */
        this.onBorderTouchListener = new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    //这里面试了几个动作,最终正明MOVE动作的效果是最好,最及时的
                    case MotionEvent.ACTION_MOVE :
                        doOnBorderListener();
                        break;
                }
                return false;
            }

        };
        super.setOnTouchListener(onBorderTouchListener);
    }

    /**
     * 重写setOnTouchListener方法,防止在MyScrollView实例中调用该方法的时候,覆盖掉我们上面做的处理
     */
    @Override
    public void setOnTouchListener(final OnTouchListener l) {
        OnTouchListener onTouchListener = new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {

                if (onBorderTouchListener != null) {
                    onBorderTouchListener.onTouch(v, event);
                }
                return l.onTouch(v, event);
            }

        };
        super.setOnTouchListener(onTouchListener);
    }

这里其实已经处理了上面提到的初始化和偶尔需要会拉才会触发靠岸事件的问题,但又会产生新的父组件会跟着子组件一起滑动的问题,这是因为我们只是给出了允许父组件拦截事件的通道,却没有给出不允许父组件拦截的通道。

3.2 继续优化,设置不允许父组件拦截的情况

其实原理很简单,就是子组件没有滑动靠岸的时候就不允许父组件拦截事件,言下之意就是我还没玩够,你在等等。

还是要修改MyScrollView类:

private void doOnBorderListener() {
        if (contentView != null && contentView.getMeasuredHeight() <= getScrollY() + getHeight()) {
            if (onBorderListener != null) {
                onBorderListener.onBottom();
            }
        } else if (getScrollY() == 0) {
            if (onBorderListener != null) {
                onBorderListener.onTop();
            }
        } else { //没靠岸的时候调用onMiddle方法
            if (onBorderListener != null) {
                onBorderListener.onMiddle();
            }
        }
    }

//在接口中加一个onMiddle方法,来处理没靠岸的情况
public static interface OnBorderListener {
        /**
         * Called when scroll to bottom
         */
        public void onBottom();

        /**
         * Called when scroll to top
         */
        public void onTop();

        /**
         * Called when scroll in middle
         */
        public void onMiddle();
    }

继续在实例化的mScrollView中进行如下修改

mScrollView.setOnBorderListener(new MyScrollView.OnBorderListener() {
            @Override
            public void onBottom() {
                mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
            }

            @Override
            public void onTop() {
                mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
            }

            @Override
            public void onMiddle() {
                //在没靠岸的时候不允许父组件拦截事件
                mScrollView.getParent().requestDisallowInterceptTouchEvent(true);
            }
        });

引用声明

  1. android 事件处理机制之requestDisallowInterceptTouchEvent
  2. 滑动到底部或顶部响应的ScrollView实现

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-23 14:58:55

Android 解决ScrollView与父视图滑动冲突问题的相关文章

完美解决ScrollView嵌套ViewPager滑动失效和无法正常滑动冲突问题

/******************************************************************************* * Copyright 2011, 2012 Chris Banes. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the Li

(转)ViewPager,ScrollView 嵌套ViewPager滑动冲突解决

ViewPager,ScrollView 嵌套ViewPager滑动冲突解决 本篇主要讲解一下几个问题 粗略地介绍一下View的事件分发机制 解决事件滑动冲突的思路及方法 ScrollView 里面嵌套ViewPager导致的滑动冲突 ViewPager里面嵌套ViewPager 导致的滑动冲突 轮播图的几种实现方式 先看一下效果图 ScrollView里面嵌套ViewPager ViewPager里面嵌套ViewPager View的 事件分发机制 这篇博客大打算详细讲解View的事件分发机制

改动ScrollView的滑动速度和解决ScrollView与ViewPager的冲突

话不多说,非常easy,能够从凝视中知道做法,直接上代码: 1.改动ScrollView的滑动速度: public class MyHorizontalScrollView extends HorizontalScrollView { public MyHorizontalScrollView(Context context) { super(context); } public MyHorizontalScrollView(Context context, AttributeSet attrs

修改ScrollView的滑动速度和解决ScrollView与ViewPager的冲突

话不多说,很简单,可以从注释中知道做法,直接上代码: 1.修改ScrollView的滑动速度: public class MyHorizontalScrollView extends HorizontalScrollView { public MyHorizontalScrollView(Context context) { super(context); } public MyHorizontalScrollView(Context context, AttributeSet attrs, i

ScrollView 嵌套ListView 滑动冲突,与显示不全

import android.content.Context; import android.util.AttributeSet; import android.widget.ListView; /** * * @author jiarh *2014-8-14 */ public class UserListView extends ListView { public UserListView(Context context) { super(context); } public UserLis

浅谈android中的ListView合集系列之解决ScrollView和ListView嵌套冲突(一)

相信大家都已经可以熟练使用ListView和GridView,大神们估计都在使用RecyclerView了.如果还在使用ListView,你肯定有这样的一个深刻的感受,那就是在做一个APP的时候使用ListView和GridView很频繁,并且经常会遇到一个页面中除了有ListView或GridView可能还有一些其他的内容,但是可能内容很多,你第一时间就会想到让它整体滑动即可,那就是在总的布局外面包裹一个ScrollView.也就是出现了ScrollView中嵌套一个ListView的场景,或

解决ScrollView嵌到listView冲突问题

把下面的方法放在绑定适配器操作的下面就行. /** * 重新计算ListView的高度,解决ScrollView和ListView两个View都有滚动的效果,在嵌套使用时起冲突的问题 * @param listView */ public void setListViewHeight(ListView listView) { // 获取ListView对应的Adapter ListAdapter listAdapter = listView.getAdapter(); if (listAdapt

ScrollView 与ListView 滑动冲突完美解决

一.介绍ListView高度的设置方法 二.根据实际需求解决冲突问题 一.介绍ListView高度的设置方法 在ScrollView中使用ListView,ListView的高度会不正常. 方式一:在XML中写死  android:layout_width="match_parent" android:layout_height="120dp" 方式二:代码中设置固定高度(如果在运行过程中才能决定ListView高度) public void setHeight(i

Android解决ScrollView视图导致其底部的布局栏被推到上边的问题

最近有个xml布局文件,我说下大概意思: <ScrollView> ...... </ScrollView> <RelativeLayout> ...... </RelativeLayout> 大家可以看到在RelativeLayout布局的上面是ScrollView,ScrollView里面包含Edittext元素,每次我点击输入东西的时候,底部的RelativeLayout总是被推上去,很麻烦,搜了好多,在stackoverFlow找到了解决方法,也很简