请看load_data_waiting_layout.xml内容,RelativeLayout, ProgressBar, TextView等控件设置clickable为true之后就不再支持ProgramListFragment的左右滑动事件。如果不设置clickable属性或者设置为false之后就支持ProgramListFragment的左右滑动事件。
programListFragment.getView().setOnTouchListener(programTouchListener);
难道设置clickable属性为true之后,就不执行programListFragment.getView()的onTouch方法了吗<?xml version="1.0" encoding="utf-8"?>
android:id="@+id/layout_waiting"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:background="#00ffff" >
android:id="@+id/waiting"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:clickable="true"
android:background="#ff00ff" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/waiting"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:text="@string/loading_data"
android:textSize="22sp"
android:clickable="true"
android:background="#0000ff" />
在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:
lThe callback that will run
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
调用setOnClickListener时,如果clickable为false,会被设定为true。
参考链接: