这是我在学习过程中总结的知识
目的是希望日后回来看或者需要用的时候可以 一目了然 # 的回顾、巩固、查缺补漏
不追求详细相当于书本的精简版或者说是导读(想看详细的直接对应翻书),但会尽力保证读者都能快速理解和快速使用(随理解加深会总结的更加精简),但必要时会附上一些较详细解释的链接
脚注是空白的:表示还没弄懂的知识,了解后会添加
这一章大致讲的是深入对View的理解以便写出完美的自定义控件
3.1 View的基础知识
主要介绍的内容有:
- View的位置参数
- MotionEvent和TouchSlop对象
- VelocityTracker
- GestureDetector和Scroller对象
3.1.1 什么是View
- View是Android中所有控件的基类
- ViewGroup是控件组,内部包含了许多个控件即一组View
- ViewGroup也继承了View,这就意味了View本身就可以是单个控件或多个控件组成的一组控件
例如:Button是一个View、LinearLayout不仅是一个View而且还是个ViewGroup
3.1.2 View的位置参数
- top:左上角纵坐标
- left:左上角横坐标
- right:右下角横坐标
- bottom:右下角纵坐标
View 的坐标是相对于父容器的左上角为中心的,x轴指向右,y轴指向下
Android3.0之后增加了4个参数
- x、y:是View左上角的坐标
- translationX、Y:是View左上角相对于父容器的偏移量
3.1.3 MotionEvent和TouchSlop
1. MotionEvent
在手指接触屏幕后所产生的一系列事件中,典型的事件类型有如下几种
- ACTION_DOWN——手指刚接触屏幕
- ACTION_MOVE——手指在屏幕上移动
- ACTION_UP——手指从屏幕上松开的一瞬间
手指触摸屏幕一般会出现如下情况:
- 点击屏幕后松开,DOWN->UP(很可能松开过程中移动了一点点手指而被判定为下面的情况)
- 点击屏幕滑动一会再松开,DOWN->MOVE->…>MOVE->UP
我们可以通过MotionEvent对象得到点击事件发生的x和y坐标
getX/Y返回的是相对于当前View左上角的x和y坐标
getRawX/Y返回的是相对于手机屏幕左上角的x和y坐标
2.TouchSlop
TouchSlop是一个最小距离,当手指滑动的距离小于这个值则系统判定为没有滑动(用来防止出现上面那种情况)
在不同的设备这个值可能是不同的
通过ViewConfiguration.get(getContext()).getScaledTouchSlop()获取这个常量
在源码中还可以找到这个常量的定义
3.1.4 VelocityTracker、GestureDetector和Scroller
1.VelocityTracker
速度追踪,用于追踪手指在滑动过程中的水平和竖直方向的速度
首先在View的ontouchEvent方法中追踪当前点击事件的速度
VelocityTracker vt=VelocityTracker.obtain();
vt.addMovement( event);
接着用下面方法获得速度
//用于计算速度
//1000ms表示在1s内手指从水平方向从左向右滑过100像素,那么水平速度就是100
vt. computeCurrentVelocity (1000);
int xVelocity=( int) vt. getXVelocity ();
速度=(终点-起点)/时间段
最后使用完毕后调用clear重置并回收内存
vt.clear ();
vt.recycle ();
2.GestureDetector
手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为
//创建对象并实现OnGetureListenter接口
GestureDetector gd=new GetureDetector(this);
//解决长按屏幕后无法拖动的现象
gd.setIsLongpressEnable(false);
接着,接管目标View的onTouchEvent 方法,在待监听View的onTouchEvent方法中添加如下实现
boolean consume = gd. onTouchEvent (event);
return consume;
表格
实际开发中其实也可以只使用View的onTouchEvent方法中实现所需的监听,作者推荐滑动时自己实现,双击时使用GetureDetector
3. Scroller
弹性滑动对象,用于实现View的弹性滑动(实现滑动过程)
View中使用的scrollTo/scrollBy进行滑动时是瞬间完成的
Scroller需要与View的computeScroll方法结合后可以实现滑动过程
Scroller scroller = new Scroller(mContext);
private void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int delta = destX - srollX;
//1000ms内滑向destX,效果是慢慢滑动
mScroller.startScroll(scrollX, 0, delta, 0, 1000);
}
3.2 View的滑动
为了给用户呈现更多的内容,就需要使用滑动来隐藏和显示一些内容
以下3种为常见实现
- 通过View本身提供的scrollTo/scrollBy
- 通过动画给View施加平移效果来实现滑动
- 改变View的LayoutParams使得View重新布局从而实现滑动
3.2.1 使用scrollTo/scrollBy
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
//实现了基于当前位置的绝对滑动(实际滑动距离)
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
//实现了基于当前位置的相对滑动
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
从源码可以看出,scrollBy实际也是调用了scrollTo方法
关于mScrollX和mScrollY:
1.这两个属性是通过getScrollX/Y获得的
2.通过下面图片可知mScrollX/Y分别代表什么
3.2.2 使用动画
主要是操作View的translationX/Y
1.传统的View动画
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
//需要设置这个属性为true才会保留动画后的状态,否则会还原
android:fillAfter="true"
android:zAdjustment="normal" >
<translate
android:duration="100"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="100"
android:toYDelta="100" />
</set>
这个操作并不能真正改变View的位置参数,包括宽/高
2.采用属性动画
//按顺序对应括号内的参数(目标view,水平移动,0位置,到100位置,100ms内移动)
ObjectAnimation.ofFloat(targetView, "translationX", 0, 100).setDuration(100).start();
在Android3.以下无法使用这个操作,需要通过nineoldandroid来实现
有一个问题,假设单纯使用传统的View动画滑动一个Button会导致新位置的Button无法点击,因为他只是移动了Button的影像
建议使用属性动画就可以解决这个问题
3.2.3 改变布局参数
即改变LayoutParams
一、比如将一个Button右移100px:将这个Button的L ayoutParams 里的marginLeft参数的值增加100px
二、在Button左边放一个空的View,然后设置这个View的宽度来挤动Button(意思这个改变参数布局的可以灵活使用)
例子:
MarginLayoutParams params = (MarginLayoutParams) mButton 1. getLayoutParams ();
params.width += 100;
params.leftMargin += 100;
//下面是应用这个改动
mButton 1. requestLayout ();
// mButton 1. setLayoutParams (params);
3.2.4 各种滑动方式的对比
- scrollTo/By:操作简单,适合对View内容的滑动(只能滑动View的内容不能滑动View本身)
- 动画:适用于没有交互的View和实现复杂的动画效果(使用属性动画则没有明显缺点)
- 改变参数布局:操作稍微复杂,适用于有交互的View
一个全屏滑动的例子
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
//获取当前手指的坐标
//注意不能使用getX/Y,因为这是全屏滑动,所以需要获取当前点击事件在屏幕中的坐标而不是相对于View本身的坐标
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
break;
}
case MotionEvent.ACTION_MOVE: {
//获得位移,这样才可以移动View
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY);
int translationX = (int)ViewHelper.getTranslationX(this) + deltaX;
int translationY = (int)ViewHelper.getTranslationY(this) + deltaY;
//ViewHelper提供的动画效果
ViewHelper.setTranslationX(this, translationX);
ViewHelper.setTranslationY(this, translationY);
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
3.3 弹性滑动
为了优化用户的滑动体验,我们要实现弹性滑动(渐进的)
实现方法:
- 使用Scroller
- Handler#postDelayed
- Thread#sleep
它们都有一个共同的思想:将一次大的滑动分成若干次小的滑动
3.3.1 使用Scroller
Scroller scroller = new Scroller(mContext);
private void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int delta = destX - srollX;
//1000ms内滑向destX,效果是慢慢滑动
mScroller.startScroll(scrollX, 0, delta, 0, 1000);
//由下面代码可知startScroll只是单纯的保存了参数
//invalidarte方法会导致View重绘,然后View中的draw方法(后面会提到)会调用computeScroll方法
invalidarte();
}
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
//这个方法原本是空的,以下是为了实现弹性滑动而写的
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
//向Scroller获取当前的scrollX/Y,通过scrollTo实现滑动
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//使用这个方法进行第二次重绘,再调用computeScroll,一直反复到滑动过程结束
postInvalidate();
}
}
再看一下computeScrollOffset的实现
/**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished.
*/
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
//由这个关键词可知这个方法是根据时间流逝来计算当前scrollX/Y的值
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
····
}
//返回这个表示滑动未结束
return true;
}
Scroller小结
- Scroller本身并不实现View的滑动
- Scroller配合View的conputeScroll方法才能完成弹性滑动的效果
- conputeScroll不断让View重绘,每一次重绘计算时间间隔和滑动距离
- 通过时间间隔就可以得出View当前的滑动位置
- 知道了滑动位置就可以通过scrollTo方法来完成滑动
- 然后每一次重绘都是一次小滑动,连续之后便是弹性滑动
3.3.2 通过动画
动画本身就是一种渐进的过程,用它实现具有天然的的弹性效果
//让一个view在100ms内向右滑动100像素
ObjectAnimatior.ofFloat(targetView,"translationX",0,100).setDuration(100).start;
同时我们还可以利用动画的特性来实现一些动画不能实现的效果
下面是模仿Scroller来实现View的弹性滑动
final int starX = 0;
final int deltaX = 100;
ValueAnimator animator = ValueAnimation.ofInt(0,1).setDuration(1000);
animator.addUpdateListener(new AnimationUpdateListener({
@Override
public void onAnimationUpdate(ValueAnimation animator){
//获取动画的每一帧和之前的Scroller类似
float faction = animator.getAnimatedFraction();
mButton1.scrollTo(startX + (int) (deltaX * fraction),0);
}
});
animator.start();
使用这种方法还可以实现其他动画效果,只需要在onAnimationUpdate方法中加上我们需要的操作
3.3.3 使用延时策略
核心思想是通过发送一系列延时消息而达到的渐进式效果
使用Handler或者View的postDelayed方法/线程的sleep方法
下面的代码是大约1000ms(因为线程调度不会很精确稳定)内将View的内容向左移动100像素
private static final int MESSAGE_SCROLL_TO = 1;
private static final int FRAME_COUNT = 30;
private static final int DELAYED_TIME = 33;
private Button mButton1;
private View mButton2;
private int mCount = 0;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_SCROLL_TO: {
mCount++;
if (mCount <= FRAME_COUNT) {
float fraction = mCount / (float) FRAME_COUNT;
int scrollX = (int) (fraction * 100);
mButton1.scrollTo(scrollX, 0);
mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO, DELAYED_TIME);
}
break;
}
default:
break;
}
};
};
上面几种弹性滑动的实现方法,侧重更多的是实现思想
实际使用可以灵活扩展
3.4 View的事件分发机制
重点难点,滑动冲突的解决基础就是事件分发机制
3.4.1 点击事件的传递规则
我们分析的主要对象就是MotionEvent
所谓点击事件的事件分发,
就是当一个MotionEvent产生以后
系统需要把这个事件传递给一个具体的View
这个过程就是分发过程
以下介绍三个分发过程中很重要的三个方法:
public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发。如果事件能够传递给当前的View,那么这个方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前的事件
public boolean onInterceptTouchEvent(MotionEvent event)
在上述方法内部调用。用来判定是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列里,此方法不会被再次调用
public boolean onTouchEvent(MotionEvent event)
在dispatchToucchEvent方法中调用,用来处理点击事件,返回结果是是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再次接收到事件1
上述三个方法的关系可用下面的伪代码表示:
//对于一个根ViewGroup来说,点击事件产生后首先会传递给它
public boolean dispatchTouchEvent ( MotionEvent ev){
boolean consume = false ;
// Intercept 返回true就表示它要拦截当前事件,意思就是:这个我拿来用了!
if( onInterceptTouchEvent ( ev ){
//那么这个事件就会交给这个ViewGroup处理,调用onTouchEvent
consume = onTouchEvent (ev );
} else {
//父不用才轮到儿子用不用
consume = child. dispatchTouchEvent ( ev );
}
return consume ;
}
当一个View需要处理事件时
1、如果它设置了OnTouchListener,那么其中的onTouch方法会被回调,如果返回true就代表事件由这个监听处理
2、如果返回的是false当前View的onTouchEvent方法会被调用
3、如果在onTouchEvent中有设置OnClickListenter,那么它的onClick方法将会被调用
由此可见事件传递处理的优先级
点击事件的传递顺序
Activity-> Window ->顶级View->View分发事件
顶级View接收到事件后,就会按照事件分发机制去分发事件
如果一个View的onTouchEvent返回false,那么它的父容器2的onTouchEvent将会被调用
如果所有View都不处理,最终将由Activity的onTouchEvent处理
传递机制的小结
(1) 同一个事件序列是从手指接触屏幕的那一刻起(down)加上中间数量不定的(move),到手指离开屏幕(up)结束
(2) 正常来说,一个事件序列只能被一个View拦截且消耗。原因见(3),因为一旦一个元素拦截了某此事件,那么同一个事件序列内所有事件都会直接交给它处理。特殊情况:这个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理
(3) 某个View一旦决定拦截,那么这一个事件序列都只能由它处理,并且它的onInterceptTouchEvent不会被调用,故这个事件不会被拦截到别的元素
(4) 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会交给它来处理,并且将事件重新交给它的父元素处理,即父元素的onTouchEvent会被调用。事件一旦交给一个View处理,那么它就必须被消耗否则就会永远失去这次机会
(5) 如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,最终传递给Activity处理。其他正常运转
(6) ViewGroup默认不拦截任何事件
(7) 如果View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent会被直接调用
(8) View的enable属性不影响onTouchEvent返回默认值。
(9) 事件传递过程是由外向内的,即事件总是传递给父元素,再由父元素分发给子View
3.4.2 事件分发的源码解析
点击事件由MotionEvent来表示
点击操作发生时,事件最先传给当前的Activity
具体是由Activity中的Window来分发给DecorView(底层容器,setContentView所设置的父容器)
1.点击事件从Activity到Window到DecorView
//Activity中的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//这个方法将事件传递给DecorView处理,DecorView以及其分发之后的View都不处理就返回false
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果所有的View都不处理,则最后交由Activity处理
return onTouchEvent(ev);
}
2.点击事件到达顶级View以及其分发过程
分发:
顶级View(一般是ViewGroup)调用dispatchTouchEvent
拦截处理:
如果ViewGroup的onInterceptTouchEvent为true,那么这个事件就由ViewGroup处理
如果ViewGroup的mOnTouchListener被设置,onTouch会被调用
否则onTouchEvent调用
在onTouchEvent中,如果设置了OnClickListener,则OnClick调用
分发
如果ViewGroup没有拦截,就传递给点击事件链上的子View,如此重复到完成
分发过程详解
其实主要实现是在ViewGroup的 dispatchTouchEvent 方法中
下面是当ViewGroup第一次接收到DOWN事件时
// Check for interception.
final boolean intercepted;
//这个方法会在onInterceptTouchEvent前判断是否拦截
//第一次DOWN进来时判断为true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//标志位定为已经接收了DOWN
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//判断是否拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
接收完DOWN之后
// Check for interception.
final boolean intercepted;
//这时候接收的是MOVE或UP
//当ViewGroup不拦截事件并将事件交由子元素处理时mFirstTouchTarget != null,假如这个为true
//假如为false的话就拦截,看其他判断便知
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//这里的标志位已经被改变了
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//判断是否拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
//直接跳到这里,不拦截MOVE和UP
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
总结一下
1.onInterceptTouchEvent不是任何时候都被调用的,如果要提前处理所有点击事件,要选择 dispatchTouchEvent 方法
2. FLAG_DISALLOW_INTERCEPT 可以用来解决滑动冲突
分发到子View时
判断子元素是否能接收到点击事件
1.子元素是否在播放动画
2.点击事件坐标是否落在子元素区域内
如果遍历所有子元素后事件没有被处理这是因为
1.ViewGroup没有子元素
2.子元素处理了点击事件,但是 dispatchTouchEvent 返回了false(一般是因为子元素的onTouchEvent返回了false)
这样的话最后都由ViewGroup自己处理
如果mFirstTouchTarget==null,那么点击事件就会交由View处理
View对点击事件的处理
View点击事件和之前说的一样(Touch的优先级),其他要注意的有:
这个View是一个单独的元素,不包含ViewGroup和子元素,必须自己处理点击事件
只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么它就一定会消耗这个事件,尽管它是DISABLE状态
3.5 View的滑动冲突
只要在界面中只要内外两层同时可以滑动,这个时候就会产生滑动冲突。解决问题有一个固定的套路
3.5.1 常见的滑动冲突场景
- 1、外部滑动方向与内部不一致
- 2、内外滑动方向一致
- 3、以上两种情况的嵌套
情况一
常见的是ViewPager和Fragment结合实现页面左右滑动效果
然而每个页面中又有一个ListView上下滑动,因为ViewPager内部处理了这种滑动冲突,所以不会出现问题
如果我们使用的事ScrollView而不是ViewPager,那就必须手动处理冲突,否则只有其中一层可以滑动
情况二
系统无法知道用户到底想让哪一层滑动,要么只有一层滑动,要么两个都滑动的很缓慢
情况三
几乎就只是单一的滑动冲突的叠加,因此只需要分别处理内层、中层和外层之间的滑动冲突即可
3.5.2 滑动冲突的处理规则
情况一
处理规则:用户左右滑动时让外部View拦截,上下滑动时就让内部View拦截点击事件
我们可以根据特征来判断
- 根据滑动过程中两个点的坐标判断是竖直还是水平滑动
- 判断滑动路径与水平方向所形成的夹角
- 根据水平方向和竖直方向的距离差
- 特殊情况还可以根据水平方向和竖直方向的速度差来判断
情况二
无法根据滑动的角度,距离差和速度来判断
这时候通过规定业务状态来区分:
情况三
也是从业务方面去判断
3.5.3 滑动冲突的解决方式
问题关键:滑动规则不同
解决基础:事件分发机制
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: {
//这里必须返回false,不然之后的事件都给它接收了
intercepted = false;
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: {
//父容器的这里必须为false,否则子元素的onClick可能无法触发
//同时父容器无论如何最后都会处理这个事件
intercepted = false;
break;
}
default:
break;
}
Log.d(TAG, "intercepted=" + intercepted);
mLastX = x;
mLastY = y;
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
2.内部拦截法
父容器不拦截任何事件,所有都交给了子元素
子元素需要就直接消耗,否则还给父容器处理
不符合事件分发机制,需要配合requestDisallowInterceptTouchEvent
@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: {
//这里必须返回false,不然之后的事件都给它接收了
parent.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
//父容器需要的点击事件
//面对不同的滑动策略只需要修改这个条件,其他的不能改动
if (Math.abs(deltaX) > Math.abs(deltaY)) {
//调用这个方法解除父元素拦截所需事件的限制
//这个方法设置标志位FLAG_DISALLOW_INTERCEPT使父容器不再拦截事件,前提是父容器不拦截DOWN
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
//父容器的这里必须为false,否则子元素的onClick可能无法触发
//同时父容器无论如何最后都会处理这个事件
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
父元素代码
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
//除了DOWN不拦截,其他都一定要拦截
if(action == MotionEvent.ACTION_DOWN){
return false;
} else{
return true;
}
}