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

简介: 探索Android软键盘的疑难杂症 深入探讨Android异步精髓Handler 详解Android主流框架不可或缺的基石 站在源码的肩膀上全解Scroller工作机制Android多...

探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


PS:如果觉得文章太长,那就直接看视频


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

这些滑动冲突的产生,一般而言都具有以下特点:

  1. 子View和父View都有滑动的需求
  2. 滑动事件不能准确地传递给合适的View

其实,Google官方不鼓励这种滑动嵌套的设计;但是在实际项目中却会碰到客户提出类似的要求。既然不太合理的东西已经存在了,开发人员再去抱怨设计或者客户都没有实际作用了,只有想办法完成对应的功能。

那么,有哪些方法可以解决滑动冲突呢?

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

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

在此,通过一个示例展示解决滑动冲突的常用方法。

示例效果,请看下图:

这里写图片描述

它的布局文件如下:

<?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


PS:如果觉得文章太长,那就直接看视频

相关文章
自己动手写RecyclerView的上拉加载
自己动手写RecyclerView的上拉加载
|
XML Android开发 数据格式
自定义View入门
自定义View入门
|
XML 前端开发 Android开发
Android自定义View-入门(明白自定义View和自定义ViewGroup)
为什么要自定义View? 主要是Andorid系统内置的View 无法实现我们的 需求,我们需要针对我们的业务需求定制我们想要的 View.
130 0
Android自定义View-入门(明白自定义View和自定义ViewGroup)
|
容器
自定义View
自定义View 画一个实心圆 效果图 创建attrs.xml文件 初始化样式属性 支持Padding属性 支持wrap_content属性 布局文件中的应用 画一个带外圆环的圆 效果图 创建attrs.xml文件 初始化样式属性 内圆与外圆环的绘制 布局文件中的应用 画一个外圆环可根据数值变动的圆 效果图 创建attrs.xml文件 初始化样式属性 绘制View 提供方法修改样式 布局文件中的应用
109 0
自定义View
|
前端开发 Android开发 调度
Android开发进阶——自定义View的使用及其原理探索
Android开发进阶——自定义View的使用及其原理探索  在Android开发中,系统提供给我们的UI控件是有限的,当我们需要使用一些特殊的控件的时候,只靠系统提供的控件,可能无法达到我们想要的效果,这时,就需要我们自定义一些控件,来完成我们想要的效果了。
1065 0
|
前端开发
自定义View -简单的 SwitchView
前言 实现一个简单的滑动开发,效果图如下: switchView完整版本 分析 平分整个View为两份 平分VIew 测量字体的高度和宽度,确定左右View的文字的位置并进行绘制 确定字体的位置和绘制 绘制...
1029 0
|
XML Android开发 数据格式
3.6 自定义View (3.6.2)
本文对应项目的码云地址:https://gitee.com/wanchuanxy/AndroidHeroesTest/tree/master/3/SystemWidget 3.6.2 创建复合控件 创建复合控件可以很好地创建出具有重用功能的控件集合。
660 0
|
前端开发 Android开发
3.6 自定义View (3.6.1)
本文对应项目的码云地址:https://gitee.com/wanchuanxy/AndroidHeroesTest/tree/master/3/SystemWidget Android给我们提供了丰富的组件库来创建丰富的UI效果,同时也提供了非常方便的拓展方法。
736 0
|
Android开发
自定义View解决滑动冲突
最近在读Android开发艺术探索,本文作为自己对view的滑动冲突的理解和实践记录 而滑动冲突,需要了解Android的事件分发机制,如果这个还有些疑惑的地方,请参考这篇文章,以及其中的参考文章 还需要View的Measure和Layout的相关知识View的Measure流程总结 自定义view注意 1.
1087 0