版权声明:本文出自汪磊的博客,转载请务必注明出处。

在上一篇《Android事件传递机制详解及最新源码分析——View篇》中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴,强烈建议先阅读上一篇。

好了,废话还是少说,直奔主题,开始本篇的ViewGroup事件传递机制探索之旅。

依然从简单的Demo例子现象开始分析

新建安卓工程,首先自定义一个Button以及一个RelativeLayout,很简单,只是重写了主要与事件传递机制相关的方法,代码如下:

自定义WLButton类:

 public class WLButton extends Button {

     private static final String TAG = "WL";

     public WLButton(Context context, AttributeSet attrs) {
super(context, attrs);
} @Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(TAG, "WLButton dispatchTouchEvent : "+event.getAction());
return super.dispatchTouchEvent(event);
} @Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "WLButton onTouchEvent : "+event.getAction());
return super.onTouchEvent(event);
} }

自定义WLRelativeLayout类:

 public class WLRelativeLayout extends RelativeLayout {

     private static final String TAG = "WL";

     public WLRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
} @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "WLRelativeLayout dispatchTouchEvent : "+ev.getAction());
return super.dispatchTouchEvent(ev);
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG, "WLRelativeLayout onInterceptTouchEvent : "+ev.getAction());
return super.onInterceptTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "WLRelativeLayout onTouchEvent : "+event.getAction());
return super.onTouchEvent(event);
}
}

对于WLRelativeLayout会额外注意到我们重写父类onInterceptTouchEvent方法,这里目前只是打印一下信息,追踪什么时候调用的,至于这个方法具体有什么作用先别着急,后续分析源码的时候咱们会着重分析。

接下来我们编写Acticity的代码,如下:

 public class MainActivity extends Activity implements OnTouchListener, OnClickListener {

     private static final String TAG = "WL";
private Button mButton;
private WLRelativeLayout myRelativeLayout; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.click);
mButton.setOnTouchListener(this);
mButton.setOnClickListener(this); myRelativeLayout = (WLRelativeLayout) findViewById(R.id.myRelativeLayout);
myRelativeLayout.setOnTouchListener(this);
myRelativeLayout.setOnClickListener(this); } @Override
public void onClick(View v) {
//
Log.i(TAG, "onClick :"+v);
} @Override
public boolean onTouch(View v, MotionEvent event) {
//
Log.i(TAG, "onTouch :"+",...action :"+event.getAction()+"...View :"+v);
return false;
} }

没什么好解释的,稍有经验的应该都能看懂,我们看下来不同操作打印信息:

首先我们正常点击Button,打印信息如下:

我们看到首先执行的是WLRelativeLayout的dispatchTouchEvent方法,然后执行onInterceptTouchEvent方法,接下来执行WLButton的dispatchTouchEvent方法,之后就不用多余解释了吧,就是上篇讲到的View的事件分发流程。

接下来,我们Button外部区域部分看看打印信息,如下:

点击外部区域有没有发现和View事件分发流程特别像,只不过多了一个onInterceptTouchEvent方法,但是我们发现在我们按下手指的时候(action为0的时候)会触发onInterceptTouchEvent方法,然而抬起手指的时候(action为1的时候)却没有触发onInterceptTouchEvent方法,这是为什么呢?别急,等我们分析完源码就会有答案。

我们注意到onInterceptTouchEvent 是有返回值的,默认我们是调用父类的onInterceptTouchEvent方法,那我们看看父类中onInterceptTouchEvent是什么逻辑呢,点进去看一下源码,我了个叉,真简单,如下:

 public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}

就直接返回一个false,那好,我们人为在子类中返回true,然后点击button看看打印情况。

接下来我们在WLRelativeLayout中将onInterceptTouchEvent返回值设置为true,然后运行程序会发现点击button内外打印信息都如下:

是不是有种触摸或者点击事件和button没关系了的感觉,就WLRelativeLayout自己玩的样子,这又是为什么呢?好了,带着这样疑问我们查看一下源码,看看源码层是怎么处理的。

ViewGroup事件传递机制源码分析(源码版本为API23

通过上面demo现象,我们得知,当我们点击Button首先触发的是WLRelativeLayout中的dispatchTouchEvent,我们向上寻找最终在ViewGroup中找到dispatchTouchEvent这个方法,我们知道ViewGroup是继承自View,不用过多解释了,ViewGroup只是将这个方法重写了而已。

ViewGroup中dispatchTouchEvent方法源码如下:

     @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
} // If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
} boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
} // Check for interception.
final boolean intercepted;
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
} 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 intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
} // Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) { // If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null; if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex); // If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
} if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
} newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
} resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
} // The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
} if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
} // Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
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.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
} // Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
} if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}

我了个叉,和View中这个方法比起来是不是量级瞬间增加不少,不过不用怕,我们着重分析重点部分,一点点来分析,再难也能攻破。

第13行定义一个变量handled,这个变量值在后续会改变,最后作为整个函数返回值返回。

第19-24行如果是手指按下的操作则执行cancelAndClearTouchTargets(ev)与resetTouchState()方法逻辑,这两个方法都干了什么呢?看上面的注释以及方法注释就知道差不多了,就是清楚一些之前的标记,状态。不过在cancelAndClearTouchTargets方法里面有个需要注意的点就是在其方法内调用了clearTouchTargets()方法,这个方法源码如下:

     /**
* Clears all touch targets.
*/
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}

第12行将mFirstTouchTarget置为null,这里我们需要特别注意,其实也是将之前记录的状态清空的操作,但是后续会对mFirstTouchTarget进行多次判断,贯穿整个主线,需要特别注意。

好了,回到dispatchTouchEvent方法中,我们继续向下分析:

第28-42行。

第28行定义一个intercepted变量,默认false。

第29,30行进行if判断,如果是ACTION_DOWN事件或者mFirstTouchTarget != null则会进入判断,在我们第一次按下手指点击的时候显然执行的是ACTION_DOWN事件,但是到这里mFirstTouchTarget依然为 null,所以第一次按下手指的时候是可以进入判断的,我们继续向下分析。

第31行 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0这又是什么鬼玩意呢?其实在ViewGroup中有个方法可供外部调用设置mGroupFlags的值(mGroupFlags是ViewGruop中定义的一个变量)。源码如下:

     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

         if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
} if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
} // Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}

核心就是8-12行代码,如果我们设置为true,再结合dispatchTouchEvent方法31行代码 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;会得出disallowIntercept最终为true。同样我们设置为false,会得出disallowIntercept最终为false。默认情况下disallowIntercept经过位运算为false。(这种按位操作进行判断的方式很多同学看起来可能不习惯,没办法基本功而已,源码中很多这种判断方式,希望大家都私下搞明白)

我们继续向下探究,32行代码,不用多余解释了吧,默认情况下是成立的,除非我们调用requestDisallowInterceptTouchEvent方法设置为false。

33行代码,调用onInterceptTouchEvent将其返回值赋值给intercepted,onInterceptTouchEvent这个方法大家还记得吧,上面提到过的。系统默认返回false

好了,到这里28-42行代码逻辑我们可以总结一下了:

这部分代码主要做了一件事就是根据不同设置改变intercepted变量的值。

如果我们调用requestDisallowInterceptTouchEvent设置为true,那么就不会执行到33行,而是执行36行代码将intercepted置为false。

默认情况下,是会执行33行代码,也就是说默认情况下,intercepted值是由onInterceptTouchEvent方法返回值决定的。

其实onInterceptTouchEvent返回值主要用来决定ViewGroup是否将触摸事件传递给具体View,起到事件拦截的作用。而如果我们调用requestDisallowInterceptTouchEvent设置为true,则无论onInterceptTouchEvent返回什么值,都会将触摸事件继续向下传递,起到禁止父布局拦截事件的作用。

好了,接下来我们继续向下分析,51-52行代码就是检测分发事件是否取消,然后赋值给canceled变量,默认为false,不取消。

56-57行代码定义newTouchTarget 与alreadyDispatchedToNewTouchTarget 变量,newTouchTarget后续用于纪录最终接收Touch事件的View。alreadyDispatchedToNewTouchTarget 用于纪录事件是否传递给子View,或者说是否有子View成功处理了Touch事件,有则置为True.

58行代码判断如果事件没被取消或者拦截则if判断成立进入58-160行代码逻辑判断。默认情况下if判断是成立的。

68-70行代码判断是否是各种ACTION_DOWN事件。

80-85行主要做了子View个数的判断以及获取子View的集合preorderedList 。

89-94行,for循环preorderedList 集合获取每个子View。这里需要说明一下:ViewGroup在addView的时候后添加的会显示在上层,我们点击View的时候肯定是想先让浮于上层的View响应触摸事件,从集合preorderedList 中取View的时候默认情况下是倒序获取的。(这里只是简单说一下,不是本文重点)

接下来,107-111行判断当前View是否能相应触摸事件以及触摸点是否在View所在区域内,如果均不成立则跳过此次循环继续下次循环。

122行,这里就是重点了,if判断调用dispatchTransformedTouchEvent方法,这个方法是做什么呢?源码如下:

 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
} // Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == ) {
return false;
} // If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
} // Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
} handled = child.dispatchTouchEvent(transformedEvent);
} // Done.
transformedEvent.recycle();
return handled;
}

这个方法起初我是看了好长时间,其实核心就是55-66行代码,对第三个参数child是否为空的判断,如果为空,则调用父类的dispatchTouchEvent方法,我们知道ViewGroup继承自View,也就是调用View中的dispatchTouchEvent方法。如果child不为空则调用child自身的dispatchTouchEvent方法,但是要知道child可能是View也可能是ViewGroup。对的,其实这里是有递归的思想的,先分析到这,稍后再回头来看这个方法。

回到122行代码,if判断调用dispatchTransformedTouchEvent方法,按照我们的Demo来分析,这里第三个参数传入的是我们自定义的WLButton ,按照上面的分析child不为空,会调用child自身的dispatchTouchEvent方法,这里也就是View中的dispatchTouchEvent方法,对于View的dispatchTouchEvent方法上篇已经分析过,这里不再多说。默认情况下,WLButton中dispatchTransformedTouchEvent会返回true,所以122行中dispatchTransformedTouchEvent方法也会返回true,表明当前child已经消耗掉此触摸事件,if判断成立。

继续向下看,138-139行代码为newTouchTarget赋值,并将alreadyDispatchedToNewTouchTarget 赋值为true,表明已经找到子View并且子View已经消耗此事件。

138行代码有个需要特别注意的地方,addTouchTarget方法里面为mFirstTouchTarget同样也赋值了,如下第四行代码:

     private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}

140行执行break,跳出整个循环。

好了,58-160行代码到此为止主要逻辑已经讲完,我们回头总结一下主要做了什么。

其实58-160行代码主要就做了一件事,在事件为ACTION_DOWN的时候查找有没有能消耗触摸事件的子View,遍历每一个子View,如果此View能消耗当前事件则跳出整个循环并且为mFirstTouchTarget 赋值。

以上主要是在ACTION_DOWN情况下进行的逻辑判断,163行以后无论什么情况下都会执行。

163行对mFirstTouchTarget 进行是否为null判断,我们知道在ACTION_DOWN情况下会查找是否有子View能消耗当前触摸事件那么mFirstTouchTarget 则不为null,如果没有则为null。

如果没有找到子View能处理当前触摸事件,则进入if判断,165行我们看到又会执行dispatchTransformedTouchEvent方法,注意此时传入的child为null,还记得我们上面分析的dispatchTransformedTouchEvent方法吗,当child为null的时候会调用View的dispatchTouchEvent方法,将当前ViewGroup当作普通View对待,依次调用onTouch以及onTouchEvent方法。

mFirstTouchTarget不为null时,继续传递给子View进行处理,依然是递归调用dispatchTransformedTouchEvent()方法来实现。

到你应该明白为什么事件传递是先传给子View处理,如果子View没有能处理的那么在传递给父类处理了吧,对的源码中关键就是dispatchTransformedTouchEvent方法中第三个参数是否为null。

好了,到此为止ViewGroup中我想说的也就讲解完了。如果你能将上述都理解了,那么开头的那几个小问题应该都不是问题了。

ViewGroup的事件传递机制有些地方还是挺绕的,不过确实很重要,希望大家静下心来好好理解其中核心点。

最后便于理解,附上一张流程图,如果图上每一个关键点你都能回忆起源码中相关核心代码那么你已经掌握差不多了:

下一篇,我们讨论一下Activity中的事件传递机制,过了ViewGroup这关,其余都是毛毛雨了。

Android事件传递机制详解及最新源码分析——ViewGroup篇的更多相关文章

  1. Android事件传递机制详解及最新源码分析——View篇

    摘要: 版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于安卓事件传递机制相信绝大部分开发者都听说过或者了解过,也是面试中最常问的问题之一.但是真正能从源码角度理解具体事件传递流程的相信并不多, ...

  2. Android事件传递机制详解及最新源码分析——Activity篇

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 在前两篇我们共同探讨了事件传递机制<View篇>与<ViewGroup篇>,我们知道View触摸事件是ViewGroup传递 ...

  3. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  4. Android 的事件传递机制,详解

    Android 的事件传递机制,详解 前两天和一个朋友聊天的时候.然后说到事件传递机制.然后让我说的时候,忽然发现说的不是非常清楚,事实上Android 的事件传递机制也是知道一些,可是感觉自己知道的 ...

  5. Android Touch事件传递机制详解 下

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...

  6. 《Android NFC 开发实战详解 》简介+源码+样章+勘误ING

    <Android NFC 开发实战详解>简介+源码+样章+勘误ING SkySeraph Mar. 14th  2014 Email:skyseraph00@163.com 更多精彩请直接 ...

  7. Android事件分发机制详解

    事件分发机制详解 一.基础知识介绍 1.经常用的事件有:MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP等 2 ...

  8. Android Touch事件传递机制详解 上

    最近总是遇到关于Android Touch事件的问题,如:滑动冲突的问题,以前也花时间学习过Android Touch事件的传递机制,可以每次用起来的时候总是忘记了,索性自己总结一下写篇文章避免以后忘 ...

  9. Android事件分发机制详解(1)----探究View的事件分发

    探究View的事件分发 在Activity中,只有一个按钮,注册一个点击事件 [java] view plaincopy button.setOnClickListener(new OnClickLi ...

随机推荐

  1. Nginx安装部署与测试

    场景:项目需要部署在生产环境中,这些新的工具都需要在生产环境中去实践练习.有时间再部署一套ELK的日志分析系统,这样的系统才算具有一定的应用价值. 1 Nginx安装 用root用户安装,采用源代码编 ...

  2. Orchard 学习

    https://github.com/OrchardCMS/Orchard  源码下载 http://www.orchardch.com/  中文介绍网站

  3. win10 vmware下Linux系统联网

    本来,这个问题网上资源很多的,但是就因为多,就变得杂了,对于许多新手,并不理解为啥,故记录下来方便以后使用.此处我采用配置VWmare虚拟网关(上学期刚刚学计算机网络,正好可以复习下).关于虚拟机下L ...

  4. 爬虫协议robots

    前面的话 Robots协议(也称为爬虫协议.机器人协议等)全称是“网络爬虫排除标准”(Robots Exclusion Protocol),网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页 ...

  5. No simulation input file assignm…

    QuartusII中仿真时出现No simulation input file assignment specified on simulator page of the settings dialo ...

  6. HDU 6097---Mindis(二分)

    题目链接 Problem Description The center coordinate of the circle C is O, the coordinate of O is (0,0) , ...

  7. WordCount去除标点方法之一

    package com.bw.day10;import java.io.IOException;import java.util.StringTokenizer;import org.apache.h ...

  8. equals()与 == 比较,hashCode方法

    1.Object类    Object类是java中一切类的父类,java中所有的类都直接或间接    继承自Object类        Object中定义的方法不多,原因在于,java的类多种多样 ...

  9. Oracle RAC 11g DG Broker配置和测试

    Oracle RAC 11g DG Broker配置和测试 之前在<RHEL6.4 + Oracle 11g DG测试环境快速搭建参考>已经简单说过. 本篇在实验环境中实际配置 环境: R ...

  10. 曲线点抽稀算法-Python实现

    何为抽稀 在处理矢量化数据时,记录中往往会有很多重复数据,对进一步数据处理带来诸多不便.多余的数据一方面浪费了较多的存储空间,另一方面造成所要表达的图形不光滑或不符合标准.因此要通过某种规则,在保证矢 ...