溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何在XML布局里給View設(shè)置點擊事件

發(fā)布時間:2020-10-26 14:11:50 來源:億速云 閱讀:156 作者:小新 欄目:編程語言

這篇文章主要介紹如何在XML布局里給View設(shè)置點擊事件,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

給一個View設(shè)置監(jiān)聽點擊事件是再普通不過的事情,比如

    view.setOnClickListener(onClickListener);

另外一種做法是直接在XML布局里面指定View點擊時候的回調(diào)方法,首先需要在Activity中編寫用于回調(diào)的方法,比如

    public void onClickView(View view){
        // do something
    }

然后在XML設(shè)置View的android:onClick屬性

    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:onClick="onClickView" />

有的時候從XML布局里直接設(shè)定點擊事件會比較方便(特別是在寫DEMO項目的時候),這種做法平時用的人并不多,從使用方式上大致能猜出來,View應(yīng)該是在運行的時候,使用反射的方式從Activity找到“onClickView”方法并調(diào)用,因為這種做法并沒有用到任何接口。

接下來,我們可以從源碼中分析出View是怎么觸發(fā)回調(diào)方法的。

如何在XML布局里給View設(shè)置點擊事件
View有5個構(gòu)造方法,第一個是內(nèi)部使用的,平時在Java代碼中直接創(chuàng)建View實例用的是第二種方法,而從XML布局渲染出來的View實例最后都是要調(diào)用第五種方法。

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        this(context);

        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
                
        for (int i = 0; i < N; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                ……
                // 處理onClick屬性
                case R.styleable.View_onClick:
                    if (context.isRestricted()) {
                        throw new IllegalStateException("The android:onClick attribute cannot "
                                + "be used within a restricted context");
                    }

                    final String handlerName = a.getString(attr);
                    if (handlerName != null) {
                        // 給當(dāng)前View實例設(shè)置一個DeclaredOnClickListener監(jiān)聽器
                        setOnClickListener(new DeclaredOnClickListener(this, handlerName));
                    }
                    break;
                }
        }
}

處理onClick屬性的時候,先判斷View的Context是否isRestricted,如果是就拋出一個IllegalStateException異常??纯磇sRestricted方法

    /**
     * Indicates whether this Context is restricted.
     *
     * @return {@code true} if this Context is restricted, {@code false} otherwise.
     *
     * @see #CONTEXT_RESTRICTED
     */
    public boolean isRestricted() {
        return false;
    }

isRestricted是用于判斷當(dāng)前的Context實例是否出于被限制的狀態(tài),按照官方的解釋,處限制狀態(tài)的Context,會忽略某些特點的功能,比如XML的某些屬性,很明顯,我們在研究的android:onClick屬性也會被忽略。

a restricted context may disable specific features. For instance, a View associated with a restricted context would ignore particular XML attributes.

不過isRestricted方法是Context中為數(shù)不多的有具體實現(xiàn)的方法(其余基本是抽象方法),這里直接返回false,而且這個方法只有在ContextWrapper和MockContext中有重寫
如何在XML布局里給View設(shè)置點擊事件

public class ContextWrapper extends Context {
    Context mBase;
    @Override
    public boolean isRestricted() {
        return mBase.isRestricted();
    }
}

public class MockContext extends Context {
    @Override
    public boolean isRestricted() {
        throw new UnsupportedOperationException();
    }
}

ContextWrapper中也只是代理調(diào)用mBase的isRestricted,而MockContext是寫單元測試的時候才會用到,所以這里的isRestricted基本只會返回false,除非使用了自定義的ContextWrapper并重寫了isRestricted。
回到View,接著的final String handlerName = a.getString(attr);其實就是拿到了android:onClick="onClickView"中的“onClickView”這個字符串,接著使用了當(dāng)前View的實例和“onClickView”創(chuàng)建了一個DeclaredOnClickListener實例,并設(shè)置為當(dāng)前View的點擊監(jiān)聽器。

/**
     * An implementation of OnClickListener that attempts to lazily load a
     * named click handling method from a parent or ancestor context.
     */
private static class DeclaredOnClickListener implements OnClickListener {
        private final View mHostView;
        private final String mMethodName;

        private Method mMethod;

        public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) {
            mHostView = hostView;
            mMethodName = methodName;
        }

        @Override
        public void onClick(@NonNull View v) {
            if (mMethod == null) {
                mMethod = resolveMethod(mHostView.getContext(), mMethodName);
            }

            try {
                mMethod.invoke(mHostView.getContext(), v);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException(
                        "Could not execute non-public method for android:onClick", e);
            } catch (InvocationTargetException e) {
                throw new IllegalStateException(
                        "Could not execute method for android:onClick", e);
            }
        }

        @NonNull
        private Method resolveMethod(@Nullable Context context, @NonNull String name) {
            while (context != null) {
                try {
                    if (!context.isRestricted()) {
                        return context.getClass().getMethod(mMethodName, View.class);
                    }
                } catch (NoSuchMethodException e) {
                    // Failed to find method, keep searching up the hierarchy.
                }

                if (context instanceof ContextWrapper) {
                    context = ((ContextWrapper) context).getBaseContext();
                } else {
                    // Can't search up the hierarchy, null out and fail.
                    context = null;
                }
            }

            final int id = mHostView.getId();
            final String idText = id == NO_ID ? "" : " with id '"
                    + mHostView.getContext().getResources().getResourceEntryName(id) + "'";
            throw new IllegalStateException("Could not find method " + mMethodName
                    + "(View) in a parent or ancestor Context for android:onClick "
                    + "attribute defined on view " + mHostView.getClass() + idText);
        }
}

到這里就清楚了,當(dāng)點擊View的時候,DeclaredOnClickListener實例的“onClick”方法會被調(diào)用,接著會調(diào)用“resolveMethod”方法,使用反射的方式從View的Context中找一個叫“onClickView”方法,這個方法有一個View類型的參數(shù),最后再使用反射調(diào)用該方法。要注意的是,“onClickView”方法必須是public類型的,不然反射調(diào)用時會拋出IllegalAccessException異常。

同時從源碼也能看出,使用android:onClick設(shè)置點擊事件的方式是從Context里面查找回調(diào)方法的,所以如果對于在Fragment的XML里創(chuàng)建的View,是無法通過這種方式綁定Fragment中的回調(diào)方法的,因為Fragment自身并不是一個Context,這里的View的Context其實是FragmentActivity,這也意味著使用這種方式能夠快速地從Fragment中回調(diào)到FragmentActivity。

此外,從DeclaredOnClickListener類的注釋也能看出android:onClick的功能,主要是起到懶加載的作用,只有到點擊View的時候,才會知道哪個方法是用于點擊回調(diào)的。

最后,特別需要補充說明的是,使用android:onClick給View設(shè)置點擊事件,就意味著要在Activity里添加一個非接口的public方法?,F(xiàn)在Android的開發(fā)趨勢是“不要把業(yè)務(wù)邏輯寫在Activity類里面”,這樣做有利于項目的維護,防止Activity爆炸,所以盡量不要在Activity里出現(xiàn)非接口、非生命周期的public方法。因此,貿(mào)然使用android:onClick可能會“污染”Activity。

以上是如何在XML布局里給View設(shè)置點擊事件的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI