自定义View系列教程08--滑动冲突的产生及其处理

来源:互联网 发布:移动数据专业认证考试 编辑:程序博客网 时间:2024/06/18 17:53

自定义View系列教程01--常用工具介绍

自定义View系列教程02--onMeasure源码详尽分析

自定义View系列教程03--onLayout源码详尽分析

自定义View系列教程04--Draw源码分析及其实践

自定义View系列教程05--示例分析

自定义View系列教程06--详解View的Touch事件处理

自定义View系列教程07--详解ViewGroup分发Touch事件

自定义View系列教程08--滑动冲突的产生及其处理

在之前的几篇文章中,我们已经分析了View对于Touch的处理以及ViewGroup对于Touch事件的分发。 
但在开发中时常遇到一个棘手的问题:Touch事件的滑动冲突。比如ListView嵌套ScrollView,ViewPager嵌套ScrollView,ListView嵌套ScrollView时常常发生。

一.常见冲突场景:

场景1:外部滑动方向和内部滑动方向不一致。例如:ViewPaget+Fragment(ListView)或者HorizontalScrollView+listView

场景2:外部滑动方向和内部滑动方向一致。例如:HoriaontalScrollView+ViewPager

场景3:上面两种的嵌套。例如:SlideMenu+ViewPager+ListView


二.滑动冲突的处理规则:

如下图:根据滑动过程中两点之间的坐标,可以得出到底是水平滑动还是竖直滑动。

得到坐标之后可以通过:1.滑动角度    2.滑动的距离差    3.滑动的速度差    或者是从业务上找突破点。


三.滑动冲突的解决方法:

    1.在父View中准确地进行事件分发和拦截【外部拦截法】 
我们可以重写父View中与Touch事件分发相关的方法,比如onInterceptTouchEvent( )。这些方法中摒弃系统默认的流程,结合自身的业务逻辑重写该部分代码,从而使父View放行子View需要的Touch。

      2.子View禁止父View拦截Touch事件 【内部拦截法】

         在分析ViewGroup的dispatchTouchEvent()源码时,我们知道:Touch事件是由父View分发的。如果一个Touch事件是子View需要的,但是被其父View拦截了,子View就无法处理该Touch事件了。在此情形下,子View可以调用requestDisallowInterceptTouchEvent( )禁止父View对Touch的拦截

伪代码:

外部拦截法:

@Override//父类    public boolean onInterceptTouchEvent(MotionEvent event) {        boolean intercepted = false;        int x = (int) event.getX();        int y = (int) event.getY();        switch (event.getAction()) {        case MotionEvent.ACTION_DOWN: {            intercepted = false;            break;        }        case MotionEvent.ACTION_MOVE:             if (父容器需要当前点击事件)) {                intercepted = true;            } else {                intercepted = false;            }            break;        }        case MotionEvent.ACTION_UP: {            intercepted = false;            break;        }        default:            break;        }        mLastXIntercept = x;        mLastYIntercept = y;        return intercepted;    }//注释:ACTION_DOWN这个事件,父容器必须返回false,因为一旦父拦截了,后续ACTION_MOVE,ACTION_UP事件都会直接交给父处理,事件无>法传给子元素。      //ACTION_MOVE这个事件,根据需要来决定:父需要拦截返回true,否则返回false。      //ACTION_UP这个事件,必须返回false,因为ACTION_UP事件本身没有太多意义。        //说明:为什么说ACTION_UP必须返回false?      //当返回true时,考虑一种情况,假设事件交给子处理,如果父在ACTION_UP时返回true,就会导致子无法收到ACTION_UP事件,子的Click事件就无法触发。(就出现了问题)      //即使总返回false,也不会出错,因为父容器比较特殊,一旦开始拦截任何一个事件,那么后续事件都会交给它来处理,而ACTION_UP作为最后一个事件,也必定可以传给父容器,即是父容器的onInterceptTouchEvent方法在ACTION_UP返回了false。
内部拦截法:

//内部拦截法 @Override//子类    public boolean dispatchTouchEvent(MotionEvent event) {        int x = (int) event.getX();        int y = (int) event.getY();        switch (event.getAction()) {        case MotionEvent.ACTION_DOWN: {            parent.requestDisallowInterceptTouchEvent(true);            break;        }        case MotionEvent.ACTION_MOVE: {            int deltaX = x - mLastX;            int deltaY = y - mLastY;                       if (父容器需要此类点击事件) {                parent.requestDisallowInterceptTouchEvent(false);            }            break;        }        case MotionEvent.ACTION_UP: {            break;        }        default:            break;        }        mLastX = x;        mLastY = y;        return super.dispatchTouchEvent(event);    }}//注释:除了子元素需要做处理以外,父元素也要默认拦截ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。 @Override//父类    public boolean onInterceptTouchEvent(MotionEvent event) {        int x = (int) event.getX();        int y = (int) event.getY();        int action = event.getAction();        if (action == MotionEvent.ACTION_DOWN) {            mLastX = x;            mLastY = y;            return false;        } else {            return true;        }    }
四,示例展示解决滑动冲突的常用方法。

示例效果,请看下图:

这里写图片描述

它的布局文件如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.stay4it.testtouch3.MainActivity">    <HorizontalScrollView        android:id="@+id/horizontalScrollView"        android:layout_height="450dp"        android:layout_width="match_parent"        android:scrollbars="horizontal"        android:fillViewport="true">        <android.support.v4.view.ViewPager            android:id="@+id/viewPager"            android:layout_width="match_parent"            android:layout_height="match_parent" />    </HorizontalScrollView></RelativeLayout>
在该示例中,我们在HorizontalScrollView中嵌入ViewPager浏览图片。由于HorizontalScrollView和ViewPager都可以响应水平滑动,当我们用手指切换图片时发现ViewPager无法正常的滚动。这是因为Touch事件被HorizontalScrollView处理,根本没有传递给ViewPager。

嗯哼,滑动冲突的原因已经清楚了,我们现在来解决这个问题。

第一种解决方法

在ViewPager中禁止HorizontalScrollView拦截Touch事件

package com.stay4it.testtouch3;import android.os.Bundle;import android.support.v4.view.ViewPager;import android.support.v7.app.AppCompatActivity;import android.view.MotionEvent;import android.view.View;/** * 原创作者 * 谷哥的小弟 * * 博客地址 * http://blog.csdn.net/lfdfhl */public class MainActivity extends AppCompatActivity {    private ViewPager mViewPager;    private ViewPagerAdapter mViewPagerAdapter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        init();    }    private void init(){        mViewPager = (ViewPager) findViewById(R.id.viewPager);        mViewPagerAdapter=new ViewPagerAdapter(this);        mViewPager.setAdapter(mViewPagerAdapter);        mViewPager.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                mViewPager.getParent().requestDisallowInterceptTouchEvent(true);                return false;            }        });    }}
在此,请关注第32行代码

mViewPager.getParent().requestDisallowInterceptTouchEvent(true);

此处首先利用 mViewPager.getParent( )得到了ViewPager的父View即HorizontalScrollView;然后再执行requestDisallowInterceptTouchEvent( )方法从而禁止掉父View对于Touch事件的传递。

第二种解决方法

在自定义HorizontalScrollView中合理地处理Touch事件的拦截和分发

package com.stay4it.testtouch4;import android.content.Context;import android.util.AttributeSet;import android.view.MotionEvent;import android.widget.HorizontalScrollView;/** * 原创作者 * 谷哥的小弟 * * 博客地址 * http://blog.csdn.net/lfdfhl */public class HorizontalScrollViewSubclass extends HorizontalScrollView{    private float LastX;    private float LastY;    private float distanceX;    private float distanceY;    public HorizontalScrollViewSubclass(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        switch (ev.getAction()){            case MotionEvent.ACTION_DOWN:                distanceX = 0f;                distanceY = 0f;                LastX = ev.getX();                LastY = ev.getY();                break;            case MotionEvent.ACTION_MOVE:                final float currentX = ev.getX();                final float currentY = ev.getY();                distanceX += Math.abs(currentX - LastX);                distanceY += Math.abs(currentY - LastY);                LastX = currentX;                LastY = currentY;                if(distanceX > distanceY){                    return false;                }                break;            case MotionEvent.ACTION_UP:                break;        }        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        return super.onTouchEvent(ev);    }}
从这段代码中,我们的主要操作是在自定义HorizontalScrollView中重写了onInterceptTouchEvent( )。 

在该方法中如果判定Touch是水平的滑动:

if(distanceX > distanceY)

那么直接返回false不拦截该Touch。这样Touch事件就可以顺利传递到其子View即ViewPager中,从而正常地切换图片。

既然已经采用了自定义的HorizontalScrollView就不要忘记修改布局文件喔~

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.stay4it.testtouch4.MainActivity">    <com.stay4it.testtouch4.HorizontalScrollViewSubclass        android:id="@+id/horizontalScrollView"        android:layout_height="450dp"        android:layout_width="match_parent"        android:scrollbars="horizontal"        android:fillViewport="true"      >        <android.support.v4.view.ViewPager            android:id="@+id/viewPager"            android:layout_width="match_parent"            android:layout_height="match_parent" />    </com.stay4it.testtouch4.HorizontalScrollViewSubclass></RelativeLayout>
嗯哼,在此通过一个完整的示例展示了事件滑动冲突的常用解决方式。 

在实际的项目开发中所遇到的滑动冲突可能比这要复杂,但是处理冲突的基本原理和方式是相同的。


至此,自定义View系列教程就全部结束了

多谢大家的指正和鼓励

Thank you very much

原文链接:http://blog.csdn.net/lfdfhl/article/details/51656492

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 租房子押金不退怎么办 外地人签户口到武汉怎么办 开发商不给办土地证怎么办 房间里的油烟味怎么办 现金借款app还不了款怎么办 学校不允许实习生自己租房怎么办 盯盯拍开不了机怎么办 向私人借钱不还怎么办 微信好友借钱不还怎么办 支付宝借不了钱怎么办 支付宝借条关了怎么办 qq群不小心解散了怎么办 qq群解散了照片怎么办 出租屋没窗户很闷怎么办 二手房交税后房主不卖怎么办 二房东收不到租拖欠房租怎么办 房东不给换门锁怎么办 租房到期房东联系不到租客怎么办 廉租房名下有车怎么办 路边停车收忘记交费怎么办 考编忘记交费了怎么办 深圳公租房入库了接下来怎么办 公租房5年以后怎么办 教务系统密码忘记了怎么办 林科大教务处密码忘记了怎么办 智学号密码忘了怎么办 正方教务管理系统忘记密码怎么办 正方教务系统忘记密码怎么办 教务网密码忘了怎么办 电动车解除限速报警器不响怎么办 公租房住满5年怎么办 广州公租房收入超标怎么办 深圳法院拍卖房子不肯搬走怎么办 上海奉贤公租房的期满怎么办 公租房人口少了怎么办 公租房太远了怎么办 商品房没有门厅业主该怎么办 公帐付款备注错了怎么办 我是农村户口在外省交社保怎么办 北京租房遇到黑中介怎么办 上海租房子不让带孩子怎么办