一个Demo带你彻底掌握View的滑动冲突

最近在重新学习Android自定义View这一块的内容,遇到了平时开发中经常碰到的一个棘手问题:View的滑动冲突。相信不少小伙伴都有相同的感觉,看似简单真正做起来却又不知道从何下手。今天就从一个简单的Demo带你彻底掌握解决View滑动冲突的办法。

老规矩,先上图:

示例图中是一个常见的下拉回弹,手指向下滑动的时候,整个布局会一起滑动。下拉到一定距离的时候松手,布局会自动回弹到开始的位置;手指向上滑动的时候,布局的子View会滑动到最底部,然后手指再向下滑动,布局的子View会滑动到最顶部,最后手指继续向下滑动,整个布局会一起滑动,下拉到一定距离后松手自动回弹到开始位置。

最终实现的效果如上所示,一起看看怎样一步步实现最终的效果:

一.布局的下拉回弹实现

下拉回弹的实现本质其实就是View的滑动,目前Android中实现View的滑动可以分为三种方式:通过改变View的布局参数使得View重新布局从而实现滑动;通过scrollTo/scrollBy方法来实现View的滑动;通过动画给View施加平移效果来实现滑动。这里我们采用第一种方式来实现,考虑到整个布局是竖直排列,我们可以直接自定义一个LinearLayout来作为父布局。然后调用layout(int l, int t, int r, int b)方法重新布局,达到滑动的效果。

public class MyParentView extends LinearLayout {

    private int mMove;
    private int yDown, yMove;
    private int i = 0;

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int y = (int) event.getY();
        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                yDown = y;
                break;
            case MotionEvent.ACTION_MOVE:
                yMove = y;
                if ((yMove - yDown) > 0) {
                    mMove = yMove - yDown;
                    i += mMove;
                    layout(getLeft(), getTop() + mMove, getRight(), getBottom() + mMove);
                }
                break;
            case MotionEvent.ACTION_UP:
                layout(getLeft(), getTop() - i, getRight(), getBottom() - i);
                i = 0;
                break;
        }
        return true;
    }
}

MotionEvent.ACTION_DOWN: 获取刚开始触碰的y坐标

MotionEvent.ACTION_MOVE: 如果是向下滑动,计算出每次滑动的距离与滑动的总距离,将每次滑动的距离作为layout(int l, int t, int r, int b)方法的参数,重新进行布局,达到布局滑动的效果。

MotionEvent.ACTION_UP: 将滑动的总距离作为layout(int l, int t, int r, int b)方法的参数,重新进行布局,达到布局自动回弹的效果。

此时的布局文件是这样的:

    <org.tyk.android.artstudy.MyParentView
        android:id="@+id/parent_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

                <View
                    android:layout_width="match_parent"
                    android:layout_height="1dp"
                    android:background="@color/divider"></View>

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="70dp">

                    <ImageView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_centerVertical="true"
                        android:layout_marginLeft="10dp"
                        android:background="@drawable/b" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_centerVertical="true"
                        android:layout_marginLeft="80dp"
                        android:text="回到首页"
                        android:textSize="20sp" />

                    <ImageView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentRight="true"
                        android:layout_centerVertical="true"
                        android:layout_marginRight="10dp"
                       android:background="@drawable/right_arrow" />
                </RelativeLayout>
    </org.tyk.android.artstudy.MyParentView>

中间重复的RelativeLayout就不贴出来了。至此,一个简单的下拉回弹就已经实现了,关于快速滑动以及惯性滑动感兴趣的可以加进去,这里不是本篇博客的重点就不做讨论了。

二.子View的滚动实现

手指向下滑动的时候,布局的下拉回弹已经实现,现在我希望手指向上滑动的时候,布局的子View能够滚动。平时接触最多的能滚动的View就是ScrollView,所以我的第一反应就是在自定义的LinearLayout内,添加一个ScrollView,让子View能够滚动。说干就干:

 <org.tyk.android.artstudy.MyParentView
        android:id="@+id/parent_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">
            </LinearLayout>
        </ScrollView>
 </org.tyk.android.artstudy.MyParentView>

兴高采烈的加上去,最后运行的结果是:布局完全变成了一个ScrollView,之前的下拉回弹效果已经完全消失!!!这显然不是我期待的结果。

仔细分析一下这种现象,其实这就是常见的View滑动冲突场景之一:外部滑动方向与内部滑动方向一致。父布局MyParentView需要响应竖直方向上的向下滑动,实现下拉回弹,子布局ScrollView也需要响应竖直方向上的上下滑动,实现子View的滚动。当内外两层都在同一个方向上可以滑动的时候,就会出现逻辑问题。因为当手指滑动的时候,系统无法知道用户想让哪一层滑动。所以这种场景下的滑动冲突需要我们手动去解决。

解决办法:

外部拦截法:外部拦截法是指点击事件先经过父容器的拦截处理,如果父容器需要处理此事件就进行拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题。外部拦截法需要重写父容器的onInterceptTouchEvent()方法,在内部做相应的拦截即可。

具体实现:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                yDown = y;
                break;
            case MotionEvent.ACTION_MOVE:
                yMove = y;
                if (yMove - yDown < 0) {
                    isIntercept = false;
                } else if (yMove - yDown > 0) {
                    isIntercept = true;
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return isIntercept;
    }

实现分析:

在自定义的父布局中重写onInterceptTouchEvent()方法,MotionEvent.ACTION_MOVE的时候,进行判断。如果手指是向上滑动,onInterceptTouchEvent()返回false,表示父布局不拦截当前事件,当前事件交给子View处理,那么我们的子View就能滚动;如果手指是向下滑动,onInterceptTouchEvent()返回true,表示父布局拦截当前事件,当前事件交给父布局处理,那么我们父布局就能实现下拉回弹。

三.连续滑动的实现

刚开始我以为这样就万事大吉了,可后来我又发现一个很严重的问题:手指向上滑动的时候,子View开始滚动,然后手指再向下滑动,整个父布局开始向下滑动,松手后便自动回弹。也就是说,刚才滚动的子View已经回不到开始的位置。仔细分析一下其实这结果是意料之中的,因为只要我手指是向下滑动,onInterceptTouchEvent()便返回true,父布局会拦截当前事件。这里其实又是上面提到的View滑动冲突:理想的结果是当子View滚动后,如果子View没有滚动到开始的位置,父布局就不要拦截滑动事件;如果子View已经滚动到开始的位置,父布局就开始拦截滑动事件。

解决办法:

内部拦截法:内部拦截法是指点击事件先经过子View处理,如果子View需要此事件就直接消耗掉,否则就交给父容器进行处理,这样就可以解决滑动冲突的问题。内部拦截法需要配合requestDisallowInterceptTouchEvent()方法,来确定子View是否允许父布局拦截事件。

具体实现:

public class MyScrollView extends ScrollView {

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

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

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

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {
            case MotionEvent.ACTION_MOVE:

                int scrollY = getScrollY();
                if (scrollY == 0) {
                    //允许父View进行事件拦截
                    getParent().requestDisallowInterceptTouchEvent(false);
                } else {
                    //禁止父View进行事件拦截
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
        }
        return super.onTouchEvent(ev);

    }
}

实现分析:

自定义一个ScrollView,重写onTouchEvent()方法,在MotionEvent.ACTION_MOVE的时候,得到滑动的距离。如果滑动的距离为0,表示子View已经滚动到开始位置,此时调用 getParent().requestDisallowInterceptTouchEvent(false)方法,允许父View进行事件拦截;如果滑动的距离不为0,表示子View没有滚动到开始位置,此时调用 getParent().requestDisallowInterceptTouchEvent(true)方法,禁止父View进行事件拦截。这样只要子View没有滚动到开始的位置,父布局都不会拦截事件,一旦子View滚动到开始的位置,父布局就开始拦截事件,形成连续的滑动。

好了,针对其他场景更复杂的滑动冲突,解决滑动冲突的原理与方式无非就是这两种方法。希望看完本篇博客能对你有所帮助,下一篇再见~~~

时间: 2024-11-17 07:13:11

一个Demo带你彻底掌握View的滑动冲突的相关文章

View的滑动冲突

View的滑动冲突指的是当有内外两层View同时可以滑动的时候,这个时候就会产生滑动冲突.那么应该如何解决滑动呢,其实要用到View的事件分发机制. View的滑动冲突主要有以下三个场景: 场景一:外部滑动方向和内部滑动方向不一致: 场景二:外部滑动方向和内部滑动方向一致: 场景三:以上两种情况的嵌套. 这里主要讨论场景一的滑动冲突的解决,其他两种思想都是类似的,根据具体情况而定. 对于场景一,它的滑动冲突处理规则是:当用户左右滑动时,需要外部的View拦截点击事件,当用户上下滑动时,需要内部的

自定义控件(视图)2期笔记11:View的滑动冲突之 概述

1. 引入: 滑动冲突可以说是日常开发中比较常见的一类问题,也是比较让人头疼的一类问题,尤其是在使用第三方框架的时候,两个原本完美的控件,组合在一起之后,忽然发现整个世界都不好了. 那到底是为什么会产生滑动冲突呢 ? 答:其实在界面中只要存在内外两层同时可以滑动,这个时候就会产生滑动冲突. 2. 常见的滑动冲突的场景: 场景1:外部滑动方向和内部滑动方向不一致 场景2:外部滑动方法和内部滑动方向一致 场景3:上面两种的嵌套 场景1:主要是将ViewPager 和Fragment配合使用所组成的页

一个Demo带你认识Design库,纯原生控件也能做出很漂亮的效果

欢迎转载,转载请注明出处http://blog.csdn.net/w804518214/article/details/51340984 不得不说开发者头条的APP真的是Material Design的典范,纯原生控件也能做出很漂亮的效果,并且不需要处理各种复杂的滑动冲突!!其主页基本把Design库的几个控件展示了一遍,今天就顺手借开发者头条主页的实现来简单介绍下官方Design扩展包里几个控件的使用.本文不会详细展开讲每个控件,仅仅针对demo效果的实现,想深入研究的推荐看官方指南! 先上效

自定义控件(视图)2期笔记12:View的滑动冲突之 外部拦截法

1. 外部拦截法: 点击事件通过父容器拦截处理,如果父容器需要就拦截,不需要就不拦截. 2. 下面通过一个Demo示例说明: (1)首先我们创建一个Android工程,如下: (2)我们来到activity_main.xml,如下: 1 <com.himi.viewconflict.ui.RevealLayout 2 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="htt

自定义控件(视图)2期笔记13:View的滑动冲突之 内部拦截法

1. 内部拦截法: 父容器不拦截事件,所有的事件全部都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理. 这种方法和Android中的事件分发机制不一样,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显负责一点. 我们需要重写子元素的dispatchTouchEvent方法. 这种方法的伪代码是: 1 @Override 2 public boolean dispatchTouchEvent(Motio

view的滑动冲突解决方案

一.常见的滑动冲突场景 1.外部滑动方向和内部滑动方向不一致 2.外部滑动方向和内部滑动方向一致 3.上面两种情况的嵌套 二.滑动冲突处理的原则 场景1的处理原则是:当用户左右滑动时,需要让外部的view拦截点击事件,当用户上下滑动时,需要让内部的view拦截点击事件.场景2和场景3比较特殊,无法如同场景1一样原则的处理冲突,需要在业务上寻找突破点.比如业务上规定:当处于某种状态时需要外部View响应用户的滑动,而处于另一种状态时则需要内部View来响应View的滑动,根据这种业务上的需求我们也

ios如果写一个提示带动画的View,可以来引导用户行为

先上图: 这个UIView可以这样写: -(id)initWithFrame:(CGRect)frame backImage:(UIImage*)image msgStr:(NSString*)txt txtColor:(UIColor*)color{ self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor clearColor]; _paopaoImage = image; _txt = t

Android 通知栏Notification的整合 全面学习 (一个DEMO让你完全了解它)

在android的应用层中,涉及到很多应用框架,例如:Service框架,Activity管理机制,Broadcast机制,对话框框架,标题栏框架,状态栏框架,通知机制,ActionBar框架等等. 下面就来说说经常会使用到通知机制中的通知栏框架(Notificaiton),它适用于交互事件的通知.它是位于顶层可以展开的通知列表.它会时不时的提醒你什么软件该更新了,什么人发你微信消息了等. (网上看了下,全面介绍的文章不多,所以就萌生了写这篇的念头,随便当作回顾笔记.下面我就通过官方文档.源代码

Android 通知栏Notification的整合 全面学习 (一个DEMO让你全然了解它)

在android的应用层中.涉及到非常多应用框架.比如:Service框架,Activity管理机制,Broadcast机制.对话框框架.标题栏框架,状态栏框架.通知机制,ActionBar框架等等. 以下就来说说常常会使用到通知机制中的通知栏框架(Notificaiton).它适用于交互事件的通知.它是位于顶层能够展开的通知列表. 它会时不时的提醒你什么软件该更新了,什么人发你微信消息了等. (网上看了下,全面介绍的文章不多,所以就萌生了写这篇的念头.随便当作回想笔记. 以下我就通过官方文档.