Android 之實現執行時註解控制元件
一、首先的說一下註解的分類,
1、執行時註解,程式碼簡單,複雜性低,但是效率稍微低一點
2、編譯時註解、程式碼多,結構複雜,但是效率高
這裡介紹的是執行時註解。
二、還是先說一下思路
首先建立對應的註解類,並且通過反射 findViewById 方法去實現控制元件的註解
方法的註解使用了動態代理模式,去減少程式碼的量,然後通過反射去呼叫對應的方法。
反正特別重要的就是反射反射反射
三、實現(這裡主要是添加布局、獲取控制元件、設定點選事件)
1、添加布局註解
正常情況下是要呼叫 setContentView的方法設定Activity的佈局檔案,如果使用註解的話也是一句話
建立註解檔案,設定註解策略為執行時,作用地方為type型別
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
int value();
}
得到當前的類,並且得到註解中的id,然後通過反射 setContentView實現呼叫
Class<?> aClass = mContext.getClass(); //獲取到註解類 ContentView contentView = aClass.getAnnotation(ContentView.class); if (contentView != null) { //獲取到註解的id int layoutID = contentView.value(); try { // 通過反射執行註解方法 Method method = aClass.getMethod("setContentView", int.class); method.invoke(mContext, layoutID); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
2、控制元件註解
還是先建立註解類
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
int value();
}
首先通過傳入的Context得到Class物件
Class<?> aClass = mContext.getClass();
通過DeclaredFields方法當前物件得到所有的成員變數
Field[] declaredFields = aClass.getDeclaredFields();
在遍歷獲取這個陣列,通過每一個成員變數的
getAnnotation方法得到註解,如果註解不為空,則說明有id,是需要的。
然後通過getMethod方法反射findViewById得到該方法,然後反射執行,得到View物件,
然後在把當前值給反射物件
Class<?> aClass = mContext.getClass();
//獲取到類中所有的成員變數
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field : declaredFields) {
//得到成員變數的註解
ViewInject viewInject = field.getAnnotation(ViewInject.class);
//如果成員變數不為空,說明有註解,則獲取id
if (viewInject != null) {
//獲取到控制元件的id
int valueID = viewInject.value();
try {
//通過反射呼叫findViewById 方法
Method method = aClass.getMethod("findViewById", int.class);
View view = (View) method.invoke(mContext, valueID);
field.setAccessible(true);
field.set(mContext, view);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
3、事件註解
實現思路 獲取當前類中所有的方法,然後去遍歷,然後得到方法的註解,然後判斷是不是存在 EventBase,如果有的話就通過反射findViewById方法得到當前控制元件,再得到當前物件的事件,通過動態代理去實現呼叫
首先的拿到事件的名字、型別、和方法名,所以建立了輔助註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {
//對點選事件進行擴充套件
//設定監聽的方法
String listenerSetter();
//事件型別
Class<?> listenerType();
//事件被觸發後,執行回撥的方法名稱
String callBackMethod();
}
然後在建立點選事件的註解(這裡以單擊為例,別的點選方法我也會把程式碼展現的)
單擊事件
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class, callBackMethod = "onClick")
public @interface OnClick {
//那些控制元件的id,進行點選事件設定
int[] value();
}
實現事件註解,先獲取當前類的所有方法且遍歷,
然後獲取當前方法的註解
Annotation[] annotations = method.getAnnotations();
在判斷當前註解是否有我們設定的三要素,如果沒有直接進入下一迴圈
有的話直接得到三要素
String listenerSeter = eventBase.listenerSetter();
//得到 listenerType--》 View.OnClickListener.class,
Class<?> listenerType = eventBase.listenerType();
//callMethod--->onClick
String callBackMethod = eventBase.callBackMethod();
通過反射得到當前的方法,然後得到他們的控制元件陣列,再去遍歷,然後通過反射findViewById得到控制元件物件
如果物件不為空通過getMethod得到Method物件,然後通過動態代理得到點選事件的物件,然後呼叫。
Class<?> clazz = mContext.getClass();
//獲取Activity中所有的方法
Method[] methods = clazz.getDeclaredMethods();
//遍歷所有方法
for (Method method : methods) {
// 獲取方法上的所有註解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
//獲取註解 anntionType OnClick OnLongClck
Class<?> annotationType = annotation.annotationType();
//獲取註解的註解 onClick 註解上面的EventBase
EventBase eventBase = annotationType.getAnnotation(EventBase.class);
if (eventBase == null) {
continue;
}
/*
開始獲取事件三要素 通過反射注入進去
1 listenerSetter 返回 setOnClickListener字串
*/
String listenerSeter = eventBase.listenerSetter();
//得到 listenerType--》 View.OnClickListener.class,
Class<?> listenerType = eventBase.listenerType();
//callMethod--->onClick
String callBackMethod = eventBase.callBackMethod();
//方法名 與方法Method的對應關係
Map<String, Method> methodMap = new HashMap<>();
methodMap.put(callBackMethod, method);
try {
Method valueMethod = annotationType.getDeclaredMethod("value");
int[] viewIDS = (int[]) valueMethod.invoke(annotation);
for (int viewID : viewIDS) {
Method findViewById = clazz.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(mContext, viewID);
if (view == null) {
continue;
}
/*
listenerSetter setOnClickLitener
listenerType View.OnClickListener.class
*/
Method setOnClickListener = view.getClass().getMethod(listenerSeter, listenerType);
//使用動態代理實現listenerType介面,類似程式碼設定控制元件點選事件
ListenerInvocationHandler handler = new ListenerInvocationHandler(mContext, methodMap);
Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, handler);
setOnClickListener.invoke(view, proxy);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
listview的item點選事件
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter ="setOnItemClickListener"
,listenerType = AdapterView.OnItemClickListener.class,callBackMethod = "onItemClick")
public @interface OnItemClick {
int[] value();
}
長按事件
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnLongClickListener",
listenerType = View.OnLongClickListener.class,callBackMethod = "onLongClick")
public @interface OnLongClick {
int[] value() default -1;
}
動態代理方法
public class ListenerInvocationHandler implements InvocationHandler{
//activity 真實物件
private Context context;
private Map<String,Method> methodMap;
public ListenerInvocationHandler(Context context, Map<String, Method> methodMap) {
this.context = context;
this.methodMap = methodMap;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name=method.getName();
//決定是否需要進行代理
Method metf=methodMap.get(name);
if(metf!=null)
{
return metf.invoke(context,args);
}else
{
return method.invoke(proxy,args);
}
}
}
呼叫註解例項
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.test)
Button buttonTest;
@ViewInject(R.id.listView)
ListView listView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.inject(this);
listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
new String[]{"aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa"}));
}
@OnItemClick(R.id.listView)
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
Toast.makeText(MainActivity.this, "aaaaaaaaaaa" + position, Toast.LENGTH_SHORT).show();
}
@OnClick(R.id.test)
public void testClick(View view) {
Toast.makeText(MainActivity.this, "aaaaaaaaaaa", Toast.LENGTH_SHORT).show();
}
@OnLongClick({R.id.test2, R.id.test})
public boolean testLongClick(View view) {
Toast.makeText(MainActivity.this, "bbbbbbbbbbbbbbbbbbbbbbbbbb", Toast.LENGTH_SHORT).show();
return true;
}
}
我會在我的資源裡儲存原始碼