引言

  上一篇文章我们从源码的角度介绍了View事件分发机制,这一篇文章我们就通过介绍滑动冲突的规则和一个实例来更加深入的学习View的事件分发机制。

1、外部滑动方向和内部滑动方向不一致

  考虑这样一种场景,开发中我们经常使用ViewPager和Fragment配合使用所组成的页面滑动效果,很多主流的应用都会使用这样的效果。在这种效果中,可以使用左右滑动来切换界面,而每一个界面里面往往又都是ListView这样的控件。本来这种情况是存在滑动冲突的,只是ViewPager内部处理了这种滑动冲突。如果我们不使用ViewPager而是使用ScrollView,那么滑动冲突就需要我们自己来处理,否者造成的后果就是内外两层只有一层能滑动。

情况1的解决思路

  对于第一种情况的解决思路是这样的:当用户左右滑动时,需要让外层的View拦截点击事件。当用户上下滑动时,需要让内部的View拦截点击事件(外层的View不拦截点击事件),这时候我们就可以根据它们的特性来解决滑动冲突。在这里我们可以根据滑动时水平滑动还是垂直滑动来判断谁来拦截点击事件。下面先介绍一种通用的解决滑动冲突的方法。

外部拦截法

  外部拦截法是指:点击事件都经过父容器的拦截处理,如果父容器需要处理此事件就进行拦截,否者不拦截交给子View进行处理。这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。这种方法的伪代码如下:

 @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int x=(int)ev.getX();
int y=(int)ev.getY();
boolean intercept=false;
switch (ev.getAction()){
//按下事件不要拦截,否则后续事件都会给ViewGroup处理
case MotionEvent.ACTION_DOWN:
intercept=false;
break;
case MotionEvent.ACTION_MOVE:
//如果是横向移动就进行拦截,否则不拦截
int deltaX=x-mLastX;
int deltaY=y-mLastY;
if(父容器需要当前点击事件){
intercept=true;
}else {
intercept=false;
}
break;
case MotionEvent.ACTION_UP:
intercept=false;
break;
}
mLastX = x;
mLastY = y;
return intercept;
}

  上面代码是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改父容器需要当前点击事件的条件即可,其他均不需要修改。我们在描述下:在onInterceptTouchEvent方法中,首先是ACTION_DOWN事件,父容器必须返回false,即不拦截ACTION_DOWN事件,这是因为一旦父容器拦截ACTION_DOWN,那么后续的ACTION_MOVE和ACTION_UP都会直接交给父容器处理,这时候事件就没法传递给子元素了;其次是ACTION_MOVE事件,这个事件可以根据需要来决定是否需要拦截。

  下面来看一个具体的实例,这个实现模拟ViewPager的效果,我们定义一个全新的控件,名称叫HorizontalScrollView。具体代码如下:

  1、我们先看Activity中的代码:

 public class MainActivity extends Activity{

     private HorizontalScrollView mListContainer;

     @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); initView();
} private void initView() {
LayoutInflater inflater = getLayoutInflater();
mListContainer = (HorizontalScrollView) findViewById(R.id.container);
final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels;
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<>();
for (int i = 0; i < 50; i++) {
datas.add("name " + i);
} ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.content_list_item, R.id.name, datas);
listView.setAdapter(adapter);
}
}

  在这个代码中,我们创建了3个ListView然后将其添加到我们自定义控件的。这里HorizontalScrollView是父容器,ListView是子View。下面我们就使用外部拦截法来实现HorizontalScrollView,代码如下:

 /**
* 横向布局控件
* 模拟经典滑动冲突
* 我们此处使用ScrollView来模拟ViewPager,那么必须手动处理滑动冲突,否则内外两层只能有一层滑动,那就是滑动冲突。另外内部左右滑动,外部上下滑动也同样属于该类
*/
public class HorizontalScrollView extends ViewGroup { //记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;
private WindowManager wm;
//子View的个数
private int mChildCount;
private int mScreenWidth;
//自定义控件横向宽度
private int mMeasureWidth;
//滑动加载下一个界面的阈值
private int mCrital;
//滑动辅助类
private Scroller mScroller;
//当前展示的子View的索引
private int showViewIndex; public HorizontalScrollView(Context context){
this(context,null);
} public HorizontalScrollView(Context context, AttributeSet attributeSet){
super(context,attributeSet);
init(context);
} /**
* 初始化
* @param context
*/
public void init(Context context) {
//读取屏幕相关的长宽
wm = ((Activity)context).getWindowManager();
mScreenWidth = wm.getDefaultDisplay().getWidth();
mCrital=mScreenWidth/4;
mScroller=new Scroller(context);
showViewIndex=1;
} /**
* 重新事件拦截机制
* 我们分析了view的事件分发,我们知道点击事件的分发顺序是 通过父布局分发,如果父布局没有拦截,即onInterceptTouchEvent返回false,
* 才会传递给子View。所以我们就可以利用onInterceptTouchEvent()这个方法来进行事件的拦截。来看一下代码
* 此处使用外部拦截法
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int x=(int)ev.getX();
int y=(int)ev.getY();
boolean intercept=false;
switch (ev.getAction()){
//按下事件不要拦截,否则后续事件都会给ViewGroup处理
case MotionEvent.ACTION_DOWN:
intercept=false;
if(!mScroller.isFinished()){
mScroller.abortAnimation();
intercept=true;
}
break;
case MotionEvent.ACTION_MOVE:
//如果是横向移动就进行拦截,否则不拦截
int deltaX=x-mLastX;
int deltaY=y-mLastY;
if(Math.abs(deltaX)>Math.abs(deltaY)){
intercept=true;
}else {
intercept=false;
}
break;
case MotionEvent.ACTION_UP:
intercept=false;
break;
}
mLastX = x;
mLastY = y;
return intercept;
} @Override
public boolean onTouchEvent(MotionEvent 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;
/**
* scrollX是指ViewGroup的左侧边框和当前内容左侧边框之间的距离
*/
int scrollX=getScrollX();
if(scrollX-deltaX>0
&& (scrollX-deltaX)<=(mMeasureWidth-mScreenWidth)) {
scrollBy(-deltaX, 0);
}
break;
case MotionEvent.ACTION_UP:
scrollX=getScrollX();
int dx;
//计算滑动的差值,如果超过1/4就滑动到下一页
int subScrollX=scrollX-((showViewIndex-1)*mScreenWidth);
if(Math.abs(subScrollX)>=mCrital){
boolean next=scrollX>(showViewIndex-1)*mScreenWidth;
if(showViewIndex<3 && next) {
showViewIndex++;
}else {
showViewIndex--;
}
}
dx=(showViewIndex - 1) * mScreenWidth - scrollX;
smoothScrollByDx(dx);
break;
}
mLastX = x;
mLastY = y;
return true;
} /**
* 缓慢滚动到指定位置
* @param dx
*/
private void smoothScrollByDx(int dx) {
//在1000毫秒内滑动dx距离,效果就是慢慢滑动
mScroller.startScroll(getScrollX(), 0, dx, 0, 1000);
invalidate();
} @Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
}

  从上面代码中,我们看到我们只是很简单的采用横向滑动距离和垂直滑动距离进行比较来判断滑动方向。在滑动过程中,当水平方向的距离大时就判断为水平滑动,否者就是垂直滑动。

浅谈Android View滑动冲突的更多相关文章

  1. 浅谈Android View滑动和弹性滑动

    引言 View的滑动这一块在实际开发中是非常重要的,无论是优秀的用户体验还是自定义控件都是需要对这一块了解的,我们今天来谈一下View的滑动. View的滑动 View滑动功能主要可以使用3种方式来实 ...

  2. 浅谈Android View事件分发机制

    引言 前面的文章介绍了View的基础知识和View的滑动,今天我们来介绍View的另一个核心知识,View的事件分发机制. 点击事件的传递规则 所谓的点击事件的分发机制,其实就是对MotionEven ...

  3. 浅谈Android View的定位

    引言 今天我们来介绍Android坐标系统和View的定位,当然也会介绍View的滑动相关话题.下面让我们开始介绍吧. View的基础知识 View是Android中所有控件的基类,无论是TextVi ...

  4. 浅谈Android进阶之路

    过去十年是移动互联网蓬勃发展的黄金期,相信每个人也都享受到了移动互联网红利,在此期间,移动互联网经历了曙光期.成长期.成熟期.现在来说已经进入饱和期.依然记得在 2010-2013 年期间,从事移动开 ...

  5. 浅谈Android保护技术__代码混淆

    浅谈Android保护技术__代码混淆   代码混淆 代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为.将代码中的各种元 ...

  6. 安卓开发_浅谈Android动画(四)

    Property动画 概念:属性动画,即通过改变对象属性的动画. 特点:属性动画真正改变了一个UI控件,包括其事件触发焦点的位置 一.重要的动画类及属性值: 1.  ValueAnimator 基本属 ...

  7. 浅谈Android应用性能之内存

    本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 文/ jaunty [博主导读]在Android开发中,不免会遇到许多OOM现象,一方面可能是由于开 ...

  8. 浅谈Android应用保护(一):Android应用逆向的基本方法

    对于未进行保护的Android应用,有很多方法和思路对其进行逆向分析和攻击.使用一些基本的方法,就可以打破对应用安全非常重要的机密性和完整性,实现获取其内部代码.数据,修改其代码逻辑和机制等操作.这篇 ...

  9. 浅谈Android五大布局

    Android的界面是有布局和组件协同完成的,布局好比是建筑里的框架,而组件则相当于建筑里的砖瓦.组件按照布局的要求依次排列,就组成了用户所看见的界面.Android的五大布局分别是LinearLay ...

随机推荐

  1. 原生js--跨域消息传递

    跨域消息传递:postMessage() 1.兼容性问题:IE8及其以上浏览器和其它主流浏览器都已经支持 2.使用范围:跨iframe.跨页面.跨域 3.使用方法: 发送消息:postMessage( ...

  2. sort与asort与ksort区别

    sort只依据值从小到大排序,键值不参与排序 asort依据值排序,键值参与排序 ksort依据键值排序,值参与排序 sort只依据值从小到大排序,键值不参与排序. 例 <?php $arr=a ...

  3. Build step 'Execute Windows batch command' marked build as failure

    坑爹的Jenkis,在执行windows命令编译.NET项目的时候命令执行成功了,但是却还是报了这样一个错: Build step 'Execute Windows batch command' ma ...

  4. 严版数据结构题集2.13 & 2.14

    1.试写一算法在带头结点的单链表结构上实现线性表操作Locate(L,x) 2.试写一算法在带头结点的单链表结构上实现线性表操作Length(L) #include<stdio.h> #i ...

  5. 如何搭建web服务器 使用Nginx搭建反向代理服务器 .

    引言:最近公司有台服务器遭受DDOS攻击,流量在70M以上,由于服务器硬件配置较高所以不需要DDOS硬件防火墙.但我们要知道,IDC机房是肯定不允许这种流量一直处于这么高的,因为没法具体知道后面陆续攻 ...

  6. VB学习之路基础(一)

    1.VB不区分大小定,VB编辑器会自动更正. 2.每一行代码语句,没有语句结束符. 3.续行符:一个"空格"加上一个"下划线".不能在参数中间加续行符. 5.合 ...

  7. java.lang.instrument 中的premain 实现类的个性化加载(附源代码)

    背景 想调用ASM API (用于字节码处理的开源API)对字节码进行处理,目标是实现对java程序运行时各种对象的动态跟踪,并进一步分析各个对象之间的关系(研究前提是目前的UML锁阐释的whole- ...

  8. 【CF725G】Messages on a Tree 树链剖分+线段树

    [CF725G]Messages on a Tree 题意:给你一棵n+1个节点的树,0号节点是树根,在编号为1到n的节点上各有一只跳蚤,0号节点是跳蚤国王.现在一些跳蚤要给跳蚤国王发信息.具体的信息 ...

  9. python----并发编程之IO模型

    一:IO模型介绍  同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个 ...

  10. 【转】.NET 应用程序是怎么运行的

    原文:http://www.cnblogs.com/xishuai/p/mono-dotnetcore.html  .NET应用程序运行过程 C#程序运行过程 CLR结构