一、首先的说一下注解的分类,
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;
}
}
我会在我的资源里保存源码