简介
特点:
- 采用注解的方式实现强大的View绑定和Click事件处理功能,简化代码,提升开发效率
- 方便的处理Adapter里的ViewHolder绑定问题
- 是一种依赖注入框架,运行时不会影响APP效率(原理和dagger一样,是在编译期生成代码,与反射没有半毛钱关系)
- 使用配置方便 ,代码清晰,可读性强
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.
Remember: A butter knife is like a dagger only infinitely less sharp(无限锐利).
【如何将注解和字段放在一行】
效果如下:
设置方式:
settings-->editor-->code style-->java-->wrapping and braces-->Field annotation
【如何使用插件自动生成代码】
使用Zelezny插件,可一键生成所有定义id的View的声明及onclick事件。
在AndroidStudio->File->Settings->Plugins->搜索【Zelezny】下载添加即可。
使用时,在要导入注解的Activity 或 Fragment 或 ViewHolder的【layout】资源代码上,右键 --> Generate --> Generate ButterKnife Injections 【alt+F1】
Gradle配置
新版本只需添加以下两行依赖即可
//旧版本需在gradle进行好几项配置,新版本只需添加以下两行依赖即可。新版本和旧版本API不一致,建议赶快升级!
compile 'com.jakewharton:butterknife:8.8.1'//2017-9-21最新版本
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'//编译器
1、在项目的build.gradle中添加:
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'//每个Android项目默认都会带的
//PS:在新版本中千万不要加这些,日了狗了,加上去之后反而不会生成需要的文件,进而导致完全不能使用ButterKnife。
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'//这个是需要我们手动添加的,apt是用于自动生成代码来进行依赖注入的
}
android-apt是Gradle编译器的插件,根据其官方文档,主要两个目的:
- 编译时使用该工具,最终打包时不会将该插件打入到apk中。
- 能够根据设置的源路径,在编译时期生成相应代码。
2、在module的build.gradle中添加:
apply plugin: 'com.android.application'//每个Android项目默认都会带的。在build.gradle的第一行
//PS:在新版本中千万不要加这些,日了狗了,加上去之后反而不会生成需要的文件,进而导致完全不能使用ButterKnife。
apply plugin: 'com.neenbedankt.android-apt'//apt支持
3、在module的build.gradle中添加:
compile 'com.jakewharton:butterknife:8.8.1'//2017-9-21最新版本
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'//为编译时期生成代码等相关的类库
在android-apt的文档中,也推荐使用这种方式。因为,编译时期生成代码的类库在运行期并不需要,那么将其分为两个库:运行类库butterknife/dagger和 编译器生成代码类库butterknife-compiler/dagger-compiler。那么在打包时,就不需要将butterknife-compiler/dagger-compiler打入其中,减小APK 的大小。
Butter Knife中定义的注解
注解分为三种:以Bind开头的绑定资源的注解,以On开头的注册事件监听的注解,以Listener开头的辅助On**注解的内部注解。
BindView
//Bind a field to the view for the specified ID. The view will automatically be cast to the field type.
@Retention(CLASS)
@Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
BindViews
// Bind a field to the view for the specified ID. The view will automatically be cast to the field type.
@Retention(CLASS) @Target(FIELD)
public @interface BindViews {
/** View IDs to which the field will be bound. */
@IdRes int[] value();
}
@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name }) List<EditText> nameViews;
BindString
//Bind a field to the specified string resource ID.
@Retention(CLASS) @Target(FIELD)
public @interface BindString {
/** String resource ID to which the field will be bound. */
@StringRes int value();
}
BindBitmap
//Bind a field to a Bitmap from the specified drawable resource ID.
@Retention(CLASS) @Target(FIELD)
public @interface BindBitmap {
/** Drawable resource ID from which the Bitmap will be created. */
@DrawableRes int value();
}
@BindBitmap(R.drawable.logo) Bitmap logo;
BindDrawable
//Bind a field to the specified drawable resource ID.
@Retention(CLASS) @Target(FIELD)
public @interface BindDrawable {
/** Drawable resource ID to which the field will be bound. */
@DrawableRes int value();
/** Color attribute resource ID that is used to tint the drawable. */
@AttrRes int tint() default 0;
}
@BindDrawable(R.drawable.placeholder) Drawable placeholder;
@BindDrawable(value = R.drawable.placeholder, tint = R.attr.colorAccent) Drawable tintedPlaceholder;
OnClick
/**
* Bind a method to an OnClickListener on the view for each ID specified.
* Any number of parameters from OnClickListener#onClick(android.view.View) onClick} may be used on the method.
*/
@Target(METHOD) @Retention(CLASS)
@ListenerClass(
targetType = "android.view.View",
setter = "setOnClickListener",
type = "butterknife.internal.DebouncingOnClickListener",
method = @ListenerMethod(
name = "doClick",
parameters = "android.view.View"
)
)
public @interface OnClick {
/** View IDs to which the method will be bound. */
@IdRes int[] value() default { View.NO_ID };
}
最常使用的注解的示例代码
绑定Activity
public class SimpleActivity extends AppCompatActivity {
@BindView(R.id.text_view) TextView mTextView; //this field should not be declared private or static
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
ButterKnife.bind(this);// ButterKnife.bind(this) should be called after setContentView()
mTextView.setText("View视图的绑定");
}
@OnClick(R.id.button)
void onButtonClick(View view) { //the method should not be declared private or static
//you can write the parameter (View view) as above or just write onButtonClick() leave parameters empty
Toast.makeText(this, "事件绑定", Toast.LENGTH_SHORT).show();
}
}
绑定Fragment
public class SimpleFragment extends Fragment {
@BindView(R.id.text_view) TextView mTextView;
private Unbinder unbinder;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, container, false);//标准写法就是这样的
unbinder = ButterKnife.bind(this, view);//绑定Activity为ButterKnife.bind(this);
mTextView.setText("TextView in Fragment are found!");
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
//Fragments have a different view lifecycle than activities.
//When binding a fragment in onCreateView, set the views to null in onDestroyView.
unbinder.unbind();//解除绑定,官方文档只对fragment做了解绑
}
}
绑定Adapter
public class PersonAdapter extends BaseAdapter {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.person_item_layout, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else holder = (ViewHolder) convertView.getTag();
holder.name.setText("包青天");
// etc...
return convertView;
}
static class ViewHolder {
@BindView(R.id.person_name) TextView name;
public ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
}
绑定资源
@BindView(R.id.app_name) TextView mTextView;//view
@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name }) List<EditText> nameViews;
@BindString(R.string.app_name) String appName;//sting
@BindColor(R.color.red) int textColor;//颜色
@BindBitmap(R.drawable.logo) Bitmap logo;
@BindDrawable(R.drawable.selector_image) Drawable selector;//drawble
@BindDrawable(value = R.drawable.placeholder, tint = R.attr.colorAccent) Drawable tintedPlaceholder;
绑定事件
@OnClick(R.id.submit) void submit(View view) { }
@OnClick(R.id.submit) void sayHi(Button button) { } //定义一个特定类型的View时,它将自动被转换
@OnClick({ R.id.door1, R.id.door2 }) void pickDoor(DoorView door) { } //多个view在一个点击事件中回调
@OnClick(R.id.submit) void submit() { } //参数都是可选的(任何参数都是:如果不需要,可以不写)
@OnItemClick(R.id.my_list_view) void onItemClick(int position) { } //虽然有四个参数, 你可以只写你想要的那个
@OnItemLongClick(R.id.my_list_view) boolean onItmeLongClick(int position) { }//返回true时,整个touch事件结束
//if return false, the onItemClick() will be invoked反射调用 when touch up
自定义view可以绑定自己的监听,而必须指定id
public class MyButton extends Button {
@OnClick void onClick() { } //当点击自己时回调
}
添加多回调方法的监听的使用方法
@OnTextChanged(value = R.id.mEditText, callback = OnTextChanged.Callback.BEFORE_TEXT_CHANGED)
void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@OnTextChanged(value = R.id.mEditText, callback = OnTextChanged.Callback.TEXT_CHANGED)
void onTextChanged(CharSequence s, int start, int before, int count) { }
@OnTextChanged(value = R.id.mEditText, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED)
void afterTextChanged(Editable s) { }
官方文档
Introduction
Annotate fields with @BindView and a view ID for Butter Knife to find and automatically cast the corresponding view in your layout.
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...
}
}
Instead of slow reflection, code is generated to perform the view look-ups. Calling bind delegates代表 to this generated code that you can see and debug.
代替其他库经常采用的非常慢的反射机制,ButterKnife是通过在编译时生成代码,在运行时通过执行view查找操作实现View的绑定的。当你调用 bind 方法时,代表你可以查看并调试生成的代码。
The generated code for the above example is roughly equivalent to the following:
public void bind(ExampleActivity activity) {
activity.subtitle = (android.widget.TextView) activity.findViewById(2130968578);
activity.footer = (android.widget.TextView) activity.findViewById(2130968579);
activity.title = (android.widget.TextView) activity.findViewById(2130968577);
}
RESOURCE BINDING
Bind pre-defined resources with @BindBool, @BindColor, @BindDimen, @BindDrawable, @BindInt, @BindString, which binds an R.bool ID (or your specified type) to its corresponding field.
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
// ...
}
NON-ACTIVITY BINDING
You can also perform binding on arbitrary任意的 objects by supplying your own view root.
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;
}
}
Another use is simplifying the view holder pattern inside of a list adapter. 另一个用途是简化列表适配器内的 view holder 模式。
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;
}
static class ViewHolder {
@BindView(R.id.title) TextView name;
@BindView(R.id.job_title) TextView jobTitle;
public ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
}
You can see this implementation in action in the provided sample. 您可以在提供的示例中看到此实现。
Calls to ButterKnife.bind can be made anywhere you would otherwise put findViewById calls.
可以在任何地方调用ButterKnife.bind,以取代 findViewById 的调用。
Other provided binding APIs:
- Bind arbitrary objects using an activity as the view root. If you use a pattern like MVC you can bind the controller using its activity with ButterKnife.bind(this, activity).
- Bind a view's children into fields using ButterKnife.bind(this). If you use <merge> tags in a layout and inflate in a custom view constructor you can call this immediately after. Alternatively或者、否则, custom view types (inflated from XML) can use it in the onFinishInflate() callback.
VIEW LISTS
You can group multiple views into a List or array.
@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name }) List<EditText> nameViews;
The apply method allows you to act on对...起作用 all the views in a list at once.
ButterKnife.apply(nameViews, DISABLE);
ButterKnife.apply(nameViews, ENABLED, false);
Action and Setter interfaces allow specifying simple behavior.
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);
}
};
An Android Property can also be used with the apply method.
ButterKnife.apply(nameViews, View.ALPHA, 0.0f);
LISTENER BINDING
Listeners can also automatically be configured onto methods.
@OnClick(R.id.submit)
public void submit(View view) {
// TODO submit data to server...
}
All arguments to the listener method are optional.
@OnClick(R.id.submit)
public void submit() {
// TODO submit data to server...
}
Define a specific type and it will automatically be cast.
@OnClick(R.id.submit)
public void sayHi(Button button) {
button.setText("Hello!");
}
Specify multiple IDs in a single binding for common event handling.
@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();
}
}
Custom views can bind to their own listeners by not specifying an ID.
public class FancyButton extends Button {
@OnClick
public void onClick() {
// TODO do something!
}
}
BINDING RESET
Fragments have a different view lifecycle than activities. When binding a fragment in onCreateView, set the views to null in onDestroyView. Butter Knife returns an Unbinder instance when you call bind to do this for you. Call its unbind method in the appropriate lifecycle callback.
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();
}
}
OPTIONAL BINDINGS
By default, both @Bind and listener bindings are required. An exception will be thrown if the target view cannot be found.
To suppress压制、阻止 this behavior and create an optional binding, add a @Nullable annotation to fields or the @Optional annotation to methods.
Note: Any annotation named @Nullable can be used for fields. It is encouraged鼓励 to use the @Nullable annotation from Android's "support-annotations" library.
@Nullable @BindView(R.id.might_not_be_there) TextView mightNotBeThere;
@Optional @OnClick(R.id.maybe_missing) void onMaybeMissingClicked() { }
MULTI-METHOD LISTENERS
Method annotations whose corresponding listener has multiple callbacks can be used to bind to any one of them. Each annotation has a default callback that it binds to. Specify an alternate using the callback parameter.
@OnItemSelected(R.id.list_view) void onItemSelected(int position) { }
@OnItemSelected(value = R.id.maybe_missing, callback = NOTHING_SELECTED) void onNothingSelected() { }
BONUS
Also included are findById methods which simplify简化 code that still has to find views on a View, Activity, or Dialog. It uses generics to infer推断 the return type and automatically performs the cast.
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);
Add a static import for ButterKnife.findById and enjoy even more fun.
2017-9-22