简单使用
一 导入配置
-
在gradle中导入依赖
dependencies { implementation 'com.jakewharton:butterknife:10.1.0' annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
二 基本使用
-
Activity
public class MainActivity extends AppCompatActivity { @BindView(R.id.tv_title) TextView title; @OnClick(R.id.bt_submit) public void submit() { title.setText("hello world"); } private Unbinder unbinder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); unbinder = ButterKnife.bind(this); } @Override protected void onDestroy() { unbinder.unbind(); super.onDestroy(); } } -
Fragment
public class SimpleFragment extends Fragment { @BindView(R.id.fragment_text_view) TextView mTextView; private Unbinder unbinder; public SimpleFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_simple, container, false); unbinder = ButterKnife.bind(this, view); mTextView.setText("TextView in Fragment are found!"); return view; } @Override protected void onDestroy() { unbinder.unbind(); super.onDestroy(); } } Adapter
static class ViewHolder {
@BindView(R.id.person_name)
TextView name;
@BindView(R.id.person_age)
TextView age;
@BindView(R.id.person_location)
TextView location;
@BindView(R.id.person_work)
TextView work;
public ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
三 绑定源码解析
最后编译一下项目。直觉告诉我们应该从ButterKnife.bind(this)开始分析,因为它像是 ButterKnife 和 Activity 建立绑定关系的过程,看具体的代码:
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
sourceView代表当前界面的顶级父 View,是一个FrameLayout,继续看createBinding()方法:
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
return constructor.newInstance(target, source);
}
...
}
首先得到要绑定的 Activity 对应的 Class,然后用根据 Class 得到一个继承了Unbinder的Constructor,最后通过反射constructor.newInstance(target, source)得到Unbinder子类的一个实例,到此ButterKnife.bind(this)操作结束。这里我们重点关注findBindingConstructorForClass()方法是如何得到constructor实例的:
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
整体的流程是先检查BINDINGS是否存在 Class 对应的 Constructor,如果存在则直接返回,否则去构造对应的 Constructor。其中BINDINGS是一个LinkedHashMap: Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>(),缓存了对应的 Class 和 Constructor 以提高效率! 接下来看当不存在对应 Constructor 时如何构造一个新的,首先如果clsName是系统相关的,则直接返回 null,否则先创建一个新的 Class:
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
这里新的 Class 的 name 是 com.shh.sometest.MainActivity_ViewBinding,最后用新的 Class 创建一个 继承了Unbinder的 Constructor,并添加到BINDINGS:
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
BINDINGS.put(cls, bindingCtor);
所以最终bind()方法返回的是MainActivity_ViewBinding类的实例。既然可以返回MainActivity_ViewBinding的实例,那MainActivity_ViewBinding这个类肯定是存在的。可以在如下目录找到它(这个类是在项目编译时期由 annotationProcessor 生成的)
MainActivity_ViewBinding
来看看它里边都做了那些事:
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view2131165217;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
target.title = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'title'", TextView.class);
view = Utils.findRequiredView(source, R.id.bt_submit, "method 'submit'");
view2131165217 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.submit();
}
});
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.title = null;
view2131165217.setOnClickListener(null);
view2131165217 = null;
}
}
之前createBinding()方法中return constructor.newInstance(target, source);操作使用的就是MainActivity_ViewBinding类两个参数的构造函数。
重点看这个构造函数,首先是给target.title赋值,即MainActivity中的TextView,用到了一个findRequiredViewAsType()方法:
public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
View view = findRequiredView(source, id, who);
return castView(view, id, who, cls);
}
继续看findRequiredView()方法:
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");
}
最终还是通过findViewById()得到对应View,然后就是castView():
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
try {
return cls.cast(view);
} catch (ClassCastException e) {
String name = getResourceEntryName(view, id);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}
核心就是把findRequiredView()得到的 View 转成指定类型的 View ,如果 xml 中定义的 View 和 Activity 中通过注解绑定的 View 类型不一致,就会抛出上边方法的异常,可能很多人都遇到过。这样target.title的赋值就结束了,接下来就是直接使用findRequiredView()找到对应 id 的Button,不用进行类型转换,然后给它绑定点击事件,最终调用了在MainActivity中给Button绑定点击事件时定义的submit()方法。到这里就完成了 相关 View 的赋值以及事件绑定!
MainActivity_ViewBinding类中还有一个unbind()方法,需要在Activity或Fragment的onDestory()中调用,以完成 相关 View 引用的释放以及点击事件的解绑操作!
四 使用中可能出现的细节问题
- 设置view时候不要用private
@BindView(R.id.fra_pro_username)
TextView usernameTextView;
//正确
@BindView(R.id.fra_pro_bio)
Private TextView bioTextView;
//错误 实际绑定view是在其他辅助类中调用findviewbyid绑定的,如果设为private辅助类就没有权限更改
-
不要漏了
ButterKnife.bind(this)还有和setContentView(R.layout.activity_profile)顺序要正确,并且activity和fragment和viewholder之中绑定函数的参数有点不同setContentView(R.layout.activity_profile); ButterKnife.bind(this); //正确 ButterKnife.bind(this); setContentView(R.layout.activity_profile); //错误 有些findviewbyid是不能改写的
accountTextView = getActivity().findViewById(R.id.user_name);
joinTimeTextView = getActivity().findViewById(R.id.user_jointime);
locationTextView = getActivity().findViewById(R.id.user_location);
//这种不能改写,因为是在fragmnet中调用的getavtivity()得到活动后在调用的findviewbyid
而butterknife 绑定的view是fragment的所以不能改写
View view = inflater.inflate(R.layout.fragment_profile_info, container, false);
userAvatarImageView = view.findViewById(R.id.user_avatar);
//可以改写,理由如上
totalAccountBtn = navigationView.getHeaderView(0).findViewById(R.id.total_account_bn);
//不能改写,理由如上,原因butterknife绑定的view和调用findviewbyId的view不同</pre>
五 使用中可能出现的细节问题
-
此框架还有其他一些功能如string等资源的自动注入,重组view list等等,详见官方文档