Android--仿ButterKnife自动绑定View实例以及点击事件之一(主要利用反射技术)

前言

要读懂本文需要有自定义注解、反射以及Java动态代理的基础。代码里有注释,暂时不做过多的分析,后面有时间再补充。

 

一、定义注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) // 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Target(ElementType.FIELD)
public @interface BindView {

    /**
     * 传入View的id
     * @return
     */
    int value();

}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) // 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Target(ElementType.METHOD)
public @interface OnClick {

    /**
     * 传入View的id
     * @return
     */
    int[] value() default -1;

}

二、定义绑定工具类

import android.app.Activity;
import android.util.SparseArray;
import android.view.View;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 在运行期利用注解传入的id通过反射,初始化View的实例
 * <p>
 * 因为是在运行期使用了反射,效率会比较低
 */
public class ButterKnife {

    public static void bind(Activity activity) {
        // 存放添加了@BindView的View实例:key为View的id,value为@BindView的View实例
        SparseArray<View> viewMap = new SparseArray<>();
        Class<?> clzz = activity.getClass();
        // 1、扫描Activity里所有添加了@BinderView的成员(不包括父类的)
        Field[] fields = clzz.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(BindView.class)) {
                BindView bindViewAnnotation = field.getAnnotation(BindView.class);
                // 获取注解上的值(View Id)
                int viewId = bindViewAnnotation.value();
                // 根据View Id调用系统方法实例化成员
                View view = activity.findViewById(viewId);
                if (view != null) {
                    field.setAccessible(true);
                    try {
                        // 【重点】初始化View
                        field.set(activity, view);
                        viewMap.put(viewId, view);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        // 处理@OnClick的方法,通过使用Java的动态代理技术,为指定的View添加OnClickListener事件
        Method[] methods = clzz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(OnClick.class)) {
                OnClick onClickAnnotation = method.getAnnotation(OnClick.class);
                // 获取注解参数值
                int[] viewIds = onClickAnnotation.value();
                if (viewIds.length == 1 && viewIds[0] == View.NO_ID) {
                    // 如果没有传参数值,那么直接退出循环
                    break;
                }

                proxyOnClickListener(activity, method, viewIds, viewMap);
            }
        }
    }

    /**
     * 为View添加OnClickListener动态代理对象
     * 在系统调用OnClickListener.onClick()方法时,实际调用的是被@OnClick注解的方法
     *
     * @param target       添加了@OnClick注解方法的对象
     * @param targetMethod 添加了@OnClick注解的方法
     * @param viewIds      需要添加代理点击事件的View的id
     * @param viewMap      已缓存的View。如果在解析@BindView里有缓存的View,那么直接拿来用,避免重复调用findViewById()
     */
    private static void proxyOnClickListener(final Object target, final Method targetMethod,
                                             int[] viewIds, SparseArray<View> viewMap) {
        targetMethod.setAccessible(true);
        View.OnClickListener listener = (View.OnClickListener) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                new Class[]{View.OnClickListener.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return targetMethod.invoke(target, args);
                    }
                });

        for (int viewId : viewIds) {
            // 查一下是否有缓存
            View view = viewMap.get(viewId);
            if (view == null) {
                // 没有缓存就直接findViewById实例化
                view = ((Activity) target).findViewById(viewId);
            }
            view.setOnClickListener(listener);
        }
    }

}

三、写个测试的Activity

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.log.basedemo.R;
import com.log.basedemo.annoationProcessor.runtime.BindView;
import com.log.basedemo.annoationProcessor.runtime.ButterKnife;
import com.log.basedemo.annoationProcessor.runtime.OnClick;

/**
 * 分别测试:对View进行自动绑定初始化(findViewById),以及添加OnclickListener事件处理的两种不同时期实现:
 * 1、运行期
 * 2、编译期(ButterKnife原理)
 */
public class TestActivity extends AppCompatActivity {

    @BindView(R.id.textView)
    private TextView textView;
    @BindView(R.id.button)
    private Button button;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.butterknife_demo);

        ButterKnife.bind(this);
    }

    @OnClick(R.id.button)
    private void handleClick(View view) {
        Toast.makeText(this, "Button:" + button.toString(), Toast.LENGTH_SHORT).show();
        textView.setText("我是由运行时注解初始化,并被动态代理设置的点击事件设置的!接收点击事件的View Id:" + view.getId());
    }

}

运行效果图:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值