自己动手实现一个简易的ButterKnife(简单快捷)

本文介绍如何通过自定义注解和反射技术在Android应用中实现View绑定及点击事件处理,有效减少findViewById的使用,简化代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      相信大家都使用过ButterKnife这个专注于Android的view注入的框架,这个框架用起来是非常的省心省力,去除了大量的"findViewById(resId);"操作,令我们写的代码简化了许多。
      其实我们也可以自己动手封装一个相似功能的简易框架,来实现view的绑定和点击事件的处理。

      首先我们需要运用到注解和反射的相关知识。

      1、定义一个自定义注解

       认识一下几个用到的注解:

  • @Target - 标记这个注解应该是哪种 Java 成员。
  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Target(ElementType.FIELD) //用于限制当前自定义注解的对象
//@Retention(RetentionPolicy.SOURCE)//只会在源码时期出现,当编译成字节码文件的时候就消失
//@Retention(RetentionPolicy.CLASS) //会加载到字节码文件中,但是当虚拟器加载该字节码文件时,注解信息会被清楚
@Retention(RetentionPolicy.RUNTIME) //一直会被保留到被加载到虚拟机中
public @interface ViewInject {

    //int id(); 这个地方抽象方法名的话我们可以自定义,但是如果我们自定义的话,就要在使用的时候多一些繁琐的步骤,
    // 比如: 我们采用int id();   我们就必须在activity中使用绑定时@ViewInject(id = R.id.iv_1)
    int value();//这里我们采用特殊的抽象方法名【 value]
 
}

好了,这个时候我们就完成了一个注解类,很简单就写了一个特殊的抽象方法。

 

     2.写一个工具类来对Activity做一个事件绑定

public class ViewUtils {
    public static void inject(Activity activity) {
        //绑定activity
        bindView(activity);
       
    }
}

在这里我们接受activity的参数,对他进行一些列的反射操作。

    3.绑定view

 private static void bindView(Activity activity) {
        /**
         *  1、首先获取到Activity的字节码文件
         */
        Class<?> clazz = activity.getClass();

        Field[] declaredFields = clazz.getDeclaredFields();

        /**
         * 2、遍历所有得到的字段,找出我们所需要的字段即可,即我们添加了自定义注解的字段
         */

        for (Field field : declaredFields) {
            //3、获取自定义字段上面的注解
            ViewInject viewInject = field.getAnnotation(ViewInject.class);

            if (viewInject != null) {
                /**
                 * 4、获得该注解的值
                 */
                int resId = viewInject.value();//本质上是资源文件的id

                /**
                 * 5、调用activity的findViewById方法, 将控件id传入,进行一个绑定,拿到该资源id的view
                 */
                View viewById = activity.findViewById(resId);

                /**
                 * 6、将当前的View设置给对应的field
                 */
                field.setAccessible(true);

                try {
                    field.set(activity, viewById);//参数1:object,参数2:value ,该方法为:给某某对象设置某个值,这里我们给activity设置好view对象
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

            } else {
                //该Activity上的字段无我们的自定义注解
            }
        }
    }

     这个地方我们可以简单梳理一下流程:1、拿到activity的字节码文件;2、遍历所有的字段,找到哪些字段是被我们添加了注解的;3、拿到我们的自定义注解;4、获取注解上的值(就是刚才定义的那个特殊的抽象方法value()返回的值,这个值就是资源的Id值)5、使用findviewById方法拿到view;6、将当前的View设置给对应的field。此时,view就绑定好。

4.使用注解在Activity中对View进行绑定 


    @ViewInject(R.id.tv_1)
    private TextView tv_1;

    @ViewInject(R.id.tv_2)
    private TextView tv2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewUtils.inject(this);

        //可以做一下测试看是否可以拿到值检测是否注入成功
//        Log.d("tv2_",tv2.getText()+"-----------");
//        Log.d("tv1_",tv_1.getText()+"-----------");
    }

  至此,view的绑定功能就完成了,接下来我们继续实现对点击事件的绑定。

  5.绑定点击事件

   与view的绑定流程几乎是一致的,首先也是定义一个自定义注解: 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnClick {
    int value();
}

 然后接受Activity对其进行一系列反射。

private static void bindMethod(final Activity activity) {
        /**
         * 1、获取字节码文件
         */
        Class<?> clazz = activity.getClass();

        /**
         * 2、获取所有的方法
         */
        Method[] declaredMethods = clazz.getDeclaredMethods();

        /**
         * 3、遍历所有的方法
         */
        for (final Method method : declaredMethods) {
            OnClick onClick = method.getAnnotation(OnClick.class);
            /**
             * 4、找到有我们自定义注解的方法
             */
            if (onClick != null) {
                int resId = onClick.value();
                /**
                 * 5、拿到View
                 */
                final View viewById = activity.findViewById(resId);

                viewById.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        method.setAccessible(true);
                        try {
                            /**
                             * 6、方法反射
                             */
                            method.invoke(activity, viewById);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                });
            } else {
                //若没有注解,则不用做任何处理
            }
        }
    }

    好了,接下来就是在Activity中做一下测试:

@OnClick(R.id.btn_test)
    private void useToast(View view){
        Toast.makeText(this, "tv_1"+String.valueOf(tv_1.getText())+"tv_2"+String.valueOf(tv2.getText()), Toast.LENGTH_SHORT).show();

    }

   6、总结

     用的知识点基本上就是注解和反射了,简简单单就可以把大量的“findViewByid”给省略了,这样也许会产生影响效率的问题,但是也可以忽略了,最后你也可以把功能单独抽取一下封装成jar包,就可以运用到自己的项目中了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值