上一篇文章讲解了ButterKnife在编译阶段通过注解生成java文件,今天需要讲解的是ButterKnife绑定上下文。
一般的,绑定上下文的操作十分简单,在activity中通常是这样的:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
在Fragment中类似下面这样:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_word_detail_display, container, false);
ButterKnife.bind(this, view);
return view;
}
在adapter里的ViewHolder里面使用:
static class ViewHolder {
@Bind(R.id.item_icon)
ImageView itemIcon;
@Bind(R.id.note_label)
TextView noteLabel;
ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
从上文就可以看出,一般Butterknife通过bind方法绑定上下文,下面我们来阅读源码:
public static void bind(Activity target) {
bind(target, target, Finder.ACTIVITY);
}
bind方法:
static void bind(Object target, Object source, Finder finder) {
Class> targetClass = target.getClass();
try {
if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
ViewBinder
在这个方法里面, 通过findViewBinderForClass(targetClass)获取与此activity相对应的我们通过注解在编译阶段生成java类的实例:
private static ViewBinder
这里的核心代码就是
Class> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
//noinspection unchecked
viewBinder = (ViewBinder) viewBindingClass.newInstance();
在这里找到Activity对应的生成java文件的class,然后生成它的实例,最后将该实例以class为键,实例为值放进hashMap里,找到该实例后,那么接下来就应该开始绑定了,因为我们生成的class都实现了ViewBinder接口,所以bind方法会跑到具体实现类的方法里面去:
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131492943, "field 'textTv'");
target.textTv = finder.castView(view, 2131492943, "field 'textTv'");
view = finder.findRequiredView(source, 2131492944, "field 'clickBtn'");
target.clickBtn = finder.castView(view, 2131492944, "field 'clickBtn'");
}
Finder是个Enum类,在这里我们传的是Finder.ACTIVITY,首先调用 finder.findRequiredView方法:
public T findRequiredView(Object source, int id, String who) {
T view = findOptionalView(source, id, who);
if (view == null) {
String name = getContext(source).getResources().getResourceEntryName(id);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' annotation.");
}
return view;
}
public T findOptionalView(Object source, int id, String who) {
View view = findView(source, id);
return castView(view, id, who);
}
@SuppressWarnings("unchecked") // That's the point.
public T castView(View view, int id, String who) {
try {
return (T) view;
} catch (ClassCastException e) {
if (who == null) {
throw new AssertionError();
}
String name = view.getResources().getResourceEntryName(id);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}
在findOptionalView方法里面调用的findView(source,id)最终会走到这里:
ACTIVITY {
@Override protected View findView(Object source, int id) {
return ((Activity) source).findViewById(id);
}
@Override public Context getContext(Object source) {
return (Activity) source;
}
}
这串代码我们就很熟悉了,就是我们经常使用的findViewById()方法,通过这几个方法ButterKnife终于将我们需要绑定的控件转换成了一个View(没有具体到是什么种类的控件,例如textView,edittext什么的),具体到种类的控件是在第二个方法finder.castView方法实现的,还是以上面的例子说明,target.textTv是TextView,那么castView的返回值就是TextView的具体实例,这个的实现是通过强转实现。
一般的控件绑定就是这样,在这里,还有一个情况就是点击事件的绑定,其实这个也很简单。首先我们一般这样定义点击事件:
@OnClick(R.id.text_tv)
void textTvClick(){
Toast.makeText(this,"hello world",Toast.LENGTH_SHORT).show();
}
那么编译时生成的相应代码就是:
View view;
view = finder.findRequiredView(source, 2131492943, "field 'textTv' and method 'textTvClick'");
target.textTv = finder.castView(view, 2131492943, "field 'textTv'");
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override public void doClick(
android.view.View p0
) {
target.textTvClick();
}
});
DebouningOnClickListener实现了View.OnClickListener的接口,其实现很简单,源代码如下:
public abstract class DebouncingOnClickListener implements View.OnClickListener {
private static boolean enabled = true;
private static final Runnable ENABLE_AGAIN = new Runnable() {
@Override public void run() {
enabled = true;
}
};
@Override public final void onClick(View v) {
if (enabled) {
enabled = false;
v.post(ENABLE_AGAIN);
doClick(v);
}
}
public abstract void doClick(View v);
}
到这里,ButterKnife源码就告一段落了,第一次剖析开源框架的源代码,写的有点粗糙,如果有什么不足的话,欢迎批评指正。