此类库只实现了View的注解模块,实现布局加载注入,view注入,view监听事件注入;
通过此类库可以学习下反射的基本用法以及XUtils注解模块的实现;
一、实现功能
1.布局注入
2.View注入
3.View监听事件注入
二、实现思路
这三种实现的原理都是通过IOC思想来实现;
功能1和2通过反射获取到自定义的注解,解析注解,通过反射调用设置布局的setContentView和findViewById方法来实现;
功能3设置监听事件,除了需要使用反射获取信息外,还需要运用动态代理,来代理view对象,才能调用被我们自己写的事件注解的方法;
三、布局注入的具体实现
/**
* 布局注入方法
* @param object 布局所在的上下文
*/
public static void layoutInject(Object object) {
Class<?> aClass = object.getClass();
LayoutInject annotation = aClass.getAnnotation(LayoutInject.class); if (null != annotation) { //1.获取layout_id
int layoutId = annotation.value(); //2.反射调用setContentView(layoutId)
try { //不能使用getDeclaredMethod(这个方法只能获取本类中声明的方法)获取setContentView此方法
// 因为setContentView方法在父类中,使用getDeclaredMethod无法获得
Method setContentView = aClass.getMethod("setContentView", int.class);
setContentView.invoke(object, layoutId);
LogUtils.e("----- Layout Inject success -----");
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}从上面代码看,我们使用了一个LayoutInject的注解,此注解如下:
/**
* Created by serenitynanian on 2018/7/5.
* 用来表示---布局注入----的注解
*/@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface LayoutInject { int value();
}此注解使用时,只能在类上面使用,里面需要一个参数是布局的id,我们通过反射所传的对象,拿到此注解上的布局id,通过反射调用setContentView方法,即可实现;
四、View注入的具体实现
/**
* View注入
* @param object
* 实现findViewById
*/
public static void viewInject(Object object) {
Class<?> aClass = object.getClass();
Field[] fields = aClass.getDeclaredFields(); for (Field field : fields) {
ViewInject annotation = field.getAnnotation(ViewInject.class); if (null != annotation) { //1.view的id
int viewId = annotation.value(); //2.反射调用findViewById
try {
Method findViewById = aClass.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(object, viewId); //3.给此field赋值
field.setAccessible(true);
field.set(object, view);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}View的注入原理和布局注入的原理几乎一样,使用了一个自定义注解ViewInject:
/**
* Created by serenitynanian on 2018/7/5.
* View注入-----findViewById()
*/@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface ViewInject { int value();
}通过注解取得view控件的id,通过反射调用findViewById方法即可实现;
五、监听事件注入的具体实现
/**
* 监听事件监听注入
* @param object
*/
public static void listenerInject(Object object) {
Class<?> aClass = object.getClass();
Method[] methods = aClass.getDeclaredMethods(); for (Method method : methods) { //不能使用下面的原因,我们这个方法是为了实现不止onClickListen监听,而是为了实现全部的,做到可扩展,
//不能直接将要实现的监听固定写死;// OnClickInject annotation = method.getAnnotation(OnClickInject.class);
//1.获得此方法上所有的注解数组
Annotation[] annotations = method.getAnnotations(); if (null != annotations) { for (Annotation annotation : annotations) { //2.单独解析一个注解,看这个注解里面是否有ListenerBaseInfo注解
//得到注解类,解析这个注解类上的注解----比如:OnClickInject.class
Class<? extends Annotation> aClass1 = annotation.annotationType();
ListenerBaseInfo annotation1 = aClass1.getAnnotation(ListenerBaseInfo.class); if (null == annotation1) { continue;
} //3.走到这一步,说明annotation这个注解上面有ListenerBaseInfo注解,是被监听事件注解的方法
//下面得到被注解的监听事件的 基本信息
String setOnXXXListenerName = annotation1.setOnXXXListenerName();
Class<?> onXXXListenerType = annotation1.onXXXListenerType();
String onXXXMethodName = annotation1.onXXXMethodName(); //4.查找需要添加事件的view
try {
Method value = aClass1.getDeclaredMethod("value"); int[] viewIdArray = (int[]) value.invoke(annotation); for (int viewId : viewIdArray) {
Method findViewById = aClass.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(object, viewId); if (null != view) {
Method setOnXXXListenerNameMethod = view.getClass().getMethod(setOnXXXListenerName, onXXXListenerType); //5.使用动态代理 代理被我们注解的方法
ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(method, object,onXXXMethodName);
Object proxyObject = Proxy.newProxyInstance(object.getClass().getClassLoader(), new Class[]{onXXXListenerType}, listenerInvocationHandler);
setOnXXXListenerNameMethod.invoke(view, proxyObject);
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}// 如果使用下面的方法,只会调用个空方法 ,无法正常回调到被我们注解的方法// try {// method.invoke(object,onXXXListenerType.newInstance());// } catch (IllegalAccessException e) {// e.printStackTrace();// } catch (InvocationTargetException e) {// e.printStackTrace();// } catch (InstantiationException e) {// e.printStackTrace();// }
}
}
}
}事件监听大概有23种(可能更多),我们为了实现好的扩展性,不能将想要的监听事件固定在我们上面的代码中,这就需要我们动态获取想要实现的监听,因此我们需要一个注解ListenerBaseInfo来表明实现监听的基本信息;
ListenerBaseInfo.class注解/**
* Created by serenitynanian on 2018/7/5.
* 定义在注解上的注解
* 此注解来说明----->监听事件具备的基本信息
*
* button.setOnClickListener(new View.OnClickListener() {
* @Override
* public void onClick(View v) {
*
* }
* });
*
*/@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface ListenerBaseInfo { /**
* 设置监听事件的名字
* @return
*/
String setOnXXXListenerName(); /**
* 监听事件的类型
* @return
*/
Class<?> onXXXListenerType() ; /**
* 监听事件被回调的方法名
* @return
*/
String onXXXMethodName();
}ListenerBaseInfo注解来说明监听事件的基本信息,比如onClickListener事件,这个注解被用来注解其他监听事件的注解,来告知其他注解到底自己要实现哪种监听的;
被这个注解OnClickInject注解的方法:是用来表明view需要的onClickLister监听事件
/**
* Created by serenitynanian on 2018/7/5.
* 监听事件的注册
*/@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@ListenerBaseInfo(setOnXXXListenerName = "setOnClickListener",
onXXXListenerType = View.OnClickListener.class,
onXXXMethodName = "onClick")public @interface OnClickInject { int[] value();
}在事件监听注解的实现方法里,我们也是反射获取到被事件监听注解(OnClickInject)注解的方法,然后获取注解的类类型【 Class<? extends Annotation> aClass1 = annotation.annotationType() 】,再通过反射获取到此注解的注解(ListenerBaseInfo),拿到监听事件的基本信息;再通过上面得到的那个类类型,反射获取到value值,也就是需要绑定监听事件的viewId数组,通过这个数组,反射获取到view对象,通过动态代理的方式,将此view的对象的setOnXXXListener方法代理;
代理如下:
/**
* Created by serenitynanian on 2018/7/5.
* 动态代理----点击事件方法
*/public class ListenerInvocationHandler implements InvocationHandler { private String callbackMehtodName; private Method realMethod ; private Object object ; public ListenerInvocationHandler(Method realMethod, Object object,String callbackMehtodName) { this.realMethod = realMethod; this.object = object; this.callbackMehtodName = callbackMehtodName ;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals(callbackMehtodName)){ return realMethod.invoke(object,args);
} return null ;
}
}六、具体使用
public class BaseActivity extends AppCompatActivity { @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);
InjectUtils.setDebug(true);
//布局注入
InjectUtils.layoutInject(this); //view注入
InjectUtils.viewInject(this); //监听注入
InjectUtils.listenerInject(this);
}
}@LayoutInject(R.layout.activity_main)public class MainActivity extends BaseActivity { @ViewInject(R.id.bt_button) private Button button ; @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
Toast.makeText(this, "------->"+button.toString(), Toast.LENGTH_SHORT).show();
} @OnClickInject({R.id.bt_button}) public void onclickTest(View view) {
Toast.makeText(this, "-------点击了------", Toast.LENGTH_SHORT).show();
}
}
作者:Serenity那年
链接:https://www.jianshu.com/p/1ebc6e4e379a
共同学习,写下你的评论
评论加载中...
作者其他优质文章