请看load_data_waiting_layout.xml内容,RelativeLayout, ProgressBar, TextView等控件设置clickable为true之后就不再支持ProgramListFragment的左右滑动事件。如果不设置clickable属性或者设置为false之后就支持ProgramListFragment的左右滑动事件。
programListFragment.getView().setOnTouchListener(programTouchListener);
难道设置clickable属性为true之后,就不执行programListFragment.getView()的onTouch方法了吗
在xml文件设置clickable属性相当于在程序中调用setClickable方法。从android.view.View的setClickable方法实现进来分析clickable属性对touch事件的影响。
void setClickable(boolean clickable)
public void setClickable(boolean clickable) { setFlags(clickable ? CLICKABLE : 0, CLICKABLE); }
void setFlags(int flags, int mask)
/** * Set flags controlling behavior of this view. * * @param flags Constant indicating the value which should be set * @param mask Constant indicating the bit range that should be changed */ void setFlags(int flags, int mask) { int old = mViewFlags; mViewFlags = (mViewFlags & ~mask) | (flags & mask); int changed = mViewFlags ^ old; if (changed == 0) { return; } int privateFlags = mPrivateFlags; /* Check if the FOCUSABLE bit has changed */ if (((changed & FOCUSABLE_MASK) != 0) && ((privateFlags & PFLAG_HAS_BOUNDS) !=0)) { if (((old & FOCUSABLE_MASK) == FOCUSABLE) && ((privateFlags & PFLAG_FOCUSED) != 0)) { /* Give up focus if we are no longer focusable */ clearFocus(); } else if (((old & FOCUSABLE_MASK) == NOT_FOCUSABLE) && ((privateFlags & PFLAG_FOCUSED) == 0)) { /* * Tell the view system that we are now available to take focus * if no one else already has it. */ if (mParent != null) mParent.focusableViewAvailable(this); } if (AccessibilityManager.getInstance(mContext).isEnabled()) { notifyAccessibilityStateChanged(); } } if ((flags & VISIBILITY_MASK) == VISIBLE) { if ((changed & VISIBILITY_MASK) != 0) { /* * If this view is becoming visible, invalidate it in case it changed while * it was not visible. Marking it drawn ensures that the invalidation will * go through. */ mPrivateFlags |= PFLAG_DRAWN; invalidate(true); needGlobalAttributesUpdate(true); // a view becoming visible is worth notifying the parent // about in case nothing has focus. even if this specific view // isn't focusable, it may contain something that is, so let // the root view try to give this focus if nothing else does. if ((mParent != null) && (mBottom > mTop) && (mRight > mLeft)) { mParent.focusableViewAvailable(this); } } } /* Check if the GONE bit has changed */ if ((changed & GONE) != 0) { needGlobalAttributesUpdate(false); requestLayout(); if (((mViewFlags & VISIBILITY_MASK) == GONE)) { if (hasFocus()) clearFocus(); clearAccessibilityFocus(); destroyDrawingCache(); if (mParent instanceof View) { // GONE views noop invalidation, so invalidate the parent ((View) mParent).invalidate(true); } // Mark the view drawn to ensure that it gets invalidated properly the next // time it is visible and gets invalidated mPrivateFlags |= PFLAG_DRAWN; } if (mAttachInfo != null) { mAttachInfo.mViewVisibilityChanged = true; } } /* Check if the VISIBLE bit has changed */ if ((changed & INVISIBLE) != 0) { needGlobalAttributesUpdate(false); /* * If this view is becoming invisible, set the DRAWN flag so that * the next invalidate() will not be skipped. */ mPrivateFlags |= PFLAG_DRAWN; if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE) && hasFocus()) { // root view becoming invisible shouldn't clear focus and accessibility focus if (getRootView() != this) { clearFocus(); clearAccessibilityFocus(); } } if (mAttachInfo != null) { mAttachInfo.mViewVisibilityChanged = true; } } if ((changed & VISIBILITY_MASK) != 0) { if (mParent instanceof ViewGroup) { ((ViewGroup) mParent).onChildVisibilityChanged(this, (changed & VISIBILITY_MASK), (flags & VISIBILITY_MASK)); ((View) mParent).invalidate(true); } else if (mParent != null) { mParent.invalidateChild(this, null); } dispatchVisibilityChanged(this, (flags & VISIBILITY_MASK)); } if ((changed & WILL_NOT_CACHE_DRAWING) != 0) { destroyDrawingCache(); } if ((changed & DRAWING_CACHE_ENABLED) != 0) { destroyDrawingCache(); mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; invalidateParentCaches(); } if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) { destroyDrawingCache(); mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } if ((changed & DRAW_MASK) != 0) { if ((mViewFlags & WILL_NOT_DRAW) != 0) { if (mBackground != null) { mPrivateFlags &= ~PFLAG_SKIP_DRAW; mPrivateFlags |= PFLAG_ONLY_DRAWS_BACKGROUND; } else { mPrivateFlags |= PFLAG_SKIP_DRAW; } } else { mPrivateFlags &= ~PFLAG_SKIP_DRAW; } requestLayout(); invalidate(true); } if ((changed & KEEP_SCREEN_ON) != 0) { if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) { mParent.recomputeViewAttributes(this); } } if (AccessibilityManager.getInstance(mContext).isEnabled() && ((changed & FOCUSABLE) != 0 || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0 || (changed & ENABLED) != 0)) { notifyAccessibilityStateChanged(); } }
扩展TextView,重写onTouchEvent方法,返回false。
public class MyTextView extends TextView { public MyTextView(Context context) { super(context); } public MyTextView(Context context, AttributeSet attrs) { super(context, attrs); } public MyTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); return false; } }
调试结果:返回false时,点击TextView无效。
跟踪touch事件的执行
11-08 15:22:57.524: D/MyLinearLayout(22210): ++onInterceptTouchEvent++ 11-08 15:22:57.524: D/MyLinearLayout(22210): result:false 11-08 15:22:57.524: D/MyRelativeLayout(22210): ++onInterceptTouchEvent++ 11-08 15:22:57.524: D/MyRelativeLayout(22210): result:false 11-08 15:22:57.524: D/MyLinearLayout(22210): ++onInterceptTouchEvent++ 11-08 15:22:57.534: D/MyLinearLayout(22210): result:false 11-08 15:22:57.534: D/MyTextView(22210): ++onTouchEvent++ 11-08 15:22:57.534: D/MyTextView(22210): result:true
上面日志显示:MyText在ViewTree中处于第4级,第1,2,3级分别是MyLinearLayout, MyRelativeLayout, MyLinearLayout.当发生touch事件时,依次调用第1,2,3级的onInterceptTouchEvent方法处理。onInterceptTouchEvent返回false,就继续调用下级View的onTouchEvent或者下级ViewGoup的onInterceptTouchEvent来处理。
11-08 15:52:56.374: D/MyLinearLayout(23972): ++onInterceptTouchEvent++ 11-08 15:52:56.374: D/MyLinearLayout(23972): result:false 11-08 15:52:56.374: D/MyRelativeLayout(23972): ++onInterceptTouchEvent++ 11-08 15:52:56.374: D/MyRelativeLayout(23972): result:false 11-08 15:52:56.374: D/MyLinearLayout(23972): ++onInterceptTouchEvent++ 11-08 15:52:56.374: D/MyLinearLayout(23972): result:false 11-08 15:52:56.374: D/MyTextView(23972): ++onTouchEvent++ 11-08 15:52:56.374: D/MyTextView(23972): result:true 11-08 15:52:56.374: D/MyTextView(23972): ++dispatchTouchEvent++ 11-08 15:52:56.374: D/MyTextView(23972): result:true 11-08 15:52:56.374: D/MyLinearLayout(23972): ++dispatchTouchEvent++ 11-08 15:52:56.374: D/MyLinearLayout(23972): result:true 11-08 15:52:56.374: D/MyRelativeLayout(23972): ++dispatchTouchEvent++ 11-08 15:52:56.374: D/MyRelativeLayout(23972): result:true 11-08 15:52:56.374: D/MyLinearLayout(23972): ++dispatchTouchEvent++ 11-08 15:52:56.374: D/MyLinearLayout(23972): result:true
上面日志显示:先是依次调用第1,2,3级的onInterceptTouchEvent方法,以及第4级的onTouchEvent方法;然后是依次调用第4,3,2,1级的dispatchTouchEvent方法。
调用toString()方法标识当前View
11-08 16:08:38.424: D/WatchTvFragment(25580): programListFragment.getView():android.support.v4.app.NoSaveStateFrameLayout{421799f0 V.E..... ........ 427,0-986,703 #7f070077 app:id/program_list_fragment} 11-08 16:09:18.994: D/MyLinearLayout(25580): ++onInterceptTouchEvent++ 11-08 16:09:18.994: D/MyLinearLayout(25580): com.tvie.ivideo.pad.live.MyLinearLayout{42188478 V.E..... ........ 0,0-559,703} 11-08 16:09:18.994: D/MyLinearLayout(25580): result:false 11-08 16:09:18.994: D/MyRelativeLayout(25580): ++onInterceptTouchEvent++ 11-08 16:09:18.994: D/MyRelativeLayout(25580): com.tvie.ivideo.pad.live.MyRelativeLayout{4216cfd0 V.E..... ........ 0,0-559,43} 11-08 16:09:18.994: D/MyRelativeLayout(25580): result:false 11-08 16:09:18.994: D/MyLinearLayout(25580): ++onInterceptTouchEvent++ 11-08 16:09:18.994: D/MyLinearLayout(25580): com.tvie.ivideo.pad.live.MyLinearLayout{4216d1f8 V.E..... ........ 0,0-524,43} 11-08 16:09:18.994: D/MyLinearLayout(25580): result:false 11-08 16:09:18.994: D/MyTextView(25580): ++onTouchEvent++ 11-08 16:09:18.994: D/MyTextView(25580): com.tvie.ivideo.pad.live.MyTextView{4216e750 V.ED..C. ...P.... 321,7-412,36 #7f07014b app:id/tomorrow} 11-08 16:09:18.994: D/MyTextView(25580): result:true 11-08 16:09:18.994: D/MyTextView(25580): ++dispatchTouchEvent++ 11-08 16:09:18.994: D/MyTextView(25580): com.tvie.ivideo.pad.live.MyTextView{4216e750 V.ED..C. ...P.... 321,7-412,36 #7f07014b app:id/tomorrow} 11-08 16:09:18.994: D/MyTextView(25580): result:true 11-08 16:09:18.994: D/MyLinearLayout(25580): ++dispatchTouchEvent++ 11-08 16:09:18.994: D/MyLinearLayout(25580): com.tvie.ivideo.pad.live.MyLinearLayout{4216d1f8 V.E..... ........ 0,0-524,43} 11-08 16:09:18.994: D/MyLinearLayout(25580): result:true 11-08 16:09:18.994: D/MyRelativeLayout(25580): ++dispatchTouchEvent++ 11-08 16:09:18.994: D/MyRelativeLayout(25580): com.tvie.ivideo.pad.live.MyRelativeLayout{4216cfd0 V.E..... ........ 0,0-559,43} 11-08 16:09:18.994: D/MyRelativeLayout(25580): result:true 11-08 16:09:18.994: D/MyLinearLayout(25580): ++dispatchTouchEvent++ 11-08 16:09:18.994: D/MyLinearLayout(25580): com.tvie.ivideo.pad.live.MyLinearLayout{42188478 V.E..... ........ 0,0-559,703} 11-08 16:09:18.994: D/MyLinearLayout(25580): result:true
上面日志显示:
programListFragment.getView()和R.layout.watchtv_program_list的root layout不是同一个view。
11-08 17:02:35.134: D/MyLinearLayout(25580): ++onInterceptTouchEvent++ 11-08 17:02:35.134: D/MyLinearLayout(25580): result:false 11-08 17:02:35.134: D/MyRelativeLayout(25580): ++onInterceptTouchEvent++ 11-08 17:02:35.134: D/MyRelativeLayout(25580): result:false 11-08 17:02:35.134: D/MyLinearLayout(25580): ++onInterceptTouchEvent++ 11-08 17:02:35.134: D/MyLinearLayout(25580): result:false 11-08 17:02:35.134: D/MyLinearLayout(25580): ++onTouchEvent++ 11-08 17:02:35.134: D/MyLinearLayout(25580): result:false 11-08 17:02:35.134: D/MyLinearLayout(25580): ++dispatchTouchEvent++ 11-08 17:02:35.134: D/MyLinearLayout(25580): result:false 11-08 17:02:35.134: D/MyRelativeLayout(25580): ++onTouchEvent++ 11-08 17:02:35.134: D/MyRelativeLayout(25580): result:false 11-08 17:02:35.134: D/MyRelativeLayout(25580): ++dispatchTouchEvent++ 11-08 17:02:35.134: D/MyRelativeLayout(25580): result:false 11-08 17:02:35.134: D/MyLinearLayout(25580): ++onTouchEvent++ 11-08 17:02:35.134: D/MyLinearLayout(25580): result:false 11-08 17:02:35.134: D/MyLinearLayout(25580): ++dispatchTouchEvent++ 11-08 17:02:35.134: D/MyLinearLayout(25580): result:false 11-08 17:02:35.134: D/WatchTvFragment(25580): ++programTouchListener.onTouch++
当touch事件发生在MyTextView外面的MyLinearLayout时,可以让fragment左右滑动起来。查看日志:MyTextView外部的MyLinearLayout在dispatchTouchEvent方法中返回false。前面,MyTextView在dispatchTouchEvent方法中返回true,导致外面的几个xxxLayout都返回true。
在MyLinearLayout.dispatchTouchEvent方法中指定返回false,那么下级MyTextView的click事件就无效。说明dispatchTouchEvent返回值起着很重要的作用。
11-08 17:27:55.144: D/MyLinearLayout(32017): ++onInterceptTouchEvent++ 11-08 17:27:55.144: D/MyLinearLayout(32017): result:false 11-08 17:27:55.144: D/MyRelativeLayout(32017): ++onInterceptTouchEvent++ 11-08 17:27:55.144: D/MyRelativeLayout(32017): result:false 11-08 17:27:55.144: D/MyLinearLayout(32017): ++onInterceptTouchEvent++ 11-08 17:27:55.144: D/MyLinearLayout(32017): result:false 11-08 17:27:55.144: D/MyTextView(32017): ++onTouchEvent++ 11-08 17:27:55.144: D/MyTextView(32017): result:true 11-08 17:27:55.144: D/MyTextView(32017): ++dispatchTouchEvent++ 11-08 17:27:55.144: D/MyTextView(32017): result:false 11-08 17:27:55.154: D/MyLinearLayout(32017): ++onTouchEvent++ 11-08 17:27:55.154: D/MyLinearLayout(32017): result:false 11-08 17:27:55.154: D/MyLinearLayout(32017): ++dispatchTouchEvent++ 11-08 17:27:55.154: D/MyLinearLayout(32017): result:false 11-08 17:27:55.154: D/MyRelativeLayout(32017): ++onTouchEvent++ 11-08 17:27:55.154: D/MyRelativeLayout(32017): result:false 11-08 17:27:55.154: D/MyRelativeLayout(32017): ++dispatchTouchEvent++ 11-08 17:27:55.154: D/MyRelativeLayout(32017): result:false 11-08 17:27:55.154: D/MyLinearLayout(32017): ++onTouchEvent++ 11-08 17:27:55.154: D/MyLinearLayout(32017): result:false 11-08 17:27:55.154: D/MyLinearLayout(32017): ++dispatchTouchEvent++ 11-08 17:27:55.154: D/MyLinearLayout(32017): result:false 11-08 17:27:55.154: D/WatchTvFragment(32017): ++programTouchListener.onTouch++
日志第8行:第4级MyTextView的dispatchTouchEvent方法返回false。接着调用第3,2,1级的onTouchEvent, dispatchTouchEvent方法。前面的日志中,dispatchTouchEvent方法返回true,后面的onTouchEvent方法就不再调用。
void android.view.View.setOnClickListener(OnClickListener l)
Register a callback to be invoked when this view is clicked. If this view is not clickable, it becomes clickable.
Parameters:
l The callback that will run
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
调用setOnClickListener时,如果clickable为false,会被设定为true。
参考链接:
1. http://www.aslibra.com/blog/post/android_GestureDetector.php
2.http://blog.csdn.net/lyb2518/article/details/7889169