加入到项目的方法
把这块放到最前是有原因的,下面是方法:
MAVEN项目:(7.0.1是本文当前版本号)(在pom.xml文件中)
<dependency>
<groupId>com.jakewharton</groupId>
<artifactId>butterknife</artifactId>
<version>7.0.1</version>
</dependency>
Gradle项目(现在Android应该都是Gradle了吧,在build.gradle文件中)
compile 'com.jakewharton:butterknife:7.0.1'
另外,还需要下面两个配置:
//支持lint warning 检查机制
lintOptions {
disable 'InvalidPackage'
}
//为什么加入这个呢?防止冲突,比如我同时用了dagger-compiler就会报错,说下面这个`Processor`重复了
packagingOptions {
exclude 'META-INF/services/javax.annotation.processing.Processor'
}
这样加入了还没有完,我们还要在Proguard中加入下面这些代码(为什么呢?Proguard的原理大家如果懂的话就知道了,Butterknife的使用和生成的一些类都是动态的,而ProGuard这样的工具可能判定这些类没有被使用而移除他们,所以要在他的配置文件下面做下面的配置):
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
最简单的用法
最简单的肯定是自动关联View了,以前我们都是样板式的代码:
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn=(Button)findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Btn Clicked", Toast.LENGTH_SHORT).show();
}
});
}
后来有了ButterKnife就简单了,如下:
class ExampleActivity extends Activity {
@Bind(R.id.title) Button btn;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
// TODO Use fields...
}
}
没有一堆的findViewById
和强制转换是不是清爽多了呢?
而且,即使这些代码,都可以通过Android Studio
的插件Android ButterKnife Zelezny
帮你完成了。
ButterKnife可不是通过反射实现了,而是在编译的时候生成的代码,和我们自己写的其实原理一样,比如上面的Button的绑定的实现就类似下面的方法:
public void bind(ExampleActivity activity) {
activity.btn = (android.widget.Button) activity.findViewById(2130968578);
}
ButterKnife以前绑定的代码是
@InjectView
注解和inject
方法,现在改了名字感觉更容易理解了,基本原理没变。- 另外,大家关心
@Bind
注解对于绑定的成员变量有没有要求呢?其实是有的,如果你试着将它的限定符改为private
,在编译的时候就会报错如下:
Error:(21, 20) 错误: @Bind fields must not be private or static. ...
也就是你的成员变量不能是private 或者static修饰了。
是不是和我们自己写的代码差不多呢?
更多的绑定
绑定资源
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的绑定
比如说Fragment
中(得到View
,然后bind
方法传入这个View
的实例):
public class FancyFragment extends Fragment {
@Bind(R.id.button1) Button button1;
@Bind(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中的用法
经常会在Adapter中使用ViewHolder,其实你也可以在ViewHolder中使用ButterKnife:
在ViewHolder的构造函数中调用bind
,然后成员变量同样的使用@Bind
注解。其实这几种绑定原理都一样,就是我们前面编译后的代码那样的方式。
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 {
@Bind(R.id.title) TextView name;
@Bind(R.id.job_title) TextView jobTitle;
public ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
}
ButterKnife.bind()还有其他的一些API,可以自己关注一下,直接看源码的注释很容易理解了。
View List批量操作
如果我们有一系列的View放到了一个List里面,就可以进行批量操作了:批量绑定,批量设置属性等。
//批量绑定
@Bind({ R.id.first_name, R.id.middle_name, R.id.last_name })
List<EditText> nameViews;
//批量设置
ButterKnife.apply(nameViews, DISABLE);
ButterKnife.apply(nameViews, ENABLED, false);
其中,DIABLE
,ENABLED
是我们定义的两个对象,一个是Action,负责执行操作,一个是Setter,负责将值设置为第三个参数:
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);
}
};
也就是第一个apply
将所有的nameViews中的View对象设置为disabled,第二个apply
将所有的对象的enable
属性设置为false
(第三个参数)
另外,我们可以直接对属性进行设置,而不需要编写Action
和Setter
,例如:
ButterKnife.apply(nameViews, View.ALPHA, 0.0f);
绑定监听器
ButterKnife还有一个比较常用的功能就是类似@OnClick等的绑定监听器的方法,Android中需要大量的监听器监听用户的操作。示例如下:
@OnClick(R.id.submit)
public void submit(View view) {
// TODO submit data to server...
}
如果不需要绑定的对象,不写也可以:
@OnClick(R.id.submit)
public void submit() {
// TODO submit data to server...
}
而且,这里可以直接将绑定的实例转换成实际的对象:
@OnClick(R.id.submit)
public void sayHi(Button button) {
button.setText("Hello!");
}
另外,多个View也可以绑定到一个处理方法上:
@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
public class FancyButton extends Button {
@OnClick
public void onClick() {
// TODO do something!
}
}
这样,用户点击FancyButton
时就会触发该方法。
绑定重置
我们可能需要在Fragment销毁的时候将绑定的View全部设置为null,ButterKnife提供了一个unbind
方法自动执行这个操作。
public class FancyFragment extends Fragment {
@Bind(R.id.button1) Button button1;
@Bind(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;
}
@Override public void onDestroyView() {
super.onDestroyView();
ButterKnife.unbind(this);
}
}
可选的绑定
如果你给一个绑定添加了一个@Nullable
注解,即使对应的资源id不存在也不会报错。有时候我们不能保证这个id一定存在,或者有特殊的需求的时候可以使用
@Nullable @Bind(R.id.might_not_be_there) TextView mightNotBeThere;
@Nullable @OnClick(R.id.maybe_missing) void onMaybeMissingClicked() {
// TODO ...
}
多方法监听
例如ListView的设置onItemOnItemSelected
的SelectedListener
接口有两个回调方法,一个是onItemSelected
,一个是onNothingSelected
,这时候,我们可以设置两个注解来分别处理这两个回调方法:
@OnItemSelected(R.id.list_view)
void onItemSelected(int position) {
// TODO ...
}
@OnItemSelected(value = R.id.maybe_missing, callback = NOTHING_SELECTED)
void onNothingSelected() {
// TODO ...
}
小工具
如果你不想用注解的方法绑定View
但是又很讨厌强制转换,就可以用ButterKnife.findById(id)这样的方法:
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);
其实这个方法你自己实现也只需要非常简单的代码,就是使用泛型,例如我自己定义一个静态的工具类Views.java,他的代码如下:
public class Views {
public static <T> T findById(Activity context, int id) {
return (T) context.findViewById(id);
}
}
//其他的方法也类似