Android UI IoC注解添加点击事件

Android UI IoC注解添加点击事件

知识点:注解 反射 AOP

接上篇 Android UI IoC注解加载控件

接下来实现类似ButterKnife点击事件

1. 业务Activity 和 OnClick注解类
@ContentView(R.layout.ioc_act)
public class IocAct extends BaseActivity {

    @InjectView(R.id.tv)
    private TextView tv;

    @InjectView(R.id.btn)
    private Button btn;

    @OnClick({R.id.btn, R.id.tv})
    public void click(View btn) {
        switch (btn.getId()) {
            case R.id.btn:
                Toast.makeText(this, "btn click", Toast.LENGTH_SHORT).show();
                break;
            case R.id.tv:
                Toast.makeText(this, tv.getText().toString(), Toast.LENGTH_SHORT).show();
                break;
        }
    }

   ......

}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class, callBackListener = "onClick")
public @interface OnClick {

    int[] value();
}
2. 业务ioc_act.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv"
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:layout_marginTop="30dp"
        android:layout_centerHorizontal="true"
        android:text="注解加载布局" />
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="40dp"
        android:text="Button" />
</RelativeLayout>
3. BaseActivity.java实现同上篇
4. InjectManager实现
public class InjectManager {

    public static void inject(Activity activity) {
        // 布局的注入
        injectLayout(activity);
        // 控件的注入
        injectViews(activity);
        // 事件的注入
        injectEvents(activity);
    }

    // 布局的注入
    private static void injectLayout(Activity activity) {
        ......
    }

    // 控件的注入
    private static void injectViews(Activity activity) {
        ......
    }

    // 事件的注入
    public static void injectEvents(Activity activity) {
        // 获取类
        Class<? extends Activity> clazz = activity.getClass();
        // 获取类的所有方法
        Method[] methods = clazz.getDeclaredMethods();
        // 遍历方法
        for (Method method : methods) {
            // 获取每个方法的注解(多个控件id)
            Annotation[] annotations = method.getAnnotations();
            // 遍历注解
            for (Annotation annotation : annotations) {
                // 获取注解上的注解
                // 获取OnClick注解上的注解类型
                Class<? extends Annotation> annotationType = annotation.annotationType();
                if (annotationType != null) {
                    // 通过EventBase指定获取
                    EventBase eventBase = annotationType.getAnnotation(EventBase.class);
                    if (eventBase != null) { // 有些方法没有EventBase注解
                        // 事件3大成员
                        String listenerSetter = eventBase.listenerSetter();
                        Class<?> listenerType = eventBase.listenerType();
                        String callBackListener = eventBase.callBackListener();

                        // 获取注解的值,执行方法再去获得注解的值
                        try {
                            // 通过annotationType获取onClick注解的value值
                            Method valueMethod = annotationType.getDeclaredMethod("value");
                            // 执行value方法获得注解的值
                            int[] viewIds = (int[]) valueMethod.invoke(annotation);

                            // 代理方式(3个成员组合)
                            // 拦截方法
                            // 得到监听的代理对象(新建代理单例、类的加载器,指定要代理的对象类的类型、class实例)
                            ListenerInvocationHandler handler = new ListenerInvocationHandler(activity);
                            // 添加到拦截列表里面
                            handler.addMethod(callBackListener, method);
                            // 监听对象的代理对象
                            // ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的
                            // Class[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
                            // InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法
                            Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(),
                                    new Class[]{listenerType}, handler);

                            // 遍历注解的值
                            for (int viewId : viewIds) {
                                // 获得当前activity的view(赋值)
                                View view = activity.findViewById(viewId);
                                // 获取指定的方法
                                Method setter = view.getClass().getMethod(listenerSetter, listenerType);
                                // 执行方法
                                setter.invoke(view, listener);
                            }
                        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}
5. EventBase是放在OnClick注解上的注解
@Target(ElementType.ANNOTATION_TYPE) // 放在注解的上面
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
    // 事件的三个成员
    // 1、set方法名
    String listenerSetter();
    // 2、监听的对象
    Class<?> listenerType();
    // 3、回调方法
    String callBackListener();
}
6. 最后还有一个代理方法 ListenerInvocationHandler
// 将回调的onClick方法拦截,执行我们自己自定义的方法(aop概念)
public class ListenerInvocationHandler implements InvocationHandler {

    // 需要拦截的对象
    private Object target;
    // 需要拦截的对象键值对
    private HashMap<String, Method> methodHashMap = new HashMap<>();

    public ListenerInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (target != null) {
            // 获取需要拦截的方法名
            String methodName = method.getName(); // 假如是onClick
            // 重新赋值,将拦截的方法换为show
            method = methodHashMap.get(methodName); // 执行拦截的方法,show
            if (method != null) {
                return method.invoke(target, args);
            }
        }
        return null;
    }

    /**
     * 将需要拦截的方法添加
     * @param methodName 需要拦截的方法,如:onClick()
     * @param method 执行拦截后的方法,如:show()
     */
    public void addMethod(String methodName, Method method) {
        methodHashMap.put(methodName, method);
    }
}

概括:

  1. 首先我们在IocAct.java中定义了OnClick注解 修饰click方法
  2. 然后在InjectManager中通过injectEvents遍历Activity的所有方法,首先找到1步注解修饰的方法(本例即:OnClick注解)
  3. 然后在判断所有方法的注解,是否在注解(OnClick)之上还有EventBase进行修饰
  4. 如果存在被EventBase修饰的注解,则通过click方法注解的值(这里值都对应Activity中View的id),找到对应的View,然后通过反射,设置View的事件监听(点击,长按等)
  5. 通过设置Proxy将指定viewId的点击事件代理(用自定义的方法)执行

整个过程就这样,看起来肯定不轻松,建议手敲代码加强一下理解就没问题了,毕竟ButterKnife的原理用这么点篇幅来讲是吧,共勉

你可能感兴趣的:(Android)