您好,登錄后才能下訂單哦!
這篇文章主要介紹如何在XML布局里給View設置點擊事件,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
給一個View設置監聽點擊事件是再普通不過的事情,比如
view.setOnClickListener(onClickListener);
另外一種做法是直接在XML布局里面指定View點擊時候的回調方法,首先需要在Activity中編寫用于回調的方法,比如
public void onClickView(View view){ // do something }
然后在XML設置View的android:onClick
屬性
<View android:layout_width="match_parent" android:layout_height="match_parent" android:onClick="onClickView" />
有的時候從XML布局里直接設定點擊事件會比較方便(特別是在寫DEMO項目的時候),這種做法平時用的人并不多,從使用方式上大致能猜出來,View應該是在運行的時候,使用反射的方式從Activity找到“onClickView”方法并調用,因為這種做法并沒有用到任何接口。
接下來,我們可以從源碼中分析出View是怎么觸發回調方法的。
View有5個構造方法,第一個是內部使用的,平時在Java代碼中直接創建View實例用的是第二種方法,而從XML布局渲染出來的View實例最后都是要調用第五種方法。
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) { // 給當前View實例設置一個DeclaredOnClickListener監聽器 setOnClickListener(new DeclaredOnClickListener(this, handlerName)); } break; } } }
處理onClick屬性的時候,先判斷View的Context是否isRestricted,如果是就拋出一個IllegalStateException異常。看看isRestricted方法
/** * 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是用于判斷當前的Context實例是否出于被限制的狀態,按照官方的解釋,處限制狀態的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中為數不多的有具體實現的方法(其余基本是抽象方法),這里直接返回false,而且這個方法只有在ContextWrapper和MockContext中有重寫
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中也只是代理調用mBase的isRestricted,而MockContext是寫單元測試的時候才會用到,所以這里的isRestricted基本只會返回false,除非使用了自定義的ContextWrapper并重寫了isRestricted。
回到View,接著的final String handlerName = a.getString(attr);
其實就是拿到了android:onClick="onClickView"
中的“onClickView”這個字符串,接著使用了當前View的實例和“onClickView”創建了一個DeclaredOnClickListener實例,并設置為當前View的點擊監聽器。
/** * 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); } }
到這里就清楚了,當點擊View的時候,DeclaredOnClickListener實例的“onClick”方法會被調用,接著會調用“resolveMethod”方法,使用反射的方式從View的Context中找一個叫“onClickView”方法,這個方法有一個View類型的參數,最后再使用反射調用該方法。要注意的是,“onClickView”方法必須是public類型的,不然反射調用時會拋出IllegalAccessException異常。
同時從源碼也能看出,使用android:onClick
設置點擊事件的方式是從Context里面查找回調方法的,所以如果對于在Fragment的XML里創建的View,是無法通過這種方式綁定Fragment中的回調方法的,因為Fragment自身并不是一個Context,這里的View的Context其實是FragmentActivity,這也意味著使用這種方式能夠快速地從Fragment中回調到FragmentActivity。
此外,從DeclaredOnClickListener類的注釋也能看出android:onClick
的功能,主要是起到懶加載的作用,只有到點擊View的時候,才會知道哪個方法是用于點擊回調的。
最后,特別需要補充說明的是,使用android:onClick
給View設置點擊事件,就意味著要在Activity里添加一個非接口的public方法。現在Android的開發趨勢是“不要把業務邏輯寫在Activity類里面”,這樣做有利于項目的維護,防止Activity爆炸,所以盡量不要在Activity里出現非接口、非生命周期的public方法。因此,貿然使用android:onClick
可能會“污染”Activity。
以上是如何在XML布局里給View設置點擊事件的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。