Android ButterKnife使用指北

图片发自简书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()方法还可以绑定其他的对象,基本包含了开发中的左右情况,如图:

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<T> extends RecyclerView.Adapter<MainAdapter.ViewHolder> {

    private Context context;
    private List<T> data;

    public MainAdapter(Context context, List<T> 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<TextView> textList;

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

绑定事件

绑定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 extends View> T findViewById(@IdRes int id) {
    return this.getDelegate().findViewById(id);
}

apply

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

  • 定义一个显示的Action,通过ButterKnife应用到targetView上。
static final ButterKnife.Action<View> SHOW = new ButterKnife.Action<View>() {
    @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<View, Boolean> VISIBILITY = new ButterKnife.Setter<View, Boolean>() {
    @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方法。

butterknife_apply.png

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

/** An action that can be applied to a list of views. */
public interface Action<T extends View> {
  /** 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<T extends View, V> {
  /** 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即可
安装zelezny

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 儿子从外边疯回来,满脸满身的汗,没换鞋子没洗没擦没换衣服就钻进了卧室,关了门。我悄悄推开一条门缝,见他趴在床上,后...
    申振柱阅读 578评论 2 9
  • 入了一款非常气质的Edgii UGG小羊皮尖头鞋,第一次下单买的白色,试穿过后果断把黑色也入了,个人比较偏好时尚...
    96somi阅读 654评论 1 0
  • 清明是二十四节气之一,含有天气晴朗、空气清新明洁、逐渐转暖、草木繁茂之意。清明节又叫踏青节,在仲春与暮春之交。是中...
    新思界再出发阅读 701评论 3 1