Android ButterKnife使用指北
前言
本文ButterKnife版本为8.8.1,使用Java语言
ButterKnife是一个编译时的注解框架,JakeWharton大神的杰作之一。ButterKnife专注于View以及相关的资源、一些事件等等,相当轻量级,使用的同时还不会影响代码执行效率(编译时的注解框架,在代码编译时生成新的.class文件)。
现在更多使用的的意义在于可以帮助我们生成代码,不用在重复的写
findViewById了,而且还能通过ButterKnife Zelezny插件来自动生成ButterKnife的注解代码。可以说,在开发中使用起来是相当的方便。
大神JakeWharton的说法:
Field and method binding for Android views which uses annotation processing to generate boilerplate code for you.
- Eliminate
findViewByIdcalls by using@BindViewon fields. - Group multiple views in a list or array. Operate on all of them at once with actions, setters, or properties.
- Eliminate anonymous inner-classes for listeners by annotating methods with
@OnClickand others. - Eliminate resource lookups by using resource annotations on fields.
大概意思是:
使用注解生成模板代码,让属性、方法与View绑定。
- 在属性上使用
@BindView消除findViewById的调用。 - 将多个View分组到列表或数组中。 使用操作,设置器或属性这些操作,一次操作所有的View。
- 通过使用
@OnClick和其他方法注解方法来消除侦听器的匿名内部类。 - 通过在字段上使用资源注解来消除资源查找。
这句话也是印象深刻啊!
Remember: A butter knife is like a dagger only infinitely less sharp.
配置
在AndroidStudio中使用ButterKnife还是很简单的,如果实在主项目中使用,只需要添加以下依赖就行
dependencies {
//ButterKnife
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}
如果使用Kotlin开发,把annotationProcessor换成kapt即可。
如果是在Library中使用,还需要额外添加plugin。首先在项目的build.gradle中添加如下代码:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.jakewharton:butterknife-gradle-plugin:8.8.1'
}
}
这里有一个问题需要注意,AndroidStudio 3.0及其以上版本对应的gradle与ButterKnife冲突,导致无法正常编译,github上也有这个问题,JakeWharton大神也给了相关解释,暂时的解决方法是将
ButterKnife版本降低至8.4.0。
然后在你的module中添加即可
apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'
PS:与主项目还有一点不同,在注解引用资源id的时候需要使用
R2文件,举个例子:@BindView(R2.id.user) EditText username;
使用
ButterKnife的初始化绑定
ButterKnife的初始化绑定也就是ButterKnife初始化入口
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化注册Activity
ButterKnife.bind(this);
}
}
ButterKnife.bind(this)就是ButterKnife的入口,绑定当前的Activity,在Activity中bind()方法必须在setContentView()之后调用,不然findViewById也找不到View啊!
当然,bind()方法还可以绑定其他的对象,基本包含了开发中的左右情况,如图:
butterknife_bind.png
可以看出来ButterKnife可以在Activity以外的类中使用,例如:自定义View、Dialog、Framgnet、ViewHolder;接下来看看在Fragment中如何绑定。
public class Main2ActivityFragment extends Fragment {
private Unbinder unbinder;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_main2, container, false);
//绑定Fragment以及View,并返回一个Unbinder对象
unbinder = ButterKnife.bind(this,root);
return root;
}
/** 在onDestroyView中使用Unbinder对象解除绑定 */
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}
由于Fragment的生命周期与Activity略有不同,在onCreateView中绑定Fragment,在onDestroyView中解除对Framgent的绑定。这里就是用ButterKnife.bind(this,root);返回的Unbinder对象,在Fragment的View销毁时解除绑定即可。
在Adapter中使用ButterKnife,有一些限制,因为注解的没法注解方法中的变量,所以在Adapter中使用需要配合ViewHolder一起使用,虽然在RecyclerView中已经强制使用ViewHolder了,但是如果使用ListView需要配合写ViewHolder类。这里以RecyclerView为例
public class MainAdapter<T> extends RecyclerView.Adapter<MainAdapter.ViewHolder> {
private Context context;
private List<T> data;
public MainAdapter(Context context, List<T> data) {
this.context = context;
this.data = data;
}
@NonNull
@Override
public MainAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(context).inflate(R.layout.item,viewGroup,false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
//etc...
}
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
static class ViewHolder extends RecyclerView.ViewHolder{
@BindView(R.id.name) TextView name;
ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}
绑定View
绑定View的关键注解就是@BindView和@BindViews,区别就在于一个还是多个view
@BindView(R.id.title) TextView title;
@BindView(R.id.subTitle) TextView subTitle;
@BindViews({R.id.text1, R.id.text2, R.id.text3}) TextView[] textArr;
@BindViews({R.id.text1, R.id.text2, R.id.text3}) List<TextView> textList;
@BindViews注解所生成的对象是View[]或者是List<View>
绑定事件
绑定view的各种事件监听,具体对应的作用如下:
| 注解名称 | 作用 |
|---|---|
| @OnCheckedChanged | 选中,选中取消,例如RadioGroup |
| @OnClick | 点击事件 |
| @OnEditorAction | 软键盘的功能按键 |
| @OnFocusChange | 焦点改变 |
| @OnItemClick | Item被点击事件 |
| @OnItemLongClick | item长按,返回真则可以拦截onItemClick |
| @OnItemSelected | Item被选择事件 |
| @OnLongClick | 长按事件 |
| @OnPageChange | 页面改变事件 |
| @OnTextChanged | EditText里面的文本变化事件 |
| @OnTouch | 触摸事件 |
接下来看看具体的使用方式
@OnClick
@OnClick注解可以绑定点击方法,参数就是View的id或者是View的id的数组
@OnClick(R.id.commit)
void commit(){
Log.i(TAG, "commit");
}
@OnClick(R.id.cannel)
void cannel(){
Log.i(TAG, "cannel");
}
or
@OnClick({R.id.commit, R.id.cannel})
void click(View v) {
switch (v.getId()) {
case R.id.commit:
Log.i(TAG, "commit");
break;
case R.id.cannel:
Log.i(TAG, "cannel");
break;
default:
break;
}
}
@OnClick在自定义view 中绑定自身的点击事件的话是不需要传递view的id的。
public class TestButton extends AppCompatButton {
public TestButton(Context context) {
super(context);
ButterKnife.bind(this);
}
public TestButton(Context context, AttributeSet attrs) {
super(context, attrs);
ButterKnife.bind(this);
}
@OnClick
public void onClick(){
//do something.
}
}
这里的示例代码ButterKnife.bind(this);在构造器调用`super()法之后,因为使用场景的比较简单。如果你在子View的布局里或者自定义view的构造方法里使用了inflate,你可以立刻调用此方法。或者,从XML inflate来的自定义view类型可以在onFinishInflate回调方法中使用它。
@OnLongClick
@OnLongClick注解的和@OnClick的使用方法相同,就不多做介绍了
@OnLongClick(R.id.delete)
boolean delete(){
Log.i(TAG, "delete");
return false;
}
@OnItemClick
这里所能绑定的item点击是AdapterView.OnItemClickListener的点击事件,所以只要是AdapterView的子类都是可以绑定这个事件的,例如:ListView、GridView、Spinner等等。
@OnItemClick(R.id.listView)
void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
//do something.
}
@OnItemLongClick
使用@OnItemLongClick绑定 item 长按点击和绑定 item 点击类似,也是遵循AdapterView的OnItemLongClickListener监听的
@OnItemLongClick(R.id.listView)
boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
//do something.
return false;
}
@OnItemSelected
使用@OnItemSelected可以绑定 item 的 Selected 事件,由于 item 的 Selected 事件监听有onItemSelected和onNothingSelected两个方法,注解使用的方式有些不同。
@OnItemSelected(R.id.listView)
void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
//do something.
}
@OnItemSelected(value = R.id.listView,callback = NOTHING_SELECTED)
void onNothingSelected(AdapterView<?> adapterView) {
//do something.
}
可以看到onItemSelected方法很简单,设置了id就可以了,但是onNothingSelected方法在注解中还添加了一个callback的参数,Callback是@OnItemSelected的一个枚举有ITEM_SELECTED和NOTHING_SELECTED两个枚举类型,用于区分onItemSelected和onNothingSelected两个方法的,而默认提供的ITEM_SELECTED类型,所以在为onItemSelected方法添加注解时不需要设置callback的值。
绑定View事件方面,还有
OnTouch、@OnCheckedChanged、@OnEditorAction、@OnFocusChange、@OnPageChange、@OnTextChanged这几个注解,使用方式基本相同,就不多做解释了。
绑定资源
绑定资源到类成员上可以使用@BindBool、@BindColor、@BindDimen、@BindDrawable、@BindInt、@BindString。使用时对应的注解需要传入对应的id资源,具体作用如下表
| 注解名称 | 作用 |
|---|---|
| @BindAnim | 绑定动画 |
| @BindArray | 绑定string中的数组 |
| @BindBitmap | 绑定bitmap资源 |
| @BindBool | 绑定boolean类型资源 |
| @BindColor | 绑定颜色 |
| @BindDimen | 绑定尺寸 |
| @BindDrawable | 绑定Drawable |
| @BindFloat | 绑定Float(这个还没用明白) |
| @BindFont | 绑定文字字体 |
| @BindInt | 绑定int类型数据 |
| @BindString | 绑定Sting类型数据 |
示例代码如下:
@BindAnim(R.anim.fade_in) Animation fadeIn;
@BindArray(R.array.strArr) String[] strArr;
@BindBitmap(R.mipmap.ic_launcher) Bitmap bitmap;
@BindBool(R.bool.test_bool) boolean testBoolean;
@BindColor(R.color.colorAccent) int colorAccent;
@BindDimen(R.dimen.round) int round;
@BindDrawable(R.drawable.ic_launcher) drawable;
@BindString(R.string.app_name) String meg;
@Optional
有一种情况,在使用绑定事件是,有可能提供的id没法找到targetView的情况,会报错。
java.lang.IllegalStateException: Required view 'delete' with ID 2131230776 for method 'delete' was not found. If this view is optional add '@Nullable' (fields) or '@Optional' (methods) annotation.
ButterKnife默认情况在绑定时间的targetView都不能为空,这时候就可以使用@Optional注解来标记的需要绑定的事件方法,让注入变成选择性的,如果targetView存在,则注入;不存在,则什么事情都不做。
@Optional注解是针对注解方法使用的,对于属性的话,可以使用android.support.annotation的@Nullable注解。
findById
ButterKnife还提供了静态方法findById,方便开发者使用,现在已经被标记为过期了
butterknife_findById.png
上图可以看到所提供的方法返回的是泛型,这样就不需要进行类型强转,但是在appcompat-v7:26.1.0以后的版本的 AppCompatActivity中提供的findViewById返回的也是继承View的泛型,也不需要进行类型强转了。
public <T extends View> T findViewById(@IdRes int id) {
return this.getDelegate().findViewById(id);
}
apply
apply方法能对View(或者View集合、数组)进行一些操作
- 定义一个显示的
Action,通过ButterKnife应用到targetView上。
static final ButterKnife.Action<View> SHOW = new ButterKnife.Action<View>() {
@Override
public void apply(View view, int index) {
view.setVisibility(View.VISIBLE);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
ButterKnife.apply(delete, SHOW);
}
- 定义一个是否显示的
Setter,通过ButterKnife应用到targetView上。
static final ButterKnife.Setter<View, Boolean> VISIBILITY = new ButterKnife.Setter<View, Boolean>() {
@Override
public void set(View view, Boolean value, int index) {
view.setVisibility(value ? View.VISIBLE : View.GONE);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
ButterKnife.apply(delete, VISIBILITY,true);
}
- 设置
View的Property
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
ButterKnife.apply(delete, View.ALPHA, 0.0f);
}
基本上可以理解
Action是对View的行为操作,Setter则是对View的设置操作
光这么说可能不明白,看看联想的出来的api方法。
butterknife_apply.png
一共有12个方法,其实只是对apply方法的重载;第一个参数就代表targetView,可以是View,View[]或者是List<View>;第二个参数可以是Action、Setter和Property,如果有第三个则是对Setter和Property设置的值。
/** An action that can be applied to a list of views. */
public interface Action<T extends View> {
/** Apply the action on the {@code view} which is at {@code index} in the list. */
@UiThread
void apply(@NonNull T view, int index);
}
/** A setter that can apply a value to a list of views. */
public interface Setter<T extends View, V> {
/** Set the {@code value} on the {@code view} which is at {@code index} in the list. */
@UiThread
void set(@NonNull T view, V value, int index);
}
在看看Action和Setter的源码的注释就明白了他们的作用了,Action就是应用一个行为到view上,Setter则是应用一值到view上。
以上就是ButterKnife使用方式,应该算是比较详细的使用教程了
Butterknife插件:zelezny
zelezny可以帮助我们快捷生成ButterKnife的注解代码,首先需要安装zelezny插件(如果已经安装请忽略安装过程)
- 打开AndroidStudio设置选中
Plugins选项 - 搜索zelezny
- 点击红框3
- 选择Android ButterKnife Zelezny
- 点击安装,安装完之后重启AndroidStudio即可
安装zelezny
安装完成之后,选中需要使用注解的layout id,右击点击Generate,选择Generate ButterKnife Injections。会出现以下弹框,可以选择需要注解的View,可以选择是否需要生成@OnClick注解,点击Confirm就会生成对应的注解代码。
这里也可以看到,zelezny能支持的绑定事件只有@OnClick,但是还是能帮助我们节省时间的。
PS:如果觉得ButterKnife是麻烦的话,可以安装ButterKnifeKiller插件,该插件可以相应的把注解代码替换成findViewById的代码。













网友评论