Android-自己动手写ButterKnife与原理解析

前言

项目做的多了,代码/布局也写的多了,相信大家都对EditText editText= (EditText) findViewById(R.id.edit_text); 这种代码非常熟悉,一个Activity里写个initView()方法,然后里面全是这种findViewById操作,相信大家也都写的烦了吧!正所谓不知道偷懒的程序员不是好程序员,这不,针对这种问题,国外大神已经研究出了ButterKnife(黄油刀)这种东西,通过对 成员变量注解 的方式,省下一大堆find操作,代码也简洁了许多,关键是再也不用手写find了,特别是(EditText) 强转这一步,真是强转到吐了有么有。。。好吧!本文并不是教你怎么用这个东西,毕竟很简单,而且文章也很多,本文重点分析一下它的原理,和我们自己来实现一个Simple版的ButterKnife。

首先看findViewById()这个方法,这个方法是view的方法,activity里的也是代理了一层,里面也是通过activity的rootView进行find操作,我们想,如果能不强转,直接返回我们想要的View子类型,那也不错,毕竟我们其实也不想写强转代码,怎么实现呢,看下面代码:

public <V extends View> V findView(@IdRes int id){
        return (V) findViewById(id);
}

用法:

 EditText editText=findView(R.id.edit_text);
 TextView textView=findView(R.id.text_view);

这样的写法是不是好多了呢?这里主要就是泛型了,指定返回类型V为view的子类,这样我们就不用强转了。。(如果对泛型有不熟悉的小伙伴,下篇会出一篇专门讲泛型的文章。)这段代码大家可以复制到你们的BaseActivity中,在BaseFragment中可以加这句:

 public <V extends View> V findView(@IdRes int id){
        return (V) getView().findViewById(id);
 }

getView()就是你在Fragment —onCreateView返回的view,你就可以在onViewCreated()方法中进行findView操作了。
接下来再来看看通过注解的方式:

public class MainActivity extends BaseActivity {

    @BindView(R.id.text_view)
    private TextView mTextView;

    @BindView(R.id.button)
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //解释注解
        ButterKnife.bindView(this);
        mTextView.setText("TextView from ButterKnife.bindView(Activity activity)");

    }

当你setContentView()之后,调用ButterKnife.bindView(this); 就会对含有@BindView注解的成员变量进行赋值,之后你就可以对变量进行操作了。。。用法就是这么的简单,是不是瞬间简洁明了了。。我们先来看一这个BindView 注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int DEFAULT_ID = -1;

    @IdRes int value() default DEFAULT_ID;
}

新建一个类,把这段复制进去 就行了,@interface表明这是一个注解,@Target() 声明这个注解可以写在什么地方上,ElementType.FIELD 声明这个注解可以写在成员变量耻,还有一些值:

ANNOTATION_TYPE, 对注解的注解
CONSTRUCTOR, 构造函数注解
METHOD, 方法
……

value()是默认方法,一般是int id() default 0; 相当定义一个变量,然后写的时候就是@BindView(id = 1),value()比较特殊,用的时候可以不用写value=xxx(当你只有一个值的时候),好吧,这不是重点,不懂的百度java 注解吧!
继续看ButterKnife类,重点的类:

/**
 * Created by wzh on 2016/8/11.
 * 解释 BindView 注解的类
 */
public class ButterKnife {

    /**
     * 在 setContentView(R.layout.activity_main); 之后调用
     * @param activity 含有BindView 注解的activity
     */
    public static void bindView(@NonNull Activity activity){
        View rootView=activity.findViewById(android.R.id.content);
        bindView(activity,rootView);
    }

    /**
     * 在 onViewCreated(view , savedInstanceState){
     *     ButterKnife.bindView(this);
     * } 之中调用最佳
     * @param fragment 含有BindView 注解的fragment
     */
    public static void bindView(@NonNull Fragment fragment){
        View rootView=fragment.getView();
        if(rootView==null){
            throw new NullPointerException(fragment+" rootView is Null!" +
                    " please call bindView(fragment) in onViewCreated(xxx)");
        }
        bindView(fragment,rootView);
    }

    /**
     * 建议 在 addView(view) 之后调用
     * @param customView 含有BindView 注解的自定义view等
     */
    public static void bindView(@NonNull View customView){
        bindView(customView,customView);
    }

    /**
     * 对 含有BindView 注解的成员变量进行findView操作,此为关键方法。
     * @param target 注解所处的对象,可为任意对象
     * @param parent xml 所构造的根view
     */
    public static void bindView(@NonNull Object target,@NonNull View parent){
        Field[] fields=target.getClass().getDeclaredFields();
        for(Field field : fields){
            if(field.isAnnotationPresent(BindView.class)){
                BindView bindView=field.getAnnotation(BindView.class);
                int id=bindView.value();
                if(id != BindView.DEFAULT_ID){
                    field.setAccessible(true);
                    try {
                        field.set(target,findView(parent,id));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }


    /**
     * ListView listView=ButterKnife.findView(root,R.id.list_view);
     * @param parent 此view 的父级view,一般为rootView
     * @param id id ,R.id.xxx
     * @param <V> 返回指定view
     * @return
     */
    public static <V extends View> V findView(@NonNull View parent, @IdRes int id){
        return (V) parent.findViewById(id);
    }
}

可以看到,四个重载bindView方法,可以在activity/Fragment/和自定义view中使用,当然可能还有其它情况,可以用最后 的bindView(@NonNull Object target,@NonNull View parent),相信能解决你的问题。前面的方法,只是找到当前的rootView,也是最根部的view。

View rootView=activity.findViewById(android.R.id.content);
就是activity中的最根部view,也就是最外面的viewGroup。
然后进入bindView(Object ,View )方法。

1、先获取当前类的所有成员:Field[] fields=target.getClass().getDeclaredFields();
2、再遍历该变量有没有我们的BindView注解,field.isAnnotationPresent(BindView.class)
3、有的话就拿到BindView对象,再等到value值,也就是R.id.text_view的int 值。

BindView bindView=field.getAnnotation(BindView.class);
 int id=bindView.value();

4、最后再通过parentView和Id就可精确find到该view了,再赋值给该变量,完成findView操作。

field.setAccessible(true);
try {
      field.set(target,findView(parent,id));
} catch (IllegalAccessException e) {
      e.printStackTrace();
}

可以看到,原理就是这简单,想办法得到 rootView,再加上id就可以找到该view,再用反射给变量赋值就可以了。。。目前该项目已上传到github,附上地址:SimpleButterKnife 如果有扩展功能,也可以添加,现在我们已经完成了最主要的bindView功能,像其它什么bindString,bindDrawable。。。。我们已经知道了原理。剩下什么的还不都是小事嘛!!(JakeWharton 的Butterknife并没有这么简单,功能比较强大,但是还需要一个插件编译来使用,否则无效果)

  上面的public <V extends View> V findView(@IdRes int id) 还是可以写到BaseActivity里的,因为有些地方你并不想要定义成员变量,省去强转也是不错的。还可以写一个android studio插件来自动通过xml生成这些代码,不过这个我不会写呀。。。如果使用过程中发现有使用问题,欢迎大家留言探讨。。。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值