View

Clickable - Enable - Focusable 的

2018-12-11  本文已影响40人  ZHDelete

[转自]
从源码的角度分析Android中setClickable()和setEnable()的区别

android中setClickable,setEnabled,setFocusable的含义及区别


setClickable 设置为true时,表明控件可以点击,如果为false,就不能点击;“点击”适用于鼠标、键盘按键、遥控器等;

注意setOnClickListener方法会默认把控件的setClickable设置为true

setEnabled 使能控件,如果设置为false,该控件永远不会活动,不管设置为什么属性,都无效;
设置为true,表明激活该控件,控件处于活动状态,处于活动状态,就能响应事件了,比如触摸、点击、按键事件等;
setEnabled就相当于总开关一样,只有总开关打开了,才能使用其他事件。

setFocusable 使能控件获得焦点,设置为true时,并不是说立刻获得焦点,要想立刻获得焦点,得用requestFocus
使能获得焦点,就是说具备获得焦点的机会、能力,当有焦点在控件之间移动时,控件就有这个机会、能力得到焦点。

1:用Button测试

Clickable的解析:

Clickable = false

    <Button
        android:id="@+id/click_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="false"
        android:text="Test Clickable"
        android:textAllCaps="false" />

onTouchEvent(...)源码中OnClickListner触发的过程:

 //...省略
 final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

 if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    //...
                      if (!post(mPerformClick)) {
                        performClickInternal();
                     }
                    //...
                break;
            }
//...省略

可见,如果clickablefalse了,点击事件就无法执行了

但是,但是,但是:

如果我们给这个Button又设置了 ClickListener,那么clickable标志位,将被重置为true

View 源码

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
Enable的解析:

Enable = false 无法点击

Enable标志位,是一个总开关,

在View的源码里:dispatchTouchEvent(...)有如下代码:

 if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

前面的if (onFilterTouchEventForSecurity(event))进不去,下面的(mViewFlags & ENABLED_MASK) == ENABLED也不会走,只能进入:onTouchEvent(event)

 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;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }
        
    ...省略...
}

ENABLE_MASK = DISABLE后,return clickable,但是,要注意,此处clickabletrue(因为这个clicable是由CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE生成的),表示,如果ViewDisable的,但是如果ViewClickable,那么仍消耗这个touch事件,
但是,onTouchEvent(...)方法,就此结束,下面的performClick(...)不会响应,
也就是说OnClickListener不会有回调

上面所述要好好理解一下,比较绕

2 :用EditText 测试 Focusable

Focusable这个标志位,与EnableClickable的交集不是很广
Focusable set为true,是使控件获得焦点,但不是立即获得焦点,得用requestFocus()来请求获得焦点,
也就是说: setFocusable(true)是使控件具备获取焦点的机会能力,当有焦点在控件之间移动式,控件才能得到焦点

focusable.gif

    <EditText
        android:id="@+id/focusable_false_et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:focusable="false"
        android:hint="focusable - false" />

    <EditText
        android:id="@+id/focusable_true_et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="focusable - true" />

从效果我们可以看到,在xml里设置android:focusable="false"会使EditText无法获取焦点,
进而,无法调出输入键盘

Focusable = false 点击事件 仍有效

class MainActivity : AppCompatActivity() {
    val TAG = "C_E_F"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.d(TAG, "click btn isClickAble-1: ${click_btn.isClickable}")


//        click_btn.setOnClickListener {
//            Log.d(TAG, "click btn isClickAble-2: ${click_btn.isClickable}")
//            Log.d(TAG, "click btn")
//        }

        enable_btn.setOnClickListener {
            Log.d(TAG, "enable btn")
        }

        focusable_btn.setOnClickListener {
            Log.d(TAG, "focusable btn")
        }

        focusable_false_et.setOnClickListener {
            Log.d(TAG, "focusable false et")
        }

        focusable_true_et.setOnClickListener {
            Log.d(TAG, "focusable true et")
        }
    }
}

_focuse_enable_test_181210 D/C_E_F: focusable false et
_focuse_enable_test_181210 D/C_E_F: focusable false et
_focuse_enable_test_181210 D/C_E_F: focusable true et
_focuse_enable_test_181210 D/C_E_F: focusable true et
_focuse_enable_test_181210 D/C_E_F: focusable true et
_focuse_enable_test_181210 D/C_E_F: focusable true et
_focuse_enable_test_181210 D/C_E_F: focusable false et

这里再附上关于requestFocuse(int direction)的翻译:

    /**
     * Call this to try to give focus to a specific view or to one of its
     * descendants and give it a hint about what direction focus is heading.

     * 调用此方法以尝试将焦点放在特定视图或特定视图的子View上
     * 并给出一个关于焦点方向的暗示。
    
     *
     * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
     * false), or if it is focusable and it is not focusable in touch mode
     * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
     *
     * 如果一个View是无法聚焦的(即: `isFoucusable()`方法返回的是false),或者这个View在触摸模式(touch 
     * mode)下无法聚焦(即: `isFocusableInTouchMode`返回false),那么这个View不会持有焦点,
     *
     * See also {@link #focusSearch(int)}, which is what you call to say that you
     * have focus, and you want your parent to look for the next one.
     *
     * 参见: `focusSearch(int direction)`方法,此方法作用是: 当前view持有焦点,然后,你打算寻找下一个可持有焦点的View
    
     * This is equivalent to calling {@link #requestFocus(int, Rect)} with
     * <code>null</code> set for the previously focused rectangle.
     * 调用`requestFocus(int direction)`和调用`requestFocus(int direction, Rect previouslyFocusedRect)`方法,
     * 并给rect传入null,是等价的

     *
     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
     * @return Whether this view or one of its descendants actually took focus.
     * 返回: 这个View或其子View 能否 持有焦点
     */
    public final boolean requestFocus(int direction) {
        return requestFocus(direction, null);
    }

笔者粗略浏览了一遍View的源码,发现:FOCUSABLE 这个标志位,并没有与dispatchTouchEvent(...)onTouchEvent(...)方法
发生交集,因此,本篇不再继续深入挖掘.只需了解即可.

--End--

上一篇 下一篇

猜你喜欢

热点阅读