请看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"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_waiting"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true"
    android:background="#00ffff" >
    <ProgressBar
        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" />
    <TextView
        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" />
</RelativeLayout>

在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