前言
本文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
findViewById
calls by using@BindView
on 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
@OnClick
and 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
可以在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
,方便开发者使用,现在已经被标记为过期了
上图可以看到所提供的方法返回的是泛型,这样就不需要进行类型强转,但是在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方法。
一共有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即可
安装完成之后,选中需要使用注解的layout id,右击点击Generate,选择Generate ButterKnife Injections。会出现以下弹框,可以选择需要注解的View,可以选择是否需要生成@OnClick
注解,点击Confirm就会生成对应的注解代码。
这里也可以看到,zelezny能支持的绑定事件只有@OnClick
,但是还是能帮助我们节省时间的。
PS:如果觉得ButterKnife是麻烦的话,可以安装ButterKnifeKiller插件,该插件可以相应的把注解代码替换成findViewById
的代码。