Android ButterKnife使用指北

Android ButterKnife使用指北_第1张图片
图片发自App

前言

本文ButterKnife版本为8.8.1,使用Java语言

ButterKnife是一个编译时的注解框架,JakeWharton大神的杰作之一。ButterKnife专注于View以及相关的资源、一些事件等等,相当轻量级,使用的同时还不会影响代码执行效率(编译时的注解框架,在代码编译时生成新的.class文件)。

现在更多使用的的意义在于可以帮助我们生成代码,不用在重复的写findViewById了,而且还能通过ButterKnife Zelezny插件来自动生成ButterKnife的注解代码。可以说,在开发中使用起来是相当的方便。

大神JakeWharton的说法:

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.

大概意思是:
使用注解生成模板代码,让属性、方法与View绑定。

  • 在属性上使用@BindView消除findViewById的调用。
  • 将多个View分组到列表或数组中。 使用操作,设置器或属性这些操作,一次操作所有的View。
  • 通过使用@OnClick和其他方法注解方法来消除侦听器的匿名内部类。
  • 通过在字段上使用资源注解来消除资源查找。

这句话也是印象深刻啊!

Remember: A butter knife is like a dagger only infinitely less sharp.

配置

在AndroidStudio中使用ButterKnife还是很简单的,如果实在主项目中使用,只需要添加以下依赖就行

dependencies {
    //ButterKnife
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}

如果使用Kotlin开发,把annotationProcessor换成kapt即可。

如果是在Library中使用,还需要额外添加plugin。首先在项目的build.gradle中添加如下代码:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.jakewharton:butterknife-gradle-plugin:8.8.1'
    }
}

这里有一个问题需要注意,AndroidStudio 3.0及其以上版本对应的gradle与ButterKnife冲突,导致无法正常编译,github上也有这个问题,JakeWharton大神也给了相关解释,暂时的解决方法是将ButterKnife版本降低至8.4.0。

然后在你的module中添加即可

apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'

PS:与主项目还有一点不同,在注解引用资源id的时候需要使用R2文件,举个例子:@BindView(R2.id.user) EditText username;

使用

ButterKnife的初始化绑定

ButterKnife的初始化绑定也就是ButterKnife初始化入口

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化注册Activity
        ButterKnife.bind(this);
    }
}

ButterKnife.bind(this)就是ButterKnife的入口,绑定当前的ActivityActivitybind()方法必须在setContentView()之后调用,不然findViewById也找不到View啊!

当然,bind()方法还可以绑定其他的对象,基本包含了开发中的左右情况,如图:

Android ButterKnife使用指北_第2张图片
butterknife_bind.png

可以看出来ButterKnife可以在Activity以外的类中使用,例如:自定义ViewDialogFramgnetViewHolder;接下来看看在Fragment中如何绑定。

public class Main2ActivityFragment extends Fragment {

    private Unbinder unbinder;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_main2, container, false);
        //绑定Fragment以及View,并返回一个Unbinder对象
        unbinder = ButterKnife.bind(this,root);
        return root;
    }

    /** 在onDestroyView中使用Unbinder对象解除绑定 */
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        unbinder.unbind();
    }
}

由于Fragment的生命周期与Activity略有不同,在onCreateView中绑定Fragment,在onDestroyView中解除对Framgent的绑定。这里就是用ButterKnife.bind(this,root);返回的Unbinder对象,在FragmentView销毁时解除绑定即可。

Adapter中使用ButterKnife,有一些限制,因为注解的没法注解方法中的变量,所以在Adapter中使用需要配合ViewHolder一起使用,虽然在RecyclerView中已经强制使用ViewHolder了,但是如果使用ListView需要配合写ViewHolder类。这里以RecyclerView为例

public class MainAdapter extends RecyclerView.Adapter {

    private Context context;
    private List data;

    public MainAdapter(Context context, List data) {
        this.context = context;
        this.data = data;
    }

    @NonNull
    @Override
    public MainAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        View itemView = LayoutInflater.from(context).inflate(R.layout.item,viewGroup,false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
        //etc...
    }

    @Override
    public int getItemCount() {
        return data == null ? 0 : data.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder{
        @BindView(R.id.name) TextView name;

        ViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }
}

绑定View

绑定View的关键注解就是@BindView@BindViews,区别就在于一个还是多个view

@BindView(R.id.title) TextView title;
@BindView(R.id.subTitle) TextView subTitle;

@BindViews({R.id.text1, R.id.text2, R.id.text3}) TextView[] textArr;
@BindViews({R.id.text1, R.id.text2, R.id.text3}) List textList;

@BindViews注解所生成的对象是View[]或者是List

绑定事件

绑定view的各种事件监听,具体对应的作用如下:

注解名称 作用
@OnCheckedChanged 选中,选中取消,例如RadioGroup
@OnClick 点击事件
@OnEditorAction 软键盘的功能按键
@OnFocusChange 焦点改变
@OnItemClick Item被点击事件
@OnItemLongClick item长按,返回真则可以拦截onItemClick
@OnItemSelected Item被选择事件
@OnLongClick 长按事件
@OnPageChange 页面改变事件
@OnTextChanged EditText里面的文本变化事件
@OnTouch 触摸事件

接下来看看具体的使用方式

@OnClick

@OnClick注解可以绑定点击方法,参数就是Viewid或者是View的id的数组

@OnClick(R.id.commit)
void commit(){
    Log.i(TAG, "commit");
}

@OnClick(R.id.cannel)
void cannel(){
    Log.i(TAG, "cannel");
}

or

@OnClick({R.id.commit, R.id.cannel})
void click(View v) {
    switch (v.getId()) {
        case R.id.commit:
            Log.i(TAG, "commit");
            break;
        case R.id.cannel:
            Log.i(TAG, "cannel");
            break;
        default:
            break;
    }
}

@OnClick在自定义view 中绑定自身的点击事件的话是不需要传递viewid的。

public class TestButton extends AppCompatButton {

    public TestButton(Context context) {
        super(context);
        ButterKnife.bind(this);
    }

    public TestButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        ButterKnife.bind(this);
    }

    @OnClick
    public void onClick(){
        //do something.
    }
}

这里的示例代码ButterKnife.bind(this);在构造器调用`super()法之后,因为使用场景的比较简单。如果你在子View的布局里或者自定义view的构造方法里使用了inflate,你可以立刻调用此方法。或者,从XML inflate来的自定义view类型可以在onFinishInflate回调方法中使用它。

@OnLongClick

@OnLongClick注解的和@OnClick的使用方法相同,就不多做介绍了

@OnLongClick(R.id.delete)
boolean delete(){
    Log.i(TAG, "delete");
    return false;
}
@OnItemClick

这里所能绑定的item点击是AdapterView.OnItemClickListener的点击事件,所以只要是AdapterView的子类都是可以绑定这个事件的,例如:ListViewGridViewSpinner等等。

@OnItemClick(R.id.listView)
void onItemClick(AdapterView adapterView, View view, int i, long l) {
    //do something.
}
@OnItemLongClick

使用@OnItemLongClick绑定 item 长按点击和绑定 item 点击类似,也是遵循AdapterViewOnItemLongClickListener监听的

@OnItemLongClick(R.id.listView)
boolean onItemLongClick(AdapterView adapterView, View view, int i, long l) {
    //do something.
    return false;
}
@OnItemSelected

使用@OnItemSelected可以绑定 item 的 Selected 事件,由于 item 的 Selected 事件监听有onItemSelectedonNothingSelected两个方法,注解使用的方式有些不同。

@OnItemSelected(R.id.listView)
void onItemSelected(AdapterView adapterView, View view, int i, long l) {
    //do something.
}

@OnItemSelected(value = R.id.listView,callback = NOTHING_SELECTED)
void onNothingSelected(AdapterView adapterView) {
    //do something.
}

可以看到onItemSelected方法很简单,设置了id就可以了,但是onNothingSelected方法在注解中还添加了一个callback的参数,Callback@OnItemSelected的一个枚举有ITEM_SELECTEDNOTHING_SELECTED两个枚举类型,用于区分onItemSelectedonNothingSelected两个方法的,而默认提供的ITEM_SELECTED类型,所以在为onItemSelected方法添加注解时不需要设置callback的值。

绑定View事件方面,还有OnTouch@OnCheckedChanged@OnEditorAction@OnFocusChange@OnPageChange@OnTextChanged这几个注解,使用方式基本相同,就不多做解释了。

绑定资源

绑定资源到类成员上可以使用@BindBool@BindColor@BindDimen@BindDrawable@BindInt@BindString。使用时对应的注解需要传入对应的id资源,具体作用如下表

注解名称 作用
@BindAnim 绑定动画
@BindArray 绑定string中的数组
@BindBitmap 绑定bitmap资源
@BindBool 绑定boolean类型资源
@BindColor 绑定颜色
@BindDimen 绑定尺寸
@BindDrawable 绑定Drawable
@BindFloat 绑定Float(这个还没用明白)
@BindFont 绑定文字字体
@BindInt 绑定int类型数据
@BindString 绑定Sting类型数据

示例代码如下:

@BindAnim(R.anim.fade_in) Animation fadeIn;

@BindArray(R.array.strArr) String[] strArr;

@BindBitmap(R.mipmap.ic_launcher) Bitmap bitmap;

@BindBool(R.bool.test_bool) boolean testBoolean;

@BindColor(R.color.colorAccent) int colorAccent;

@BindDimen(R.dimen.round) int round;

@BindDrawable(R.drawable.ic_launcher) drawable;

@BindString(R.string.app_name) String meg;

@Optional

有一种情况,在使用绑定事件是,有可能提供的id没法找到targetView的情况,会报错。

java.lang.IllegalStateException: Required view 'delete' with ID 2131230776 for method 'delete' was not found. If this view is optional add '@Nullable' (fields) or '@Optional' (methods) annotation.

ButterKnife默认情况在绑定时间的targetView都不能为空,这时候就可以使用@Optional注解来标记的需要绑定的事件方法,让注入变成选择性的,如果targetView存在,则注入;不存在,则什么事情都不做。

@Optional注解是针对注解方法使用的,对于属性的话,可以使用android.support.annotation@Nullable注解。

findById

ButterKnife还提供了静态方法findById,方便开发者使用,现在已经被标记为过期了

butterknife_findById.png

上图可以看到所提供的方法返回的是泛型,这样就不需要进行类型强转,但是在appcompat-v7:26.1.0以后的版本的 AppCompatActivity中提供的findViewById返回的也是继承View的泛型,也不需要进行类型强转了。

public  T findViewById(@IdRes int id) {
    return this.getDelegate().findViewById(id);
}

apply

apply方法能对View(或者View集合、数组)进行一些操作

  • 定义一个显示的Action,通过ButterKnife应用到targetView上。
static final ButterKnife.Action SHOW = new ButterKnife.Action() {
    @Override
    public void apply(View view, int index) {
        view.setVisibility(View.VISIBLE);
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
    ButterKnife.apply(delete, SHOW);
}
  • 定义一个是否显示的Setter,通过ButterKnife应用到targetView上。
static final ButterKnife.Setter VISIBILITY = new ButterKnife.Setter() {
    @Override
    public void set(View view, Boolean value, int index) {
        view.setVisibility(value ? View.VISIBLE : View.GONE);
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
    ButterKnife.apply(delete, VISIBILITY,true);
}
  • 设置ViewProperty
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
    ButterKnife.apply(delete, View.ALPHA, 0.0f);
}

基本上可以理解Action是对View的行为操作,Setter则是对View的设置操作

光这么说可能不明白,看看联想的出来的api方法。

Android ButterKnife使用指北_第3张图片
butterknife_apply.png

一共有12个方法,其实只是对apply方法的重载;第一个参数就代表targetView,可以是ViewView[]或者是List;第二个参数可以是ActionSetterProperty,如果有第三个则是对SetterProperty设置的值。

/** An action that can be applied to a list of views. */
public interface Action {
  /** Apply the action on the {@code view} which is at {@code index} in the list. */
  @UiThread
  void apply(@NonNull T view, int index);
}
/** A setter that can apply a value to a list of views. */
public interface Setter {
  /** Set the {@code value} on the {@code view} which is at {@code index} in the list. */
  @UiThread
  void set(@NonNull T view, V value, int index);
}

在看看ActionSetter的源码的注释就明白了他们的作用了,Action就是应用一个行为到view上,Setter则是应用一值到view上。

以上就是ButterKnife使用方式,应该算是比较详细的使用教程了

Butterknife插件:zelezny

zelezny可以帮助我们快捷生成ButterKnife的注解代码,首先需要安装zelezny插件(如果已经安装请忽略安装过程)

  1. 打开AndroidStudio设置选中Plugins选项
  2. 搜索zelezny
  3. 点击红框3
  4. 选择Android ButterKnife Zelezny
  5. 点击安装,安装完之后重启AndroidStudio即可
Android ButterKnife使用指北_第4张图片
安装zelezny

安装完成之后,选中需要使用注解的layout id,右击点击Generate,选择Generate ButterKnife Injections。会出现以下弹框,可以选择需要注解的View,可以选择是否需要生成@OnClick注解,点击Confirm就会生成对应的注解代码。

Android ButterKnife使用指北_第5张图片

这里也可以看到,zelezny能支持的绑定事件只有@OnClick,但是还是能帮助我们节省时间的。

PS:如果觉得ButterKnife是麻烦的话,可以安装ButterKnifeKiller插件,该插件可以相应的把注解代码替换成findViewById的代码。

你可能感兴趣的:(Android ButterKnife使用指北)