View的滑动冲突

View的滑动冲突指的是当有内外两层View同时可以滑动的时候,这个时候就会产生滑动冲突。那么应该如何解决滑动呢,其实要用到View的事件分发机制。

View的滑动冲突主要有以下三个场景:

场景一:外部滑动方向和内部滑动方向不一致;

场景二:外部滑动方向和内部滑动方向一致;

场景三:以上两种情况的嵌套。

这里主要讨论场景一的滑动冲突的解决,其他两种思想都是类似的,根据具体情况而定。

对于场景一,它的滑动冲突处理规则是:当用户左右滑动时,需要外部的View拦截点击事件,当用户上下滑动时,需要内部的View拦截点击事件。解决滑动冲突的思想就是根据滑动郭晨各种两个点之间的坐标就可以得出到底是水平滑动还是竖直滑动。那么如何判断是水平滑动还是竖直滑动呢?这个有很多参考,比如我们可以根据水平方向和竖直方向的距离差来判断,某些情况还可以根据速度差来判断。这里根据距离差来处理滑动冲突。

当水平方向的滑动距离大于竖直方向的滑动距离时,判断为水平滑动,否则判断为竖直滑动。如下图所示

也就是当dx > dy时判断为水平滑动,否则为竖直滑动。针对滑动冲突,这里给出两种解决滑动冲突的方式:外部拦截法,内部拦截法。

1、外部拦截法

所谓外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题。这种方法也比较符合点击事件的分发机制。外部拦截发需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可,伪代码如下。

    @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;
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
                intercepted = true;
            }
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            int deltaX = x - mLastXIntercept;
            int deltaY = y - mLastYIntercept;
            if (父容器需要当前点击事件) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        }
        case MotionEvent.ACTION_UP: {
            intercepted = false;
            break;
        }
        default:
            break;
        }

        Log.d(TAG, "intercepted=" + intercepted);
        mLastX = x;
        mLastY = y;
        mLastXIntercept = x;
        mLastYIntercept = y;

        return intercepted;
    }

上述代码是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改父容器的当前点击事件这个条件即可,其他的均不需要修改。

另外针对上述代码再说明一下,

首先down事件,父容器必须返回false,即不拦截down事件,如果拦截了down事件,那么后续的move和up事件都交给父容器处理了,不存在什么滑动了,也就是不存在什么滑动冲突的解决了,更不能将事件传递给子元素。

然后是move事件,这是我们要处理的核心事件,这个时候需要根据具体需要看判断是否要拦截事件。如果父容器需要就拦截,不需要就不拦截。

最后是up事件,必须要返回false,因为本身没有什么意义。考虑一种情况,假设父容器up返回了true,那么元素的onClick事件无法触发。

为了实现场景一的效果,定义了一个类似于水平的LinearLayout的东西,只不过它可以水平滑动,初始化时我们在它的内部添加若干个ListView,这样一来,由于它内部的ListView可以竖直滑动,而它本身又可以水平滑动,因此一个典型的场景一滑动冲突就出现了,这里根据水平和竖直距离差来判断。

首先贴上Activity代码:

package com.ryg.chapter_3;

import java.util.ArrayList;
import com.ryg.chapter_3.R;
import com.ryg.chapter_3.ui.HorizontalScrollViewEx;
import com.ryg.chapter_3.utils.MyUtils;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class DemoActivity_1 extends Activity {
    private static final String TAG = "DemoActivity_1";

    private HorizontalScrollViewEx mListContainer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.demo_1);
        Log.d(TAG, "onCreate");
        initView();
    }

    private void initView() {
        LayoutInflater inflater = getLayoutInflater();
        mListContainer = (HorizontalScrollViewEx) findViewById(R.id.container);
        final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels;
        final int screenHeight = MyUtils.getScreenMetrics(this).heightPixels;
        for (int i = 0; i < 3; i++) {
            ViewGroup layout = (ViewGroup) inflater.inflate(
                    R.layout.content_layout, mListContainer, false);
            layout.getLayoutParams().width = screenWidth;
            TextView textView = (TextView) layout.findViewById(R.id.title);
            textView.setText("page " + (i + 1));
            layout.setBackgroundColor(Color.rgb(255 / (i + 1), 255 / (i + 1), 0));
            createList(layout);
            mListContainer.addView(layout);
        }
    }

    private void createList(ViewGroup layout) {
        ListView listView = (ListView) layout.findViewById(R.id.list);
        ArrayList<String> datas = new ArrayList<String>();
        for (int i = 0; i < 50; i++) {
            datas.add("name " + i);
        }

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                R.layout.content_list_item, R.id.name, datas);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
                Toast.makeText(DemoActivity_1.this, "click item",
                        Toast.LENGTH_SHORT).show();

            }
        });
    }
}

上述代码只是创建了3个ListView并且把ListView加入到我们自定义的HorizontalScrollViewEx中,HorizontalScrollViewEx就是父容器,上面提到的类似水平LinearLayout的东西。而ListView是子元素。

采用外部拦截法,它的onInterceptTouchEvent方法如下:

   @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;
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
                intercepted = true;
            }
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            int deltaX = x - mLastXIntercept;
            int deltaY = y - mLastYIntercept;
            if (Math.abs(deltaX) > Math.abs(deltaY)) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        }
        case MotionEvent.ACTION_UP: {
            intercepted = false;
            break;
        }
        default:
            break;
        }

        Log.d(TAG, "intercepted=" + intercepted);
        mLastX = x;
        mLastY = y;
        mLastXIntercept = x;
        mLastYIntercept = y;

        return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            scrollBy(-deltaX, 0);
            break;
        }
        case MotionEvent.ACTION_UP: {
            int scrollX = getScrollX();
            int scrollToChildIndex = scrollX / mChildWidth;
            mVelocityTracker.computeCurrentVelocity(1000);
            float xVelocity = mVelocityTracker.getXVelocity();
            if (Math.abs(xVelocity) >= 50) {
                mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
            } else {
                mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
            }
            mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
            int dx = mChildIndex * mChildWidth - scrollX;
            smoothScrollBy(dx, 0);
            mVelocityTracker.clear();
            break;
        }
        default:
            break;
        }

        mLastX = x;
        mLastY = y;
        return true;
    }

其实就是修改外部拦截伪代码的拦截条件,在滑动过程中,当水平方向的距离大于就判断为水平滑动,为了能够水平滑动就让父容器拦截事件,而竖直距离大时就不拦截事件,于是事件就传递给了ListView,所以ListView也能上下滑动,这样滑动冲突就解决了。

HorizontalScrollViewEx的完整代码如下;代码参考了Android开发艺术探索一书。

package com.ryg.chapter_3.ui;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

public class HorizontalScrollViewEx extends ViewGroup {
    private static final String TAG = "HorizontalScrollViewEx";

    private int mChildrenSize;
    private int mChildWidth;
    private int mChildIndex;

    // 分别记录上次滑动的坐标
    private int mLastX = 0;
    private int mLastY = 0;
    // 分别记录上次滑动的坐标(onInterceptTouchEvent)
    private int mLastXIntercept = 0;
    private int mLastYIntercept = 0;

    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;

    public HorizontalScrollViewEx(Context context) {
        super(context);
        init();
    }

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

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

    private void init() {
        mScroller = new Scroller(getContext());
        mVelocityTracker = VelocityTracker.obtain();
    }

    @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;
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
                intercepted = true;
            }
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            int deltaX = x - mLastXIntercept;
            int deltaY = y - mLastYIntercept;
            if (Math.abs(deltaX) > Math.abs(deltaY)) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        }
        case MotionEvent.ACTION_UP: {
            intercepted = false;
            break;
        }
        default:
            break;
        }

        Log.d(TAG, "intercepted=" + intercepted);
        mLastX = x;
        mLastY = y;
        mLastXIntercept = x;
        mLastYIntercept = y;

        return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            scrollBy(-deltaX, 0);
            break;
        }
        case MotionEvent.ACTION_UP: {
            int scrollX = getScrollX();
            int scrollToChildIndex = scrollX / mChildWidth;
            mVelocityTracker.computeCurrentVelocity(1000);
            float xVelocity = mVelocityTracker.getXVelocity();
            if (Math.abs(xVelocity) >= 50) {
                mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
            } else {
                mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
            }
            mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
            int dx = mChildIndex * mChildWidth - scrollX;
            smoothScrollBy(dx, 0);
            mVelocityTracker.clear();
            break;
        }
        default:
            break;
        }

        mLastX = x;
        mLastY = y;
        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measuredWidth = 0;
        int measuredHeight = 0;
        final int childCount = getChildCount();
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        if (childCount == 0) {
            setMeasuredDimension(0, 0);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            setMeasuredDimension(measuredWidth, heightSpaceSize);
        } else {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(measuredWidth, measuredHeight);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;
        final int childCount = getChildCount();
        mChildrenSize = childCount;

        for (int i = 0; i < childCount; i++) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != View.GONE) {
                final int childWidth = childView.getMeasuredWidth();
                mChildWidth = childWidth;
                childView.layout(childLeft, 0, childLeft + childWidth,
                        childView.getMeasuredHeight());
                childLeft += childWidth;
            }
        }
    }

    private void smoothScrollBy(int dx, int dy) {
        mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        mVelocityTracker.recycle();
        super.onDetachedFromWindow();
    }
}
时间: 2024-10-11 15:50:05

View的滑动冲突的相关文章

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

最近在重新学习Android自定义View这一块的内容,遇到了平时开发中经常碰到的一个棘手问题:View的滑动冲突.相信不少小伙伴都有相同的感觉,看似简单真正做起来却又不知道从何下手.今天就从一个简单的Demo带你彻底掌握解决View滑动冲突的办法. 老规矩,先上图: 示例图中是一个常见的下拉回弹,手指向下滑动的时候,整个布局会一起滑动.下拉到一定距离的时候松手,布局会自动回弹到开始的位置:手指向上滑动的时候,布局的子View会滑动到最底部,然后手指再向下滑动,布局的子View会滑动到最顶部,最

view的滑动冲突解决方案

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

自定义控件(视图)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

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

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

滑动冲突的补充——Event的流程走向

一.之前分析的滑动冲突,并没有讲述event事件是如何分发到不同的控件 View的滑动冲突 现在分析一下滑动冲突event事件的流向 假设:  我们的一个事件为  点下——>左滑动一次——>松手   这三个为该事件的子事件 点下: 当点击屏幕的时候,点击事件会从Activity层 经过 Window 到 DecoreView,并调用DecoreView的dispatchTouchEvent()方法向下分发. 根据上次的文章,会先分发到HorizonLayout控件中,调用dispatchTou

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

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

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

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

关于Android滑动冲突的解决方法(二)

之前的一遍学习笔记主要就Android滑动冲突中,在不同方向的滑动所造成冲突进行了了解,这样的冲突非常easy理解,当然也非常easy解决.今天,就同方向的滑动所造成的冲突进行一下了解,这里就先以垂直方向的滑动冲突为背景,这也是日常开发中最常见的一种情况. 这里先看一张效果图 由于GIF 图片大小的限制.截图效果不是非常好 上图是在购物软件上常见的上拉查看图文详情,关于这中动画效果的实现.事实上实现总体的效果,办法是有非常多的,网上有非常多相关的样例,可是对某些细节的处理不是非常清晰.比方,下拉