View是界面层控件的一种抽象,它代表一个控件。
ViewGroup继承自View,内部可以包含多个控件,即一组View。
View的位置参数
VelocityTracker ve = VelocityTracker.obtain();
在View的onTouchEvent方法中调用:
ve.addMovement(event);
计算滑动速度:
ve.computeCurrentVelocity(1000);
int xVe = (int)ve.getXVelocity();
int yVe = (int)ve.getYVelocity();
内存回收
ve.clear();
ve.recycle();
1.创建GestureDetector对象,并继承OnGestureListener接口
GestureDetector gd = new GestureDetector(this);
//解决长按屏幕无法拖动现象
gd.setIsLongpressEnabled(false);
2.接管目标View的onTouchEvent方法
boolean consume = gd.onTouchEvent(event);
return comsume;
View的scrollTo/scrollBy滑动时,过程是瞬间完成的。
Scroller可以实现有过渡效果的滑动。
Scroller sc = new Scroller(mContext);
//缓慢滚动到指定位置
private void smoothScrollTo(int destX, int destY){
int scrollX = getScrollX();
int delta = destX - scrollX;
//1000ms内滑向destX
sc.startScroll(scrollX, 0, delta, 0, 1000);
invalidate();
}
@Override
public void computeScroll(){
if(sc.computeScrollOffset()){
scrollTo(sc.getCurrX(), sc.getCurrY());
postInvalidate();
}
}
1.scrollTo/scrollBy
2.View动画
3.LayoutParams
1.Scroller
2.动画
ObjectAnimator.ofFloat(targetView, "translationX", 0, 100).setDuration(100).start();
3.Handler/postDelayed/sleep
public boolean dispatchTouchEvent(MotionEvent ev)
用来分发事件。
public boolean onInterceptTouchEvent(MotionEvent event)
用来判断是否拦截事件,如果当前View拦截某个事件,那么在同一事件序列中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
public boolean onTouchEvent(MotionEvent event)
处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
三个方法关系:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
对于一个根ViewGroup,点击事件产生后,首先传递给dispatchTouchEvent,如果这个ViewGroup的onInterceptTouchEvent返回true,就表示它要拦截当前事件,接着事件就会交给ViewGroup处理,即它的onTouchEvent方法就会被调用。
如果onInterceptTouchEvent返回false,就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent就会被调用。
如果一个View需要处理事件时,如果设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被回调。这时事件如何处理还要看onTouch的返回值,
如果返回false,则当前View的onTouchEvent方法会被调用;
如果返回true,则当前View的onTouchEvent方法将不会调用。
当View的onTouchEvent被调用时,如果当前View设置了onClickListener,那么它的onClick方法会被调用。
因此优先级:
onTouchListener > onTouchEvent > OnClickListener
Activity -> Window -> View
如果一个View的onTouchEvent返回false,那么它的父容器onTouchEvent将会被调用,依此类推,如果所有元素都不处理这个事件,事件最终会传递给Activity处理,即Activity的onTouchEvent方法会被调用
1.同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,这一过程由down开始,经过N个move事件,最终up结束。
2.一个事件序列只能被一个View拦截消耗。
3.某个View一旦决定拦截,那么这一个事件序列都只能由它处理,并且它的onInterceptTouchEvent不会再被调用。
4.某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中的其他事件都不会再交给它处理,并且事件将重新交由它的父元素处理,即父元素的onTouchEvent会被调用。
5.如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。
6.ViewGroup默认不拦截任何事件。
7.View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,它的onTouchEvent方法会被调用。
8.View的onTouchEvent默认都会消耗事件(返回true),除非它时不可点击的(clickable和longClickable同时为false)。View的longClickable属性磨人都为false,clickable属性要分情况:如button的clickable为true,TextView的clickable属性为false。
9.View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true。
10.onClick会发生的前提是当前View是可点击的,并且它收到了down、up事件。
11.事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View。通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。
当一个点击事件发生时,最先传递给Activity,由Activity的dispatchTouchEvent来进行事件分发,具体的工作是由Activity内部的Window来完成的。
Window会讲事件传递给decor view。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow()的到的是Window,Window的实现类是PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
mDecor是什么呢?
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks{
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
通过getWindow().getDecorView().findViewById(android.R.id.content).getChildAt(0)可以获取Activity设置的View,这个mDecor就是getWindow().getDecorView()返回的View。
Activity中通过setContentView设置的View是它的一个子View。
DecorView会将事件传递给setContentView所设置的View,这个View称为顶级View或根View
点击事件达到顶级View(ViewGroup),会调用ViewGroup的dispatchTouchEvent。
如果顶级ViewGroup拦截事件,即onInterceptTouchEvent返回true,则事件由ViewGroup处理,如果ViewGroup的mOnTouchListener被设置,则onTouch会被调用,否则onTouchEvent会被调用。也就是说,如果都提供的话,onTouch会屏蔽掉onTouchEvent。在onTouchEvent中,如果设置了mOnClickListener,则onClick会被调用。
如果顶级ViewGroup不拦截事件,则事件会传递给它所在的点击事件链上的子View,这时子View的dispatchTouchEvent会被调用。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Check for interception.
final boolean intercepted;
//如果点击事件为Down或者事件被子View处理了
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//如果允许拦截事件
if (!disallowIntercept) {
//调用ViewGrop的onInterceptTouchEvent方法
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;
}
...
//如果事件没有被取消并且没有被拦截
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
if (newTouchTarget == null && childrenCount != 0) {
//遍历子View
for (int i = childrenCount - 1; i >= 0; i--) {
newTouchTarget = getTouchTarget(child);
//实际调用了child的dispatchTouchEvent方法,也就是事件交给了子View处理
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 给mFirstTouchTarget赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
break;
}
}
}
...
}
}
// 如果子View没有消耗事件或者没有子View
if (mFirstTouchTarget == null) {
// 实际调用了ViewGroup的super.dispatchTouchEvent方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
....
}
...
}
return handled;
}
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
//赋值
mFirstTouchTarget = target;
return target;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
return handled;
}
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
//首先判断mOnTouchListener是否为null
//如果mOnTouchListener不为null,则调用onTouch
//如果onTouch返回true,则不会调用onTouchEvent
//否则,调用onTouchEvent
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
View对点击事件的处理就比较简单了,View(不是ViewGroup)是一个单独的元素,它没有子元素,因此不会再向下传递事件,只能自己处理。
通过上面源码,可以看出View首先判断有没有设置OnTouchListener,如果OnTouchListener中的onTouch返回true,那么onTouchEvent就不会被调用。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//判断是否可以点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//如果是DISABLED,返回clickable
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return clickable;
}
//如果有代理并且处理事件,返回true
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果可以点击,默认会返回true,即消耗事件
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
//判断是否是preparessed
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
//如果不是长按点击
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//执行mOnClickListener的onClick事件
if (!post(mPerformClick)) {
performClick();
}
}
}
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
} else {
setPressed(true, x, y);
//检查是否是长按事件
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
break;
}
return true;
}
return false;
}
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
//延迟执行长按事件,即mPendingCheckForLongPress的run方法
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
private final class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
private float mX;
private float mY;
private boolean mOriginalPressedState;
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
//最终执行的是performLongClickInternal
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}
}
private boolean performLongClickInternal(float x, float y) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(android.view.View.this);
}
if (!handled) {
final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
}
if ((mViewFlags & TOOLTIP) == TOOLTIP) {
if (!handled) {
handled = showLongClickTooltip((int) x, (int) y);
}
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
onTouchEvent首先会判断View是否是可以点击的,判断标准为:
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
如果是可以点击的话,默认会返回true,即消耗事件。
首先ACTION_DOWN会检查是否是长按事件
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
} else {
setPressed(true, x, y);
//检查是否是长按事件
checkForLongClick(0, x, y);
}
break;
如果是长按事件,就会用handler.postDelayed执行长按事件的方法
如果不是长按事件,ACTION_UP就会执行mOnClickListener的onClick事件。
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
//判断是否是preparessed
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
//如果不是长按点击
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//执行mOnClickListener的onClick事件
if (!post(mPerformClick)) {
performClick();
}
}
}
}
break;
1.外部拦截,即父View拦截处理冲突
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;
break;
case MotionEvent.ACTION_MOVE:
if(父容器需要拦截当前点击事件){
intercepted = true;
}else{
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
2.内部拦截
父容器不拦截任何事件,所有事件传递给子元素,如果子元素需要事件就直接消耗,否则交给父容器处理。
public boolean dispatchTouchEvent(MotionEvent event){
int x = (int) event.getX();
int y = (int) event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if(父容器需要此类点击事件){
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
父容器
public boolean onInterceptTouchEvent(MotionEvent event){
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN){
return false;
}else{
return true;
}
}