引言

  上一篇文章我们从源码的角度介绍了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. vs2017编译网狐荣耀服务端的心得

    1.找不到d3dx9.h 从D:\Microsoft DirectX SDK (June 2010)\Include复制 d3dx9.hd3dx9anim.hd3dx9core.hd3dx9effec ...

  2. [原]rpm安装rpm-package报错:Header signature NOKEY 和 error: Failed dependencies:

    以前经常遇到这个问题,一直未有记录,今天记录下来: 在安装rpm包的时候报错误如下: Question 1: warning: *.rpm: Header V3 DSA signature: NOKE ...

  3. [原]sublime Text2

    sublime Text2 升级到 2.0.2 build 2221 64位 的破破解 sublime Text2 download website 链接: http://pan.baidu.com/ ...

  4. Robotium源码解读-native控件/webview元素的获取和操作

    目前比较有名的Uitest框架有Uiautomator/Robotium/Appium,由于一直对webview元素的获取和操作比较好奇,另外Robotium代码量也不是很大,因此打算学习一下. 一. ...

  5. jquery validate使用笔记

    1 表单验证的准备工作 在开启长篇大论之前,首先将表单验证的效果展示给大家.     1.点击表单项,显示帮助提示 2.鼠标离开表单项时,开始校验元素  3.鼠标离开后的正确.错误提示及鼠标移入时的帮 ...

  6. 【转】python中json.loads与eval的区别

    JSON有两种结构: “名称/值”对的集合(A collection of name/value pairs).不同的语言中,它被理解为对象(object),纪录(record),结构(struct) ...

  7. vue--拖动排序

    https://blog.csdn.net/jx950915/article/details/79803485?from=singlemessage

  8. Linux wc

    命令参数: -c 统计字节数. -l 统计行数. -m 统计字符数.这个标志不能与 -c 标志一起使用. -w 统计字数.一个字被定义为由空白.跳格或换行字符分隔的字符串. -L 打印最长行的长度. ...

  9. vue之计算属性和侦听器

    一.计算属性 模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护.例如: <div> {{ message.split('').rev ...

  10. anaconda资源链接

    清华源: https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ anaconda所有版本大全: http://www.bubuko.com/in ...