ButterKnife是JakeWharton大神开发的一个开源库,官方对这个库的介绍为:
Butter Knife
Field and method binding for Android views
ButterKnife是一个使用注解方式来为Android中的View视图绑定字段和方法,能通过自动解析注解来搜索资源文件并赋值给Activity中的字段,如使用@BindView,@BindColor替代原生的findViewById,getColor等方法,或者给View视图的监听器绑定方法,如使用@OnClick 替代 setOnClickListener等方法。ButterKnife通过注解方式为我们封装了很多原生操作,我们只需要编写少量的代码就能实现跟原生代码一样的操作,减少了开发者的很多工作,提高了工作效率。
ButterKnife是通过使用注解方式来自动生成模板代码,从而来将Activity中的字段和方法与View绑定在一起。目前ButterKnife支持如下的特性:
这样说很抽象,下面就使用代码来实践说明一下。
从外部引入ButterKnife库很简单,只需要在项目的App包中的build.gradle文件中的dependencies中加入依赖即可:
dependencies {
.....//省略
compile 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
}
最新版本的8.5.1,如需了解到最新的版本号可以参考Github:
https://github.com/JakeWharton/butterknife
我们一般在Activity中的onCreate中使用findViewById方式来引用布局文件中的View对象,而且还需要进行强制类型转换成所声明的变量的类型。当有多个View时,就需要调用多次findViewById方法,代码工作量大,而且也不简洁。ButterKnife使用@BindView(View的ID)注解字段来注解变量,这样ButterKnife就能自动引用布局中的View资源并能转换为所声明的相应的View类型。
@BindView(R.id.MainActivity_Btn)Button btn;
@BindView(R.id.MainActivity_Text)TextView text;
@BindView(R.id.MainActivity_RecyclerView)RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
注意还需要在onCreate方法调用ButterKnife.bind(this)来绑定Activity和ButterKnife。
以前一直以为ButterKnife是使用反射机制的,所以一直对ButterKnife的性能有点担忧。现在才了解到ButterKnife并不是使用反射机制的,而是解析注解字段来自动生成代码来执行查询和绑定的,说到底就是为我们封装了一些代码,减少我们的工作量。具体生成的代码文件可以查看app包中的build-generated-source-apt-debug-包名中的类文件,里面的类文件命名规则为调用ButterKnife的bind方法的class类加上”_ViewBinding”,具体如下:
点击MainActivity_ViewBinding文件进去查看源码,可以看到变量对应我们在MainActivity中声明的View变量一致:
在MainActivity_ViewBinding方法进行变量的初始化和监听绑定:
在其中的代码中可以看到 Utils.findOptionalViewAsType来引用布局中的View并进行类型转换的:
target.text = Utils.findOptionalViewAsType(source, R.id.MainActivity_Text, "field 'text'", TextView.class);
target.recyclerView = Utils.findRequiredViewAsType(source, R.id.MainActivity_RecyclerView, "field 'recyclerView'", RecyclerView.class);
对于监听器的绑定,则直接调用了对应的setOnXXXX方法,然后在监听器方法里面调用target即Activity里面的被ButterKnife注解的事件处理方法:
其中的部分源代码:
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.multiHandleClick(Utils.castParam(p0, "doClick", 0, "multiHandleClick", 0));
}
});
view = Utils.findRequiredView(source, R.id.MainActivity_EditText, "method 'onTextChanged', method 'beforeTextChanged', and method 'afterTextChanged'");
view2131427424 = view;
view2131427424TextWatcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence p0, int p1, int p2, int p3) {
target.onTextChanged(p0, p1, p2, p3);
}
@Override
public void beforeTextChanged(CharSequence p0, int p1, int p2, int p3) {
target.beforeTextChanged(p0, p1, p2, p3);
}
@Override
public void afterTextChanged(Editable p0) {
target.afterTextChanged(p0);
}
};
((TextView) view).addTextChangedListener(view2131427424TextWatcher);
从上面的分析中就可以总结出ButterKnife的原理了,简单来说就是ButterKnife框架解析注解字段,然后自动生成代码来引用View和为View绑定事件监听器。
除了使用@BindView来引用view,ButterKnife还对引用resource资源文件也提供了相应的支持。可以使用@BindBool,@BindColor,@BindDimen,@BindDrawable,@BindInt,@BindString等注解字段来引用预定义的resource资源。
如:
@BindString(R.string.app_name)String sub;
@BindColor(R.color.TextColor)int textColor;
这个资源绑定的原理也很简单,可以参考上面的分析,在MainActivity_ViewBinding文件中的MainActivity_ViewBinding方法中可以看到:
Context context = source.getContext();
Resources res = context.getResources();
target.textColor = ContextCompat.getColor(context, R.color.TextColor);
target.sub = res.getString(R.string.app_name);
除了可以在Activity中使用 ButterKnife来进行绑定,还可以在Fragment,RecyclerView的Adapter等任意对象上使用,前提是要提供这个对象上绑定的根视图View Root。
使用 ButterKnife.bind(Object,View);方法来绑定对象和ButterKnife,如下为Fragment中的使用:
public class MainFragment extends Fragment{
@BindView(R.id.MainFragment_Btn)Button btn;
@BindView(R.id.MainFragment_Text)TextView text;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view=inflater.inflate(R.layout.fragement_main,container,false);
ButterKnife.bind(this,view);
return view;
}
}
在Adapter中使用ButterKnife来简化ViewHolder模式:
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ListHolder> {
private ArrayList data;
public ListAdapter(ArrayList list){
this.data=list;
}
@Override
public ListHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ListHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item,parent,false));
}
@Override
public void onBindViewHolder(ListHolder holder, int position) {
holder.text.setText("Text: no"+data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
static class ListHolder extends RecyclerView.ViewHolder{
@BindView(R.id.MainActivity_ListItem_Btn)Button btn;
@BindView(R.id.MainActivity_ListItem_Text)TextView text;
public ListHolder(View itemView) {
super(itemView);
ButterKnife.bind(this,itemView);
}
}
}
对于多个有相同作用的View时,我们可以将这些View组合成一个List或者Array,然后只需调用一个方法就可以对这些View实行同一个操作。
使用@BindViews( { View.ID,View.ID,View.ID…} )方法来初始化,注意有花括号 { }:
@BindViews({R.id.MainActivity_FirstName,R.id.MainActivity_MiddleName,R.id.MainActivity_LastName})
List nameTexts;
对同一组中的View实行同一操作可以使用apply方法,首先我们来了解下两个重要的接口 Action和Setter,这两个接口可以指定对View的行为。在Activity中初始化这两个接口:
static final ButterKnife.Action DISABLE=new ButterKnife.Action() {
@Override
public void apply(@NonNull View view, int index) {
view.setEnabled(false);
}
};
static final ButterKnife.Setter ENABLE=new ButterKnife.Setter() {
@Override
public void set(@NonNull View view, Boolean value, int index) {
view.setEnabled(value);
}
};
static final ButterKnife.Setter ChangeColor=new ButterKnife.Setter() {
@Override
public void set(@NonNull TextView view, Integer value, int index) {
view.setTextColor(value);
}
};
可以看到Action和Setter方法的区别,Action接口只能获取到View对象,但是不能传递参数;而Setter接口使用了泛型,可以在实现接口的时候指定需要传递的数据类型,然后在其中的set方法的第二个参数中获取传递参数。
使用apply就可以执行这些定义中的操作了:
ButterKnife.apply(nameTexts,DISABLE);
ButterKnife.apply(nameTexts,ENABLE,false);
ButterKnife.apply(nameTexts,ChangeColor,textColor);
ButterKnife还允许在apply方法中直接使用Android中的属性,例如:
ButterKnife.apply(nameViews, View.ALPHA, 0.0f);
一般我们为View设置监听需要调用View.addOnXXXXXListener或者setOnXXXXXListener方法来绑定事件监听器,这样每次都要新建匿名内部类,而且有时还必须实现监听器接口中我们并不需要使用的事件方法。ButterKnife也对事件监听器绑定提供了支持,只需要使用相应的注解字段,如@OnClick,@OnTextChanged字段,来注解一个已声明的方法,然后ButterKnife就会自动将这个方法与事件监听器绑定在一起。
对于单个View的事件监听方法,方法参数可有可无:
@OnClick(R.id.MainActivity_Btn)
public void click(View view){
Log.e("ButterKnife","Click:"+view.getId());
}
@OnClick(R.id.MainActivity_Btn)
public void click(){
Log.e("ButterKnife","Click no arguments");
}
@OnClick(R.id.MainActivity_Btn)
public void click(Button button){//指定View类型,会被自动类型转换
Log.e("ButterKnife","button Click:"+button.getId());
}
我们也可以在OnClick方法参数中声明多个View.ID,然后就可以对这一组View绑定同一监听器来实现相同的事件处理:
@OnClick({R.id.MainActivity_FirstName,R.id.MainActivity_MiddleName,R.id.MainActivity_LastName})
public void multiHandleClick(TextView textView){
Log.e("ButterKnife","multiHandleClick:"+textView.getId());
}
在自定义View中,可以不指定View.ID来绑定监听器:
public class FancyButton extends Button {
@OnClick
public void onClick() {
// TODO do something!
}
}
当一个View的监听器中有多个事件处理函数时,如TextWatch监听器,我们可以指定其中的一个事件处理函数来绑定方法。ButterKnife为每一个方法注解字段都默认绑定了一个事件处理函数,我们可以使用callback 参数来为方法注解字段指定相应的事件处理函数。
例如@OnTextChanged方法注解字段默认绑定的是TextWatch中的onTextChanged事件处理函数,但我们可以使用callback = OnTextChanged.Callback.BEFORE_TEXT_CHANGED来为@OnTextChanged注解绑定beforeTextChanged事件处理函数,使用callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED来为@OnTextChanged注解绑定afterTextChanged事件处理函数:
@OnTextChanged(R.id.MainActivity_EditText)
public void onTextChanged(CharSequence s, int start, int before, int count){
Log.e("onTextChanged",String.valueOf(s));
}
@OnTextChanged(value = R.id.MainActivity_EditText,callback = OnTextChanged.Callback.BEFORE_TEXT_CHANGED)
public void beforeTextChanged(CharSequence s, int start, int count, int after){
Log.e("beforeTextChanged",String.valueOf(s));
}
@OnTextChanged(value = R.id.MainActivity_EditText,callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED)
public void afterTextChanged(Editable s){
Log.e("afterTextChanged",String.valueOf(s.toString()));
}
Fragment跟Activity一样,有自己对应的生命周期。在Fragment的onCreateView方法中绑定View,在onDestroyView将view置为null。当我们在onCreateView中调用ButterKnife.bind(this,view);时,这个方法会返回一个Unbinder实例对象,可以在适当的生命周期回调函数中调用Unbinder的unbind方法来解除View和ButterKnife的绑定,释放资源。
public class MainFragment extends Fragment{
@BindView(R.id.MainFragment_Btn)Button btn;
@BindView(R.id.MainFragment_Text)TextView text;
private Unbinder unbinder;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view=inflater.inflate(R.layout.fragement_main,container,false);
unbinder=ButterKnife.bind(this,view);
return view;
}
@Override
public void onDestroyView(){
super.onDestroyView();
unbinder.unbind();
}
}
当使用@Bind来引用View和资源或者绑定View事件处理函数时,如果目标view是null就会抛出异常。为了防止异常的发生和创建可选择性的绑定,在变量上使用@Nullable注解,在方法上使用@Optional注解。
@Nullable @BindView(R.id.MainActivity_Btn)Button btn;
@Nullable @BindView(R.id.MainActivity_Text)TextView text;
@Optional
@OnClick(R.id.MainActivity_Btn)
public void click(View view){
Log.e("ButterKnife","Click:"+view.getId());
}
除了使用@BindView来简化findViewById的使用,我们还可以使用ButterKnife.findById来简化在View,Activity或者Dialog中来引用View的代码。ButterKnife使用了泛型来推断返回的View类型并自动进行强制类型转换:
View view = LayoutInflater.from(context).inflate(R.layout.thing, null);
TextView firstName = ButterKnife.findById(view, R.id.first_name);
TextView lastName = ButterKnife.findById(view, R.id.last_name);
ImageView photo = ButterKnife.findById(view, R.id.photo);
参考:
http://jakewharton.github.io/butterknife/