標籤:
前言在我們的項目中,我們幾乎天天和一些固定的代碼打交道,比如在Activity中你要寫findViewById(int)方法來找到控制項,然而這樣子的代碼對於一個稍微有點資格的程式員來說,都是毫無營養的,你根本學不到任何的東西,但是你卻必須寫。這也就是註解架構的出現,極大的簡化了程式員的工作,並且讓代碼簡潔。也許你早就使用過了註解的架構,那麼你會自己自己寫嗎?好了,今天就讓大家來完成一個註解的架構閱讀的你需要掌握的知識
1.Java反射的知識
2.Java註解的知識
普通的寫法xml布局檔案,就一個按鈕
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="${relativePackage}.${activityClass}" > <Button android:id="@+id/bt" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="點我" /></RelativeLayout>
Activity中的代碼
public class MainActivity extends Activity implements OnClickListener {/** * 按鈕 */private Button bt = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 找到按鈕bt = (Button) findViewById(R.id.bt);// 設定點擊事件bt.setOnClickListener(this);}@Overridepublic void onClick(View v) {Toast.makeText(this, "點我了", Toast.LENGTH_LONG).show();}}
也是很簡單的,然後是
上面就是我們平常的寫法,那麼我們來看看用了註解架構的寫法,先體驗,再動手寫註解架構用了註解架構的寫法
public class MainActivity extends Activity {/** * 按鈕 */@Injection(value = R.id.bt,click = "clickView")private Button bt = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//讓註解起作用ViewInjectionUtil.injectView(this);}public void clickView() {Toast.makeText(this, "點我了,用了註解架構", Toast.LENGTH_LONG).show();}}
就這麼少代碼?是的,你沒有看錯,就這麼點代碼,和剛剛的普通的寫法的效果是一模一樣的只是多了一句讓註解起作用的代碼,看下用了註解架構的效果
可以看到,程式是沒有問題的,那麼讓我們來實現它吧!
思路1.首先明確一點,架構一定是幫你做了findViewById(int)和setOnClickListener(View.OnCLickListener)的這些操作,否則代碼是不可能正常啟動並執行2.findViewById(int)方法需要的是控制項的id,從架構的使用上看,控制項的id就是利用註解告訴了架構
@Injection(value = R.id.bt,click = "clickView")
此處的R.id.bt就是value的值,所以可以知道就是這裡告訴架構的,而我們的點擊事件起作用了,這裡告訴了架構需要調用的方法的名稱所以我們的一個和普通的方法可以被調用
思路的總結所以註解就是一個資訊,它會被架構讀取並且架構會幫你做一些繁瑣的工作,從而讓你的代碼編輯簡潔,所以用了註解之後,比如讓架構來讀取,所以這也就是為什麼會有一句很陌生的語句:
//讓註解起作用ViewInjectionUtil.injectView(this);
這句代碼就是架構讀取了註解中的資訊並且幫你找到了控制項,並且註冊相應的事件
開工寫代碼首先定義建立一個註解就是上述使用的那個註解:@Injection,你可以理解為建立一個類差不多的概念,但是兩者是不同的,自己要清楚哦
/** * 運行時期有效,和針對的是欄位 * * @author xiaojinzi * */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Injection {/** * 欄位對應布局檔案的id * * @return */int value();/** * 點擊事件 * @return */String click() default "";/** * 長按的點擊事件 * @return */String longClick() default "";}這裡對代碼做一下解釋@Target(ElementType.FIELD)表示我們聲明的註解是作用到欄位上的,你肯定見過作用在方法上的註解,比如:@Override 這個不陌生吧?@Retention(RetentionPolicy.RUNTIME)這個表示我們聲明的註解是啟動並執行時候有效,還有其他的情況,這裡不做深入
好了,我們的註解就寫好了,註解中有三個屬性可以用,分別是:1.value int類型 表示控制項的id2.click String類型 點擊事件的方法名稱3.longClick String類型 長按事件的方法名稱好了,我們的註解就寫完了,你可以在任何一個欄位上面使用這個註解了,都是不會報錯的,但是還沒有實際的作用
編寫核心代碼首先建立一個類,用來讓註解起作用,也就是之前說過的,去讀取註解中的資訊,然後完成繁瑣的操作我就起一個名字為:ViewInjectionUtil,你可以自己起一個其他的名稱架構幫你完成findViewById(int)的操作那麼是不是你需要把當前的Activity對象傳給架構呢?
/*** 對activity中的欄位進行注入** @param act*/public static void injectView(Activity act) { // 擷取這個activity中的所有欄位 Field[] fields = act.getClass().getDeclaredFields(); for (int i = 0; i < fields.length; i++) { // 迴圈拿到每一個欄位 Field field = fields[i]; if (field.isAnnotationPresent(Injection.class)) { // 如果這個欄位有注入的註解 // 擷取註解對象 Injection injection = field.getAnnotation(Injection.class); int value = injection.value(); field.setAccessible(true); // 即使私人的也可以設定資料 Object view = null; try { view = act.findViewById(value); // 設定欄位的屬性 field.set(act, view); } catch (Exception e) { e.printStackTrace(); L.s(TAG, "注入屬性失敗:" + field.getClass().getName() + ":" + field.getName()); } try { if (view instanceof View) { View v = (View) view; // 擷取點擊事件的觸發的方法名稱 String methodName = injection.click(); EventListener eventListener = null; // 如果不是Null 字元串 if (!"".equals(methodName)) { eventListener = new EventListener(act); // 設定點擊事件 v.setOnClickListener(eventListener); eventListener.setClickMethodName(methodName); } methodName = injection.longClick(); if (!"".equals(methodName)) { if (eventListener == null) { eventListener = new EventListener(act); } // 設定點擊事件 v.setOnLongClickListener(eventListener); eventListener.setLongClickMethodName(methodName); } } } catch (Exception e) { e.printStackTrace(); } } }}
/** * Created by cxj on 2016/1/21. * * @author 小金子 */public class EventListener implements View.OnClickListener, View.OnLongClickListener {/** * 類的標識 */private String tag = "EventListener";/** * 設定是否有日誌的輸出 */private boolean isLog = false;/** * 反射中要被呼叫者法的對象,通過構造方法進來 */private Object receiver = null;/** * 點擊事件的方法名字 */private String clickMethodName = "";/** * 長按事件的方法的名字 */private String longClickMethodName = "";/** * 設定點擊事件的方法名字 * * @param clickMethodName */public void setClickMethodName(String clickMethodName) {this.clickMethodName = clickMethodName;}/** * 設定長按的點擊事件的方法的名字 * * @param longClickMethodName */public void setLongClickMethodName(String longClickMethodName) {this.longClickMethodName = longClickMethodName;}/** * 建構函式 * * @param receiver * 控制項所在的activity或者Fragment */public EventListener(Object receiver) {this.receiver = receiver;}@Overridepublic void onClick(View v) {Method method = null;try {method = receiver.getClass().getMethod(clickMethodName);if (method != null) {// 調用該方法method.invoke(receiver);}} catch (Exception e) {if (isLog)L.s(tag, "尋找無參數列表的方法:" + clickMethodName + "失敗");}try {if (method == null) {method = receiver.getClass().getMethod(clickMethodName, View.class);if (method != null) {method.invoke(receiver, v);}}} catch (Exception e) {if (isLog)L.s(tag, "尋找帶有View型別參數的方法:" + clickMethodName + "失敗");}}@Overridepublic boolean onLongClick(View v) {Method method = null;try {method = receiver.getClass().getMethod(longClickMethodName);if (method != null) {// 調用該方法method.invoke(receiver);}} catch (Exception e) {if (isLog)L.s(tag, "尋找無參數列表的方法:" + longClickMethodName + "失敗");}try {if (method == null) {method = receiver.getClass().getMethod(longClickMethodName, View.class);if (method != null) {method.invoke(receiver, v);}}} catch (Exception e) {if (isLog)L.s(tag, "尋找帶有View型別參數的方法:" + longClickMethodName + "失敗");}return true;}}
類EventListener:這個類主要是處理View的事件的,比如點擊事件,長按事件,類實現了這些介面建立該類的時候需要傳入Activity對象或者Fragment對象,比如點擊事件觸發的時候該類中實現的方法onClick(View)方法被調用,但是並不是使用者指定的方法名的那個方法,所以這裡需要有註解中的click字串資訊,也就是Activity需要調用的方法的方法名,有了方法名和Activity,就可以利用反射從activity中調用指定方法名的方法,從而達到我們開始的時候示範的時候那樣子,點擊按鈕的時候,會調用activity中的bt欄位上的註解中的click寫的那個方法名對應的方法
這裡對方法injectView(Activity act)中的代碼做一個詳細的解釋:1.利用反射擷取到activity中所有的欄位2.迴圈所有的欄位,篩選出有Injection註解的欄位3.讀取欄位中的註解中的資訊: a)拿到控制項的id資訊,調用act中的findViewById(int)找到控制項,利用反射賦值給欄位,這個過程等同於普通代碼: bt = (Button)findViewById(R.id.bt); b)拿到點擊事件和長按事件觸發的時候調用的方法的方法名,然後建立EventListener對象,讓這個對象儲存點擊事件的方法名稱和長按事件的方法名稱和activity的引用,並且給View對象註冊相應的事件,EventListener中已經實現了對應的介面,所以註冊的時候,傳入的對象就是EventListener的對象,後續事件生效的時候就是上述類EventListener的功能了
到這裡其實我們的代碼就寫完了,其實你懂反射的知識的話,這些代碼都是不難的,看起來會很快,當然了,如果你不懂反射的知識,這裡建議你去學習一下,作為一個JavaCoder,我覺得反射這麼棒的內容,你不應該錯過!因為在Java的世界中到處都是它的影子!
總結博主只介紹了點擊事件和長按事件,其實你可以照貓畫虎的加入其他更多的事件,讓你的架構支援更多的操作,也更強大另外,本篇部落格其實給大家一個思路,我相信你們能寫出更好的小架構的!只為了讓編碼變的更加輕鬆!
小架構下載註解架構:https://github.com/xiaojinzi123/xiaojinzi-openSource-viewAnnotation
Android 自訂註解架構