AS 中 Butter Knife 使用详解

介绍开源注入框架 Butter Knife 的使用方法

Butter Knife介绍

官方Git地址
官网使用说明
Android开发中大量的findViewById和点击事件,像初始view、设置view监听这样简单而重复的操作让人觉得特别麻烦,这个时候就有了相应的偷懒文案-依赖注入。
目前比较流行的两种 ButterKnife 和 Dagger:
而Dagger中View的注入写法非常困难或者难懂,这个时候 ButterKnife 诞生了,
所以直接叫 ButterKnife 为 findViewById 都是没有问题的。

Butter Knife的特点:

  1. 强大的View绑定和Click事件处理功能,简化代码,提升开发效率
  2. 方便的处理Adapter里的ViewHolder绑定问题
  3. 运行时不会影响APP效率,使用配置方便
  4. 代码清晰,可读性强

AS配置

module的build.gradle文件中添加如下代码

最新版本信息请在官网使用说明中查询

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

即可;

绑定View、资源

下面这些绑定操作在 ButterKnife.bind(this) 后执行相应id与view的绑定,要注意的是,这一行代码必须在 setContentView 之后,否则会有异常发生;

Butter Knife 支持下面这些绑定操作,对于View的绑定,相当于findViewById操作;对于资源绑定,相当于已经初始化,取值为对应的values资源 或者 图片文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// View绑定
@BindView(R.id.tvWorld)
TextView tvWorld;
@BindView(R.id.tvChina)
TextView tvChina;
@BindView(R.id.tvShenZhen)
TextView tvShenZhen;
// 资源绑定
@BindArray(R.array.arrayTest)
String[] arrays;
@BindColor(R.color.colorTest)
int colorValue;
@BindString(R.string.stringTest)
String stringValue;
@BindBitmap(R.mipmap.avator_one)
Bitmap avatorOne;
@BindDrawable(R.mipmap.avator_two)
Drawable avatorTwo;
@BindBool(R.bool.boolTest)
boolean bool;
@BindDimen(R.dimen.dimenTest)
int dimen; // int (for pixel size) or float (for exact value) field
@BindInt(R.integer.intTest)
int intValue;
// 可绑定 float 类型的 dimen 资源 - 待验证
// @BindFloat(R.dimen.floatTest)
// float floatValue;
// 批量绑定
@BindViews({R.id.tvWorld, R.id.tvChina, R.id.tvShenZhen})
List<TextView> tvList;
@BindView(R.id.ivBitmap)
ImageView ivBitmap;
@BindView(R.id.ivDrawable)
ImageView ivDrawable;

使用情况如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
Log.d(TAG, "onCreate called, bool=" + bool + ", dimen=" + dimen + ", intValue="
+ intValue + ", stringValue=" + stringValue + ", arrays=" + arrays.toString());
tvShenZhen.setText(stringValue);
tvShenZhen.setTextColor(colorValue);
tvList.get(0).setText(arrays[0]);
ivBitmap.setBackground(avatorTwo);
ivDrawable.setBackground(new BitmapDrawable(avatorOne));
}

绑定Fragment

onCreateView中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@BindView(R.id.id_title_left_btn)
ImageButton mLeftMenu;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_title, container, false);
/*
mLeftMenu = (ImageButton) view.findViewById(R.id.id_title_left_btn);
mLeftMenu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getActivity(), "i am a imagebutton in TitleFragment!", Toast.LENGTH_LONG).show();
}
});
*/
// Fragment中的绑定操作
ButterKnife.bind(this, view);
return view;
}
@OnClick(R.id.id_title_left_btn)
public void show() {
Toast.makeText(getActivity(), "i am a imagebutton in TitleFragment!", Toast.LENGTH_LONG).show();
}

绑定Adapter

主要是创建ViewHolder的方式不一样,代码逻辑在getView中完成。
标准写法

1
2
3
4
5
6
public final class ViewHolder {
public ImageView img;
public TextView title;
public TextView info;
public Button viewBtn;
}

标准用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.list_item, null);
holder.img = (ImageView) convertView.findViewById(R.id.img);
holder.title = (TextView) convertView.findViewById(R.id.title);
holder.info = (TextView) convertView.findViewById(R.id.info);
holder.viewBtn = (Button) convertView.findViewById(R.id.view_btn);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.img.setBackgroundResource((Integer) mData.get(position).get("img"));
holder.title.setText((String) mData.get(position).get("title"));
holder.info.setText((String) mData.get(position).get("info"));
holder.viewBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showInfo();
}
});
return convertView;
}

Butter knife的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final class ViewHolderBK {
@BindView(R.id.img)
ImageView img;
@BindView(R.id.title)
TextView title;
@BindView(R.id.info)
TextView info;
@BindView(R.id.view_btn)
Button viewBtn;
public ViewHolderBK(View view) {
ButterKnife.bind(this, view);
}
@OnClick(R.id.view_btn)
public void show() {
showInfo();
}
}

Butter knife的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolderBK holder = null;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item, null);
holder = new ViewHolderBK(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolderBK) convertView.getTag();
}
holder.img.setBackgroundResource((Integer) mData.get(position).get("img"));
holder.title.setText((String) mData.get(position).get("title"));
holder.info.setText((String) mData.get(position).get("info"));
return convertView;
}

对比发现,会简洁很多。

OnClick、OnLongClick绑定

支持可变参数的函数,并能批量绑定处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@OnClick({R.id.ivBitmap, R.id.ivDrawable})
public void sayHiToast(View v) {
switch (v.getId()) {
case R.id.ivBitmap:
Toast.makeText(getApplicationContext(), "ivBitmap click", Toast.LENGTH_SHORT).show();
break;
case R.id.ivDrawable:
Toast.makeText(getApplicationContext(), "ivDrawable click", Toast.LENGTH_SHORT).show();
break;
}
}
@OnClick(R.id.btnLogin)
public void doLogin() {
Toast.makeText(getApplicationContext(), "doLogin click", Toast.LENGTH_SHORT).show();
}
@OnLongClick(R.id.btnLogin)
public boolean doLogin(Button button) {
button.setText("Login update");
return true;
}
@OnClick(R.id.btnCancel)
public void doCancel(Button button) {
Toast.makeText(getApplicationContext(), "doCancel click", Toast.LENGTH_SHORT).show();
button.setText("Error");
}
// 自定义View的话,绑定自己的监听,不需要指定 ID
public class JingButton extends Button {
@OnClick
public void onClick() {
}
}

其它监听器绑定

Butter Knife还支持下面这些监听器操作

实际上,BK对该类型的回调函数的写法有一定的要求,如返回值或者参数等,这个具体问题具体对待,从直观的做法是直接看源码的注释,如下所示:

可选绑定

默认情况下,如果找不到目标视图,则会抛出异常
使用 @Optional,存在即绑定,不存在即忽略,不抛异常

1
2
3
4
5
@Optional @OnClick(R.id.fab)
public void make(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}

ButterKnife.findById()强转

ButterKnife.findById()自动强转,第一个参数可为 View, Activity, Dialog,使用如下:

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);

Binding Reset操作

Fragments 有比Avtivity更多的生命周期 ,如果需要在onCreateView中注入一个fragment,在onDestroyView中销毁它
Butter Knife 有一个reset 方法自动实现它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义一个变量并通过bind操作获得返回值
private Unbinder unbinder;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_title, container, false);
// Fragment中的绑定操作
unbinder = ButterKnife.bind(this, view);
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
// 解绑操作
unbinder.unbind();
}

ButterKnife.apply使用

Zelezny插件使用

如果还想偷懒的话,可以在AndroidStudio->File->Settings->Plugins->搜索Zelezny插件下载使用, 它可以快速生成对应组件的实例对象,不用手动写。
使用时,在要导入注解的Activity 或 Fragment 或 ViewHolder的layout资源代码上,右键—>Generate—>Generate ButterKnife Injections,然后就出现如图的选择框,在里面可以选择相应的事件并自动生成。

引用文章

  1. 绝对不容错过,ButterKnife使用详谈
  2. ButterKnife使用详解
  3. ButterKnife基本使用