1,简介
butterKnife是一个view依赖注入框架,可以将一些模块代码如findViewById和OnClick等通过注解方式简化;
依赖如下:(注意,annotationProcessor与dagger产生冲突)
// https://mvnrepository.com/artifact/com.jakewharton/butterknife
implementation'com.jakewharton:butterknife:10.2.3'
annotationProcessor'com.jakewharton:butterknife-compiler:10.2.3'
2,实例
创建一个Activity,使用ButterKnife绑定View并且设置事件,
public class ButterKnifeActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
// 通过注解方式,绑定对应view,或者views
@BindView(R.id.text_view)
TextView textView;
@BindView(R.id.button)
Button button;
@BindView(R.id.image_view)
ImageView imageView;
@BindView(R.id.checkbox)
CheckBox checkBox;
@BindView(R.id.progressBar)
ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// onCreate编译期间生成代码
ButterKnife.bind(this);
Log.d(TAG, "onCreate: "+textView+" "+button+" "+imageView+" "+checkBox+" "+progressBar);
}
@OnClick(value = {R.id.button})
public void click(View view){
switch (view.getId()){
case R.id.button:
Log.d(TAG, "button was be click!");
textView.setText("button was be click!");
break;
default:
break;
}
}
@OnTouch(value = R.id.text_view)
public void touch(){
Log.d(TAG, "on touch!");
}
}
2021-12-20 10:12:38.675 11677-11677/com.zjw.demoApp D/MainActivity: onCreate: com.google.android.material.textview.MaterialTextView{e7406c1 V.ED..... ......ID 0,0-0,0 #7f0801ad app:id/text_view} com.google.android.material.button.MaterialButton{bbbde66 VFED..C.. ......I. 0,0-0,0 #7f080062 app:id/button} androidx.appcompat.widget.AppCompatImageView{5a4f5a7 V.ED..... ......I. 0,0-0,0 #7f0800d5 app:id/image_view} com.google.android.material.checkbox.MaterialCheckBox{3faa54 VFED..C.. ......I. 0,0-0,0 #7f08006c app:id/checkbox} android.widget.ProgressBar{1f94dfd V.ED..... ......I. 0,0-0,0 #7f080146 app:id/progressBar}
2021-12-20 10:12:45.903 11677-11677/com.zjw.demoApp D/MainActivity: button was be click!
2021-12-20 10:13:03.852 11677-11677/com.zjw.demoApp D/MainActivity: on touch!
当然,除此之外还有其它控件事件处理,
//listView item点击事件
@OnItemSelected(R.id.list_view)
void onItemSelected(int position) {
// TODO …
}
//view的onTouchEvent
@OnTouch(R.id.example) boolean onTouch() {
Toast.makeText(this, “Touched!”, Toast.LENGTH_SHORT).show();
return false;
}
//监听EditText的addTextChangedListener
@OnTextChanged(R.id.example) void onTextChanged(CharSequence text) {
Toast.makeText(this, "Text changed: " + text, Toast.LENGTH_SHORT).show();
}
//设置ViewPager的OnPageChangeListener
@OnPageChange(R.id.example_pager) void onPageSelected(int position) {
Toast.makeText(this, "Selected " + position + “!”, Toast.LENGTH_SHORT).show();
}
//设置TextView的OnEditorActionListener(该事件主要用来设置软键盘上的按钮)
@OnEditorAction(R.id.example) boolean onEditorAction(KeyEvent key) {
Toast.makeText(this, "Pressed: " + key, Toast.LENGTH_SHORT).show();
return true;
}
//设置View的OnFocusChangeListener事件
@OnFocusChange(R.id.example) void onFocusChanged(boolean focused) {
Toast.makeText(this, focused ? “Gained focus” : “Lost focus”, Toast.LENGTH_SHORT).show();
}
//设置View的OnLongClickListener长按事件
@OnLongClick(R.id.example) boolean onLongClick() {
Toast.makeText(this, “Long clicked!”, Toast.LENGTH_SHORT).show();
return true;
}
//关于资源的绑定
@BindString(R.string.title) String title; //字符串
@BindDrawable(R.drawable.graphic) Drawable graphic; //drawable
@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
@BindArray(R.array.countries) String[] countries; 字符串数组
@BindArray(R.array.icons) TypedArray icons;
@BindBool(R.bool.is_tablet) boolean isTablet;
3,原理
(1)与dagger相同,通过生成代码减少性能影响,生成代码如下,
public class ButterKnifeActivity_ViewBinding implements Unbinder {
private ButterKnifeActivity target;
private View view7f0801ad;
private View view7f080062;
@UiThread
public ButterKnifeActivity_ViewBinding(ButterKnifeActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
@SuppressLint("ClickableViewAccessibility")
public ButterKnifeActivity_ViewBinding(final ButterKnifeActivity target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.text_view, "field 'textView' and method 'touch'");
target.textView = Utils.castView(view, R.id.text_view, "field 'textView'", TextView.class);
view7f0801ad = view;
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View p0, MotionEvent p1) {
target.touch();
return true;
}
});
view = Utils.findRequiredView(source, R.id.button, "field 'button' and method 'click'");
target.button = Utils.castView(view, R.id.button, "field 'button'", Button.class);
view7f080062 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.click(p0);
}
});
target.imageView = Utils.findRequiredViewAsType(source, R.id.image_view, "field 'imageView'", ImageView.class);
target.checkBox = Utils.findRequiredViewAsType(source, R.id.checkbox, "field 'checkBox'", CheckBox.class);
target.progressBar = Utils.findRequiredViewAsType(source, R.id.progressBar, "field 'progressBar'", ProgressBar.class);
}
@Override
@CallSuper
public void unbind() {
ButterKnifeActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.textView = null;
target.button = null;
target.imageView = null;
target.checkBox = null;
target.progressBar = null;
view7f0801ad.setOnTouchListener(null);
view7f0801ad = null;
view7f080062.setOnClickListener(null);
view7f080062 = null;
}
}
(2)然后通过ButterKnife.bind(this)反射实例化已经生成的对象;
拿到decorView,跟进bind方法,
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
扫描构造方法,然后实例化,跟进findBindingConstructorForClass;
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
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);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InstantiationException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException("Unable to create binding instance.", cause);
}
}
findBindingConstructorForClass方法,会排除framework或者java中的对象,然后通过loadClass方法加载自动生成的XXXX_ViewBinding对象,此类一定继承Unbinder,然后返回此构造方法;如果找不到此构造方法,在ClassNotFoundException异常中会去拿超类的构造方法,由此可知butterKnife注入view子类是可继承的;
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
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;
}