美文网首页AndroidAndroidAndroid 入门进阶
Android Butter Knife 框架——最好用的Vie

Android Butter Knife 框架——最好用的Vie

作者: 李斯维 | 来源:发表于2015-10-16 11:00 被阅读80960次

    最近在看GitHub上的一些代码时,发现很多工程都用到了Butter Knife这个框架,能节省很多代码量。像findViewById这种代码就不用再出现了,而且这个框架也提供了很多其他有用的注解。
    抱着学习的心态看了官网上的文档,挺简单,也很实用,决定以后就用这个库了。
    下面是我翻译的官方文档,诸位看官轻喷。官方文档也挺简单,英语好的不好的,都建议去看看原文。

    另外注意,这个库的版本更新挺快的,我第一次用到的时候是7.1.0,而现在的最新版本已经是8.5.1了,也就是说大家可能需要去ButterKnife的Github查看最近的版本。

    image.png

    Butter Knife

    本文章翻译自:http://jakewharton.github.io/butterknife/

    Butter Knife,专门为Android View设计的绑定注解,专业解决各种findViewById

    简介

    对一个成员变量使用@BindView注解,并传入一个View ID, ButterKnife 就能够帮你找到对应的View,并自动的进行转换(将View转换为特定的子类):

    class ExampleActivity extends Activity {
        @BindView(R.id.title)  TextView title;
        @BindView(R.id.subtitle) TextView subtitle;
        @BindView(R.id.footer) TextView footer;
    
        @Override public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.simple_activity);
            ButterKnife.bind(this);
            // TODO Use fields...
        }
    }
    

    与缓慢的反射相比,Butter Knife使用再编译时生成的代码来执行View的查找,因此不必担心注解的性能问题。调用bind来生成这些代码,你可以查看或调试这些代码。

    例如上面的例子,生成的代码大致如下所示:

    public void bind(ExampleActivity activity) {
        activity.subtitle = (android.widget.TextView) activity.findViewById(2130968578);
        activity.footer = (android.widget.TextView) activity.findViewById(2130968579);
        activity.title = (android.widget.TextView) activity.findViewById(2130968577);
    }
    

    资源绑定

    绑定资源到类成员上可以使用@BindBool@BindColor@BindDimen@BindDrawable@BindInt@BindString。使用时对应的注解需要传入对应的id资源,例如@BindString你需要传入R.string.id_string的字符串的资源id。

    class ExampleActivity extends Activity {
      @BindString(R.string.title) String title;
      @BindDrawable(R.drawable.graphic) Drawable graphic;
      @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
      // ...
    }
    

    在非Activity中使用绑定

    Butter Knife提供了bind的几个重载,只要传入跟布局,便可以在任何对象中使用注解绑定。

    例如在Fragment中:

    public class FancyFragment extends Fragment {
        @BindView(R.id.button1) Button button1;
        @BindView(R.id.button2) Button button2;
    
        @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fancy_fragment, container, false);
            ButterKnife.bind(this, view);
            // TODO Use fields...
            return view;
        }
    }
    

    还有一种比较常见的场景,就是在ListView的Adapter中,我们常常会使用ViewHolder:

    public class MyAdapter extends BaseAdapter {
        @Override public View getView(int position, View view, ViewGroup parent) {
            ViewHolder holder;
            if (view != null) {
                holder = (ViewHolder) view.getTag();
            } else {
                view = inflater.inflate(R.layout.whatever, parent, false);
                holder = new ViewHolder(view);
                view.setTag(holder);
            }
    
            holder.name.setText("John Doe");
            // etc...
    
            return view;
        }
    
        static class ViewHolder {
            @BindView(R.id.title)
            TextView name;
            @BindView(R.id.job_title) TextView jobTitle;
    
            public ViewHolder(View view) {
                ButterKnife.bind(this, view);
            }
        }
    }
    

    你能在提供给的例子中找到上述实现。

    ButterKnife.bind的调用可以被放在任何你想调用findViewById的地方。

    提供的其他绑定API:

    • 使用Activity作为跟布局在任意对象中进行绑定。如果你使用了类似MVC的编程模式,你可以对controller使用它的Activity用ButterKnife.bind(this, activity)进行绑定。

    • 使用ButterKnife.bind(this)绑定一个布局的子布局。如果你在布局中使用了<merge>标签并且在自定义的控件构造时inflate这个布局,你可以在inflate之后立即调用它。或者,你可以在onFinishInflate()回调中使用它。

    View 列表

    你可以一次性将多个views绑定到一个List或数组中:

    @BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name })
    List<EditText> nameViews;
    

    apply函数,该函数一次性在列表中的所有View上执行一个动作:

    ButterKnife.apply(nameViews, DISABLE);
    ButterKnife.apply(nameViews, ENABLED, false);
    

    ActionSetter接口能够让你指定一些简单的动作:

    static final ButterKnife.Action<View> DISABLE = new ButterKnife.Action<View>() {
        @Override public void apply(View view, int index) {
            view.setEnabled(false);
        }
    };
    static final ButterKnife.Setter<View, Boolean> ENABLED = new ButterKnife.Setter<View, Boolean>() {
        @Override public void set(View view, Boolean value, int index) {
            view.setEnabled(value);
        }
    };
    

    Android中的Property属性也可以使用apply方法进行设置:

    ButterKnife.apply(nameViews, View.ALPHA, 0.0f);
    

    监听器绑定

    使用本框架,监听器能够自动的绑定到特定的执行方法上:

    @OnClick(R.id.submit)
    public void submit(View view) {
      // TODO submit data to server...
    }
    

    而监听器方法的参数都时可选的:

    @OnClick(R.id.submit)
    public void submit() {
        // TODO submit data to server...
    }
    

    指定一个特定的类型,Butter Knife也能识别:

    @OnClick(R.id.submit)
    public void sayHi(Button button) {
        button.setText("Hello!");
    }
    

    可以指定多个View ID到一个方法上,这样,这个方法就成为了这些View的共同事件处理。

    @OnClick({ R.id.door1, R.id.door2, R.id.door3 })
    public void pickDoor(DoorView door) {
        if (door.hasPrizeBehind()) {
            Toast.makeText(this, "You win!", LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "Try again", LENGTH_SHORT).show();
        }
    }
    

    自定义View时,绑定事件监听不需要指定ID

    public class FancyButton extends Button {
        @OnClick
        public void onClick() {
            // TODO do something!
        }
    }
    

    重置绑定:

    Fragment的生命周期与Activity不同。在Fragment中,如果你在onCreateView中使用绑定,那么你需要在onDestroyView中设置所有view为null。为此,ButterKnife返回一个Unbinder实例以便于你进行这项处理。在合适的生命周期回调中调用unbind函数就可完成重置。

    public class FancyFragment extends Fragment {
        @BindView(R.id.button1) Button button1;
        @BindView(R.id.button2) Button button2;
        private Unbinder unbinder;
    
        @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fancy_fragment, container, false);
            unbinder = ButterKnife.bind(this, view);
            // TODO Use fields...
            return view;
        }
    
        @Override public void onDestroyView() {
            super.onDestroyView();
            unbinder.unbind();
        }
    }
    

    可选绑定:

    在默认情况下, @bind和监听器的绑定都是必须的,如果目标view没有找到的话,Butter Knife将会抛出个异常。

    如果你并不想使用这样的默认行为而是想创建一个可选的绑定,那么你只需要在变量上使用@Nullable注解或在函数上使用@Option注解。

    注意:任何名为@Nullable的注解都可以使用在变量上。但还时强烈建议使用Android注解库中的@Nullable。使用这个库对你的代码有很多好处,关于该库的详情,可以点击此处:Android Tools Project

    @Nullable @BindView(R.id.might_not_be_there) TextView mightNotBeThere;
    
    @Optional @OnClick(R.id.maybe_missing) void onMaybeMissingClicked() {
        // TODO ...
    }
    

    对于包含多个方法的监听

    当一个监听器包含多个回调函数时,使用函数的注解能够对其中任何一个函数进行绑定。每一个注解都会绑定到一个默认的回调。你也可以使用callback参数来指定一个其他函数作为回调。

    @OnItemSelected(R.id.list_view)
    void onItemSelected(int position) {
        // TODO ...
    }
    
    @OnItemSelected(value = R.id.maybe_missing, callback = NOTHING_SELECTED)
    void onNothingSelected() {
        // TODO ...
    }
    

    福利

    Butter Knife提供了一个findViewById的简化代码:findById,用这个方法可以在ViewActivityDialog中找到想要View,而且,该方法使用的泛型来对返回值进行转换,也就是说,你可以省去findViewById前面的强制转换了。

    View view = LayoutInflater.from(context).inflate(R.layout.thing, null);
    TextView firstName = ButterKnife.findById(view, R.id.first_name);
    TextView lastName = ButterKnife.findById(view, R.id.last_name);
    ImageView photo = ButterKnife.findById(view, R.id.photo);
    

    如果你只是使用这个方法,可以使用静态引入ButterKnife.findById方法。

    下载

    dependencies {
        compile 'com.jakewharton:butterknife:8.5.1'
        annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
    }
    

    License

    Copyright 2013 Jake Wharton
    
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    
    http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    相关文章

      网友评论

      • 牟乘风:有没有人能告诉我使用Butter Knife的意义,感觉为了省去findViewById而学习和引进一个库,不太值得,要是真想省去这些find和set,搞个代码自动生成插件,一键搞定
        逗逼一号:@牟乘风 初步看了一下,不止是省略一点点代码额。可以打包式的封装,还蛮官方的
      • 彼岸青园:写的很棒!
      • 若无初见:为什么老是空指针啊 烦死了。
        若无初见:@liu灰1314 不知道 反正一放就出问题。放到app的module就没问题
        liu灰1314:@若无初见 可以放在library里面的
        若无初见:自己回答下: 原来butterknife和dbflow一样不能放在library module里面
      • 41521d37feca:注入不可以继承,不可以继承,继承……
      • xiaobinZh:ButterKnife 功能太弱, AndroidAnnotation太重影响编译速度; 不如试试 这个: https://github.com/gybin02/Jet Jet 代码注入;
      • jasonlee3652:有一个地方翻译应该有问题:

        •使用Activity在任意对象中进行绑定。如果你使用了类似MVC的编程模式,你可以使用ButterKnife.bind(this, activity)在Controller中进行绑定

        Controller代表的是activity,直接用ButterKnife.bind(activity)绑定即可,原文的意思应该是如果你想在其他类中使用注解获取Controller(activity)中的控件,应该传入该类的对象this,和Controller(activity)。

      • 真胖大海:在adapter中使用bind后需要在恰当时候调用unbind吗?
      • JakeyYe:有遇到过这个问题的吗,RecyclerView.ViewHolder 无法绑定 view 与 ButterKnife,有什么好解决方法吗?
        李斯维:@JakeyYe 我在RecyclerView.ViewHolder中用过,是可以的。在ViewHolder的构造函数中用ButterKnife.bind(this, itemView); 你是怎么用的,发个代码看一下
      • Alex_Code:Error:Could not find com.android.support:support-annotations:24.1.0.
        Required by:
        FingertipReimbursement:Fingertip:unspecified > com.jakewharton:butterknife:8.4.0
        FingertipReimbursement:Fingertip:unspecified > com.jakewharton:butterknife-compiler:8.4.0 > com.jakewharton:butterknife-annotations:8.4.0 哥们这个问题遇到过吗?
        Speronie:androidSDK的extras下面找support/support-annotations包,放到项目里依赖试一下
        Wonderland_ds:你这个问题怎么解决的,我也遇到了
        码夫:@alex老码农 我遇到了,蛋疼中,有解么
      • 真胖大海:你不使用unbinde解绑定吗?
      • 渐行渐远的术士:@InjectView(R.id.title,R.id.subtitle,R.id.footer)
        TextView title,subtitle,footer;
        这样子的注解是不是会出错?
        是不是只能一个一个的注解?
        快乐小哥:你不会 写写运行一下试试?
        Moo_Night:@不过软考不改备注
        @Bind({ R.id.first_name, R.id.middle_name, R.id.last_name })
        List<EditText> nameViews; 不知道这个是不是你想要的,上文作者是这么写的
      • 8321:问下作者,当使用ButterKnife已经绑定一个Activity,之后在这个Activity还想要绑定一个View,这种情况该怎么办?谢谢
      • b89c80620fdc:干的漂亮
      • michaellct:http://androidannotations.org/ 这个更简洁啊 原理也一样
        michaellct:@EvangelineWong 嗯 AndroidAnnotation就是有点重 各有利弊
        e49d831def8b:@michaellct 使用AndroidAnnotation,需要写的代码精简了不少。
      • c7b8eed9368e:以前用的是thinkAndroid的绑定,现在可以换换这个了……

      本文标题:Android Butter Knife 框架——最好用的Vie

      本文链接:https://www.haomeiwen.com/subject/hutacttx.html