架构师学习--IOC方式实现布局、控件、事件依赖

项目中通过findViewById方式来初始化控件显得代码很臃肿,并且类之间的耦合度很高。为了解决这个问题,有两种方式可以实现:

  • IOC:反射+注解方式,运行时处理
  • APT:比如butterKnife,编译期处理

下面将用IOC的方式实现布局控件的运行时注解。

一、布局注解

1、创建布局注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LayoutInject {
    int value();
}

注解在类之上,value值就是R.layout.xxx。

2、解析注解

    /**
     * 注册布局
     */
    private static void injectLayout(Object object) {
        Class<?> activityClass = object.getClass();
        LayoutInject layoutAnnotation = activityClass.getAnnotation(LayoutInject.class);

        if(layoutAnnotation != null){

            //拿到布局id
            int layoutId = layoutAnnotation.value();

            try {

                //调用setContentView(layoutId)
                Method setContentViewMethod = activityClass.getMethod("setContentView", int.class);
                setContentViewMethod.setAccessible(true);

                //反射
                setContentViewMethod.invoke(object,layoutId);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

首先拿到布局id,然后通过反射调用setContentView()方法,将布局填充进去。

二、控件注解

1、创建控件注解类
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
    int value();
}

注解在属性之上,value值就是R.id.xxx。

2、解析注解
/**
     * 注册控件
     */
    private static void injectViews(Object object) {

        Class<?> activityClass = object.getClass();

        //所有的字段属性
        Field[] allFields = activityClass.getDeclaredFields();

        //1、遍历找到ViewInject注解的属性
        for (Field field : allFields) {

            ViewInject viewAnnotation = field.getAnnotation(ViewInject.class);

            if(viewAnnotation == null){
                continue;
            }

            //2、拿到注解的值
            int viewId = viewAnnotation.value();

            try {
                //3、调用findViewById(viewId)
                Method findViewByIdMethod = activityClass.getMethod("findViewById", int.class);
                findViewByIdMethod.setAccessible(true);

                //4、反射得到view
                Object view = findViewByIdMethod.invoke(object, viewId);

                //5、重新赋值activity中的view
                field.setAccessible(true);
                field.set(object,view);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

首先需要遍历所有的属性,然后拿到符合条件的属性注解,通过反射调用findViewById()方法,拿到当前控件,最重要的就是需要将拿到的控件重新赋值给activity中的控件。

三、事件注解

控件的事件包括点击事件、长按事件、拖拽事件等。如果针对每一个事件都进行单独的设置,代码会冗余。所以有没有一种通用的形式?那么就需要了解事件的共同点:

  • 事件名称:比如 setOnClickListener
  • 事件的类:比如 View.OnClickListener
  • 事件的回调方法名:比如 onClick
    找到了共同点,就可以展开下面的工作了。
1、创建事件三部曲注解类
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnBaseClick {

    /**
     * 设置事件的名称 比如 setOnClickListener
     */
    String clickName();

    /**
     * 设置事件类 比如 View.OnClickListener
     */
    Class clickClass();

    /**
     * 设置事件的回调 比如 onClick
     */
    String clickCallback();
}

注意它是注解在注解之上的。

2、创建点击事件注解类(onClick事件)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnBaseClick(
        clickName = "setOnClickListener",
        clickClass = View.OnClickListener.class,
        clickCallback = "onClick")
public @interface OnClick {
    int value();
}

这里使用到三部曲的注解类,并把当前事件的具体信息传递过去。

3、创建长按事件注解类(onLongClick事件)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnBaseClick(
        clickName = "setOnLongClickListener",
        clickClass = View.OnLongClickListener.class,
        clickCallback = "onLongClick")
public @interface OnLongClick {
    int value();
}

同样使用到三部曲的注解类。

	/**
     * 注册事件
     */
    private static void injectEvents(final Object object) {

        Class activityClass = object.getClass();

        Method[] allMethods = activityClass.getDeclaredMethods();

        for (final Method method : allMethods) {

            //拿到方法上所有的注解
            Annotation[] annotations = method.getAnnotations();

            for (Annotation methodAnnotation : annotations) {

                Class<? extends Annotation> aClass = methodAnnotation.annotationType();

                //拿到注解上的注解
                OnBaseClick annotation = aClass.getAnnotation(OnBaseClick.class);

                if(annotation == null){
                    continue;
                }

                //拿到事件的方法名
                String clickName = annotation.clickName();

                //拿到事件的类
                Class clickClass = annotation.clickClass();

                //拿到事件的回调方法
                final String clickCallback = annotation.clickCallback();

                try {
                    //拿到控件id,通过反射
                    Method valueMethod = aClass.getDeclaredMethod("value");
                    valueMethod.setAccessible(true);

                    int viewId = (int) valueMethod.invoke(methodAnnotation);

                    //拿到控件
                    Method findViewByIdMethod = activityClass.getMethod("findViewById", int.class);
                    findViewByIdMethod.setAccessible(true);

                    //拿到view
                    Object view = findViewByIdMethod.invoke(object, viewId);

                    //给view设置事件
                    final Method eventMethod = view.getClass().getMethod(clickName, clickClass);

                    eventMethod.setAccessible(true);

                    //使用动态代理拦截事件
                    Object proxy = Proxy.newProxyInstance(clickClass.getClassLoader(), new Class[]{clickClass}, new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method2, Object[] args) throws Throwable {
                            //执行activity的方法
                            return method.invoke(object,null);
                        }
                    });

                    eventMethod.invoke(view,proxy);

                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
    }

以上代码,步骤如下:
(1)遍历当前activity的所有方法(allMethods )
(2)遍历每个方法所有的注解(allMethods )
(3)找到当前注解的类型,符合OnBaseClick注解的满足条件
(4)拿到注解的值:事件名称、事件类、事件回调方法(clickName ,clickClass ,clickCallback )
(5)反射得到当前控件(view )
(6)反射得到当前控件的事件方法(eventMethod)
(7)如果需要事件执行,需要通过动态代理的方式,拦截事件。执行activity的事件方法(proxy )
(8)设置动态代理,反射执行

四、使用

@LayoutInject(R.layout.activity_butterknife)
public class ButterKnifeActivity extends AppCompatActivity {

    @ViewInject(R.id.btn1)
    Button btn1;

    @ViewInject(R.id.btn2)
    Button btn2;

    TextView textView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ViewInjector.injectActivity(this);



    }

    @OnClick(R.id.btn1)
    public void clickEvent(){
        Toast.makeText(this,"点击事件",Toast.LENGTH_SHORT).show();
    }

    @OnLongClick(R.id.btn2)
    public boolean longClickEvent(){
        Toast.makeText(this,"长按事件",Toast.LENGTH_SHORT).show();
        return false;
    }
}

onCreate()方法中调用ViewInjector.injectActivity()完成注解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值