此类库只实现了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
共同学习,写下你的评论
评论加载中...
作者其他优质文章