博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一个Demo带你彻底掌握View的滑动冲突
阅读量:5158 次
发布时间:2019-06-13

本文共 11237 字,大约阅读时间需要 37 分钟。

本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发。

近期在又一次学习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)方法的參数。又一次进行布局,达到布局自己主动回弹的效果。

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

中间反复的RelativeLayout就不贴出来了。

至此,一个简单的下拉回弹就已经实现了,关于高速滑动以及惯性滑动感兴趣的能够加进去。这里不是本篇博客的重点就不做讨论了。

二.子View的滚动实现

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

兴高採烈的加上去。最后执行的结果是:布局全然变成了一个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滚动到開始的位置,父布局就開始拦截事件,形成连续的滑动。

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

写在最后:

昨天一直忙到下午才有时间去看博客。看到这篇博客评论以下炸开了锅。

这里有几个问题说明一下:

关于Denon源代码的问题,由于这个Demo的源代码不是单独的。合集打包下来有30多M。所以当时就没传上去。

我相信依照文章所说的步骤来,肯定会实现最后的效果,最后我上传的源代码与文章代码是一模一样的,这一点我是百分百保证的。

关于Demo存在的问题。这个问题是真实存在的:

这里写图片描写叙述

谢谢这位小伙伴。我当时也马上回复了他。今天我把这个问题攻克了。

public class MyScrollView extends ScrollView {
private scrollTopListener listener; public void setListener(scrollTopListener listener) { this.listener = listener; } 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); listener.scrollTop(); } else { //禁止父View进行事件拦截 getParent().requestDisallowInterceptTouchEvent(true); } break; } return super.onTouchEvent(ev); } public interface scrollTopListener {
void scrollTop(); }}

给自己定义的ScrollView加入一个接口。监听是否滑到開始的位置。

public class MyParentView extends LinearLayout {
private int mMove; private int yDown, yMove; private boolean isIntercept; private int i = 0; private MyScrollView myScrollView; private boolean isOnTop; public MyParentView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); myScrollView = (MyScrollView) getChildAt(0); myScrollView.setListener(new MyScrollView.scrollTopListener() { @Override public void scrollTop() { isOnTop = true; } }); } @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; } @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 (isOnTop) { yDown = y; isOnTop = false; } if (isIntercept && (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; isIntercept = false; break; } return true; }}

自己定义的父布局中,实现这个接口,然后在MotionEvent.ACTION_MOVE的时候。进行推断:

if (isOnTop) {

yDown = y;
isOnTop = false;
}

假设滑动到顶部,就让yDown的初始值为(int) event.getY(),这样就不会出现闪的问题。滑动也更加自然流畅。

关于Demo的优化与改进。我非常感谢这位小伙伴:

这里写图片描写叙述

他用不同的方式实现了一样的效果,而且还把源代码发到了我的邮箱。实现的效果一模一样,而且仅仅用了自己定义的父布局加外部拦截法,贴一下代码:

public class MyParentView extends LinearLayout {
private int mMove; private int yDown, yMove; private int i = 0; private boolean isIntercept = false; public MyParentView(Context context) { super(context); } public MyParentView(Context context, AttributeSet attrs) { super(context, attrs); } public MyParentView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } private ScrollView scrollView; @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); scrollView = (ScrollView) getChildAt(0); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { onInterceptTouchEvent(ev); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int y = (int) ev.getY(); int mScrollY = scrollView.getScrollY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: yDown = y; break; case MotionEvent.ACTION_MOVE: yMove = y; if (yMove - yDown > 0 && mScrollY == 0) { if (!isIntercept) { yDown = (int) ev.getY(); isIntercept = true; } } break; case MotionEvent.ACTION_UP: layout(getLeft(), getTop() - i, getRight(), getBottom() - i); i = 0; isIntercept = false; break; } if (isIntercept) { mMove = yMove - yDown; i += mMove; layout(getLeft(), getTop() + mMove, getRight(), getBottom() + mMove); } return isIntercept; }}

这样就不用自己定义一个ScrollView。直接将原生的ScrollView放到这个父布局中就可以。大家能够试试他的方法,点个大大的赞。

源代码地址:

欢迎star,fork,提issues,一起进步!

转载于:https://www.cnblogs.com/lytwajue/p/7358852.html

你可能感兴趣的文章
Hibernate一级缓存
查看>>
jQuery鼠标经过图片切换
查看>>
listview和简单的记事本操作
查看>>
ssis包部署
查看>>
pat 1006 Sign In and Sign Out (25)
查看>>
shell
查看>>
poj1459 最大流Dinic
查看>>
20155320 2016-2017-2《Java程序设计》第十周学习总结
查看>>
第4次作业类测试代码+149+肖雷
查看>>
xshell SSH 连接出现 outgoing encryption ,或者no matching host key algorithm found错误的解决...
查看>>
第三方库Mantle的简单实用
查看>>
算法45----逆波兰数【栈】
查看>>
jar包重启脚本-restart.sh
查看>>
P3372 【模板】线段树 1
查看>>
剑指offer题解02-10
查看>>
安装 SQL Server 2008,不断要求重启电脑,解决办法
查看>>
001.SSH配置文件
查看>>
node知识积累
查看>>
HDU 1710 Binary Tree Traversals
查看>>
mina 字节数组编解码器的写法 II
查看>>