Leo's Blog

ButterKnife 框架使用详解

前言:

findViewById 是每一个 Android 开发者的必经之路,同样也是噩梦。为了提高开发效率,准备在项目中使用 ButterKnife(PS:感谢JakeWharton大神),本文基于8.5.1 版本官方文档翻译,不同的版本在用法上可能会差异,实际使用时,请参考官网文档。
Github 地址
官方文档

ButterKnife 介绍

  • Android 开发者的福音,专为 Android View 设计的绑定注解,专治 findViewById。
  • 配合 AndroidStudio 插件使用,一键生成 view field。。。爽歪歪
  • 还有其他很多有用的注解。。。

ButterKnife引入

Download

1
2
3
4
dependencies {
compile 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
}

Library Module中使用

###在 Module 的 build.gradle 添加如下配置:

1
2
3
4
5
6
7
8
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'
}
}
1
2
apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'

使用

使用 R2 代替 R

1
2
3
4
class ExampleActivity extends Activity{
@BindView(R2.id.user) EditText username;
@BindView(R2.id.pwd) EditText pwd;
}

控件绑定

用 @BindView 注解,并注明view id,ButterKnife 会自动查找控件,并转换成layout文件中指定的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ExampleActivity extends Activity{
@BindView(R.id.title)
TextView title;
@BindView(R.id.subtitle)
TextView subtitle;
@BindView(R.id.footer)
TextView footer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}

资源绑定

@BindArray, @BindBitmap, @BindBool, @BindColor, @BindDimen, @BindDrawable, @BindFloat, @BindInt, @BindString

1
2
3
4
5
6
7
class ExampleActivity extends Activity {
@BindString(R.string.title) String title;
@BindDrawable(R.drawable.graphic) Drawable graphic;
@BindColor(R.color.red) int red; // int or ColorStateList field
@BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value) field
// ...
}

非Activity绑定

ButterKnife 提供了 bind 的几个重载,只要传入根布局,便可以在任何对象中使用注解绑定。

例如在 Fragment 中:

1
2
3
4
5
6
7
8
9
10
11
12
public class FancyFragment extends Fragment {
@BindView(R.id.button1) Button button1;
@BindView(R.id.button2) Button button2;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
}

还有一种比较常见的场景,是 adapter 中的 ViewHolder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyAdapter extends BaseAdapter{
@Override
public View getView(int position, View view, ViewGroup parent){
ViewHolder holder;
if(view != null){
holder = (ViewHolder) view.getTag();
}else{
view = inflater.inflate(R.layout.whatever,parent,false);
holder = new ViewHolder(view);
view.setTag(holder);
}
holder.name.setText("John Doe");
// etc...
}
return view;
}

1
2
3
4
5
6
7
8
9
10
static class ViewHolder{
@BindView(R.id.title)
TextView name;
@BindViewe(R.id.job_title)
TextView jobTitle;
public ViewHolder(View view){
ButterKnife.bind(this,view)
}
}

ButterKnife 可以在任何调用 fingViewById 的地方使用。

提供的其他绑定 API :

  • 使用Activity作为跟布局在任意对象中进行绑定。如果你使用了类似MVC的编程模式,你可以对controller使用它的Activity用ButterKnife.bind(this, activity)进行绑定。

  • 使用ButterKnife.bind(this)绑定一个布局的子布局。如果你在布局中使用了标签并且在自定义的控件构造时inflate这个布局,你可以在inflate之后立即调用它。或者,你可以在onFinishInflate()回调中使用它。

View Lists

一次性将多个 Views 绑定到一个 List 或数组中

1
2
@BindViews({R.id.first_name,R.id.middle_name,R.id.last_name})
List<EditText> nameViews;

我们可以通过 apply 函数在列表中的所有view上执行一个动作。

1
2
ButterKnife.apply(nameViews,DISABLE);
ButterKnife.apply(nameViews,ENABLED,false);

ActionSetter 接口指定一些动作

1
2
3
4
5
6
7
8
9
10
11
12
static final ButterKnife.Action<View> DISABLE = new ButterKnife.Action<View>(){
@Override
public void apply(View view, int index){
view.setEnabled(false);
}
};
static final ButterKnife.Setter<View,Boolean> ENABLED = new ButterKnife.Setter<View,Boolean>(){
@Override
public void set(View view,Boolean value,int index){
view.setEnabled(value);
}
};

Android 中的 Property 属性也可以使用 apply 方法进行设置:

1
ButterKnife.apply(nameViews,View.ALPHA,0.0f);

监听器绑定

事件绑定示例

监听器可以自动绑定到指定的方法上

1
2
3
4
@OnClick(R.id.submit)
public void submit(View view){
// TODO something
}

监听器方法的参数是可选的

1
2
3
4
@OnClick(R.id.submit)
public void submit(){
// TODO something
}

指定一个特定的类型,ButterKnife 会将它自动转换

1
2
3
4
@OnClick(R.id.submit)
public void sayHi(Button button){
button.setText("Hello!");
}

在一个方法上指定多个 view id ,多个 view 共用一个处理方法。

1
2
3
4
5
6
7
8
@OnClick({R.id.door1,R.id.door2,R.id.door3})
public void pickDoor(DoorView door){
if(door.hasPrizeBehind()){
Toast.makeText(this,"You win!",LENGTH_SHORT).show();
}else{
Toast.makeText(this,"Try again",LENGTH_SHORT).show();
}
}

自定义 view 绑定事件监听不需要指定ID

1
2
3
4
5
6
public class FancyButton extends Button{
@OnClick
public void onClick(){
// todo something!
}
}

ButterKnife 事件监听注解

屏幕快照 2017-05-04 10.46.33.png-26.3kB

可以看到,我们常用的事件,ButterKniffe 都提供了注解。

多个方法的监听绑定

当一个监听器包含多个回调函数时,使用函数的注解能够对其中任何一个函数进行绑定。每一个注解都会绑定到一个默认的回调。你也可以使用callback参数来指定一个其他函数作为回调。

屏幕快照 2017-05-04 15.01.15.png-142kB

1
2
3
4
5
6
7
8
9
@OnItemSelected(R.id.list_view)
void onItemSelected(int position){
// TODO
}
@OnItemSelected(value = R.id.maybe_missing,callback = NOTHING_SELSECTED)
void onNothingSelected(){
// TODO
}

解除绑定

Fragment 的生命周期和Activity是不同的。如果在 onCreateView 中绑定了 Fragment,我们应该在 onDestoryView 方法中解除绑定。ButterKnife 返回了 Unbinder 实例来完成这个操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FancyFragment extends Fragment{
@BindView(R.id.button1) Button button1;
@BindView(R.id.button2) Button button2;
private Unbinder unbinder;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
unbinder = ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}

可选绑定

默认情况下, @Bind 和 监听绑定是必须得,如果目标view没有找到的话,ButterKnife会抛出异常。
我们可以通过 @Nullable 标注字段或者 @Optional 标注方法,防止异常的抛出,创建可选的绑定。

1
2
3
4
5
@Nullable @BindView(R.id.might_not_be_there) TextView mightNotBeThere;
@Optional @OnClick(R.id.maybe_missing) void onMaybeMissingClicked() {
// TODO ...
}

彩蛋

ButterKnife 提供了 findViewById 方法的简化版: findById,可以在Activity,View 和 Dialog 中查找view。 它使用泛型对返回值类型进行自动转换,不需要我们进行强转。

1
2
3
4
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);