上一篇博主已经带大家简单的了解了IoC,实现了Activity中View的布局以及控件的注入,如果你不了解,请参考:浅谈android ioc控制反转一(控件篇)
本篇文章主要讲解事件注入
目标效果(继上篇文章代码下)
package shopping.com.androidioc; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.TextView; import android.widget.Toast; @SetContentView(R.layout.activity_main) public class MainActivity extends AppCompatActivity { @ViewBind(R.id.tv) private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ViewInjectUtils.inject(this); } @OnClick({R.id.tv,R.id.btn}) public void click(View view) { switch (view.getId()) { case R.id.tv: Toast.makeText(MainActivity.this,"我是textview被点击了",Toast.LENGTH_LONG).show(); break; case R.id.btn: Toast.makeText(MainActivity.this,"我btn被点击了",Toast.LENGTH_LONG).show(); break; } } }
从上面代码可以看出事件绑定的关键代码是在 @OnClick({R.id.tv,R.id.btn}) 这个注解中
好,下面先来看下OnClick注解类
package shopping.com.androidioc; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * <p>描述:<p> * * @author guoweiquan * @version 1.0 * @data 2018/5/15 上午10:41 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface OnClick { int[] value(); }
这个类和之前的注解不同的是 @Target(ElementType.METHOD) ,ElementType.METHOD意思是一个方法注解。
然后还是来看核心实现类ViewInjectUtils:
package shopping.com.androidioc; import android.app.Activity; import android.view.View; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * <p>描述:<p> * * @author guoweiquan * @version 1.0 * @data 2018/5/14 下午2:19 */ public class ViewInjectUtils { private static final String SETCONTENTVIEW = "setContentView"; private static final String FINDVIEWBYID = "findViewById"; private static final String EVENTMETHOD = "setOnClickListener"; private static final Class EVENTTYPE = View.OnClickListener.class; private static final String METHODNAME = "onClick"; public static void inject(Activity activity) { bindContentView(activity); bindViews(activity); bindEvents(activity); } /** * 绑定主布局文件 * * @param activity */ private static void bindContentView(Activity activity) { Class<? extends Activity> clazz = activity.getClass(); SetContentView contentView = clazz.getAnnotation(SetContentView.class);// 查询类上SetContentView注解 if (contentView != null) { int contentViewLayoutId = contentView.value(); try { Method method = clazz.getMethod(SETCONTENTVIEW, int.class); method.setAccessible(true);//设置可以访问private域 method.invoke(activity, contentViewLayoutId); } catch (Exception e) { e.printStackTrace(); } } } /** * 绑定所有的控件 * * @param activity */ private static void bindViews(Activity activity) { Class<? extends Activity> clazz = activity.getClass(); Field[] fields = clazz.getDeclaredFields();//获取自己声明的各种字段,包括public,protected,private for (Field field : fields) { ViewBind viewInjectAnnotation = field .getAnnotation(ViewBind.class); if (viewInjectAnnotation != null) { int viewId = viewInjectAnnotation.value(); if (viewId != -1) { try { Method method = clazz.getMethod(FINDVIEWBYID, int.class); Object view = method.invoke(activity, viewId);//找到相应控件对象 field.setAccessible(true); field.set(activity, view);//给本字段赋值 } catch (Exception e) { e.printStackTrace(); } } } } } /** * 绑定所有的事件 * * @param activity */ private static void bindEvents(Activity activity) { Class<? extends Activity> clazz = activity.getClass(); Method[] methods = clazz.getMethods(); //遍历所有的方法 for (Method method : methods) { //拿到方法上的所有的注解 Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations) { //获取注解类型 OnClick Class<? extends Annotation> annotationType = annotation .annotationType(); //判断是否为OnClick注解 if(annotationType.getName().equals(OnClick.class.getName())) { try { //拿到Onclick注解中的value方法 Method method1 = annotationType .getDeclaredMethod("value"); //取出所有的viewId int[] viewIds = (int[]) method1.invoke(annotation); //设置动态代理 DynamicHandler handler = new DynamicHandler(activity); handler.addMethod(METHODNAME, method);//添加onClick方法 Object listener = Proxy.newProxyInstance( EVENTTYPE.getClassLoader(), new Class<?>[] { EVENTTYPE }, handler); // BindListener listener = new BindListener(activity); // listener.setmMethod(method); //遍历所有的View,设置事件 for (int viewId : viewIds) { View view = activity.findViewById(viewId); Method setEventListenerMethod = view.getClass().getMethod(EVENTMETHOD, EVENTTYPE); setEventListenerMethod.invoke(view, listener); } } catch (Exception e) { e.printStackTrace(); } } } } } }
事件绑定的方法是bindEvents。这个方法主要就是遍历activity所有的方法,过滤拿到注释为OnClick的方法,得到事件监听的需要调用的方法名,通过Proxy和InvocationHandler得到监听器的代理对象,显示设置了方法,最后通过反射设置监听器。
这里有个难点,就是关于DynamicHandler和Proxy的出现,如果不理解没事,注释掉动态代理这一段,打开BindListener这段好了实现效果一样。
DynamicHandler代码如下:
package shopping.com.androidioc; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.HashMap; /** * <p>描述:<p> * * @author guoweiquan * @version 1.0 * @data 2018/5/15 上午11:28 */ public class DynamicHandler implements InvocationHandler { private Object handler; private final HashMap<String, Method> methodMap = new HashMap<String, Method>(); public DynamicHandler(Object handler) { this.handler = handler; } public void addMethod(String name, Method method) { methodMap.put(name, method); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (handler != null) { String methodName = method.getName(); method = methodMap.get(methodName); if (method != null) { return method.invoke(handler, args); } } return null; } }
好了到这里我们已经实现了事件绑定效果:
项目地址:https://github.com/seaeel/AndroidIoc.git
博主技术qq交流群:239025382