Android Butter Knife 框架——最好用的View注入

最近在看GitHub上的一些代碼時(shí),發(fā)現(xiàn)很多工程都用到了Butter Knife這個(gè)框架,能節(jié)省很多代碼量。像findViewById這種代碼就不用再出現(xiàn)了,而且這個(gè)框架也提供了很多其他有用的注解。
抱著學(xué)習(xí)的心態(tài)看了官網(wǎng)上的文檔,挺簡(jiǎn)單,也很實(shí)用,決定以后就用這個(gè)庫(kù)了。
下面是我翻譯的官方文檔,諸位看官輕噴。官方文檔也挺簡(jiǎn)單,英語(yǔ)好的不好的,都建議去看看原文。

另外注意,這個(gè)庫(kù)的版本更新挺快的,我第一次用到的時(shí)候是7.1.0,而現(xiàn)在的最新版本已經(jīng)是8.5.1了,也就是說(shuō)大家可能需要去ButterKnife的Github查看最近的版本。

image.png

Butter Knife

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

Butter Knife,專門為Android View設(shè)計(jì)的綁定注解,專業(yè)解決各種findViewById。

簡(jiǎn)介

對(duì)一個(gè)成員變量使用@BindView注解,并傳入一個(gè)View ID, ButterKnife 就能夠幫你找到對(duì)應(yīng)的View,并自動(dòng)的進(jìn)行轉(zhuǎn)換(將View轉(zhuǎn)換為特定的子類):

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使用再編譯時(shí)生成的代碼來(lái)執(zhí)行View的查找,因此不必?fù)?dān)心注解的性能問(wèn)題。調(diào)用bind來(lái)生成這些代碼,你可以查看或調(diào)試這些代碼。

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

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。使用時(shí)對(duì)應(yīng)的注解需要傳入對(duì)應(yīng)的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的幾個(gè)重載,只要傳入跟布局,便可以在任何對(duì)象中使用注解綁定。

例如在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;
    }
}

還有一種比較常見的場(chǎng)景,就是在ListView的Adapter中,我們常常會(huì)使用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);
        }
    }
}

你能在提供給的例子中找到上述實(shí)現(xiàn)。

ButterKnife.bind的調(diào)用可以被放在任何你想調(diào)用findViewById的地方。

提供的其他綁定API:

  • 使用Activity作為跟布局在任意對(duì)象中進(jìn)行綁定。如果你使用了類似MVC的編程模式,你可以對(duì)controller使用它的Activity用ButterKnife.bind(this, activity)進(jìn)行綁定。

  • 使用ButterKnife.bind(this)綁定一個(gè)布局的子布局。如果你在布局中使用了<merge>標(biāo)簽并且在自定義的控件構(gòu)造時(shí)inflate這個(gè)布局,你可以在inflate之后立即調(diào)用它?;蛘?,你可以在onFinishInflate()回調(diào)中使用它。

View 列表

你可以一次性將多個(gè)views綁定到一個(gè)List或數(shù)組中:

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

apply函數(shù),該函數(shù)一次性在列表中的所有View上執(zhí)行一個(gè)動(dòng)作:

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

ActionSetter接口能夠讓你指定一些簡(jiǎn)單的動(dòng)作:

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方法進(jìn)行設(shè)置:

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

監(jiān)聽器綁定

使用本框架,監(jiān)聽器能夠自動(dòng)的綁定到特定的執(zhí)行方法上:

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

而監(jiān)聽器方法的參數(shù)都時(shí)可選的:

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

指定一個(gè)特定的類型,Butter Knife也能識(shí)別:

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

可以指定多個(gè)View ID到一個(gè)方法上,這樣,這個(gè)方法就成為了這些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時(shí),綁定事件監(jiān)聽不需要指定ID

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

重置綁定:

Fragment的生命周期與Activity不同。在Fragment中,如果你在onCreateView中使用綁定,那么你需要在onDestroyView中設(shè)置所有view為null。為此,ButterKnife返回一個(gè)Unbinder實(shí)例以便于你進(jìn)行這項(xiàng)處理。在合適的生命周期回調(diào)中調(diào)用unbind函數(shù)就可完成重置。

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();
    }
}

可選綁定:

在默認(rèn)情況下, @bind和監(jiān)聽器的綁定都是必須的,如果目標(biāo)view沒(méi)有找到的話,Butter Knife將會(huì)拋出個(gè)異常。

如果你并不想使用這樣的默認(rèn)行為而是想創(chuàng)建一個(gè)可選的綁定,那么你只需要在變量上使用@Nullable注解或在函數(shù)上使用@Option注解。

注意:任何名為@Nullable的注解都可以使用在變量上。但還時(shí)強(qiáng)烈建議使用Android注解庫(kù)中的@Nullable。使用這個(gè)庫(kù)對(duì)你的代碼有很多好處,關(guān)于該庫(kù)的詳情,可以點(diǎn)擊此處:Android Tools Project

@Nullable @BindView(R.id.might_not_be_there) TextView mightNotBeThere;

@Optional @OnClick(R.id.maybe_missing) void onMaybeMissingClicked() {
    // TODO ...
}

對(duì)于包含多個(gè)方法的監(jiān)聽

當(dāng)一個(gè)監(jiān)聽器包含多個(gè)回調(diào)函數(shù)時(shí),使用函數(shù)的注解能夠?qū)ζ渲腥魏我粋€(gè)函數(shù)進(jìn)行綁定。每一個(gè)注解都會(huì)綁定到一個(gè)默認(rèn)的回調(diào)。你也可以使用callback參數(shù)來(lái)指定一個(gè)其他函數(shù)作為回調(diào)。

@OnItemSelected(R.id.list_view)
void onItemSelected(int position) {
    // TODO ...
}

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

福利

Butter Knife提供了一個(gè)findViewById的簡(jiǎn)化代碼:findById,用這個(gè)方法可以在View、ActivityDialog中找到想要View,而且,該方法使用的泛型來(lái)對(duì)返回值進(jìn)行轉(zhuǎn)換,也就是說(shuō),你可以省去findViewById前面的強(qiáng)制轉(zhuǎn)換了。

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);

如果你只是使用這個(gè)方法,可以使用靜態(tài)引入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.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容