1. 程式人生 > >Android Butter Knife 框架——最好用的View注入

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

最近在看GitHub上的一些程式碼時,發現很多工程都用到了Butter Knife這個框架,能節省很多程式碼量。像findViewById這種程式碼就不用再出現了,而且這個框架也提供了很多其他有用的註解。
抱著學習的心態看了官網上的文件,挺簡單,也很實用,決定以後就用這個庫了。
下面是我翻譯的官方文件,諸位看官輕噴。官方文件也挺簡單,英語好的不好的,都建議去看看原文。


image.png

Butter Knife

Butter Knife,專門為Android View設計的繫結註解。

簡介

使用 @Bind註解並傳入一個View ID,Butter Knife 就可以找到並且自動地對你的佈局中的View進行轉換並繫結到類成員上。

class ExampleActivity extends Activity {
  @Bind(R.id.title) TextView title;
  @Bind(R.id.subtitle) TextView subtitle;
  @Bind(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的程式碼是生成的,因此不必擔心註解的效能問題。呼叫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 {
  @Bind(R.id.button1) Button button1;
  @Bind(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 {
    @Bind(R.id.title) TextView name;
    @Bind(R.id.job_title) TextView jobTitle;

    public ViewHolder(View view) {
      ButterKnife.bind(this, view);
    }
  }
}

你能在提供給的例子中找到上述程式碼。

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

提供的其他繫結API:

  • 使用Activity在任意物件中進行繫結。如果你使用了類似MVC的程式設計模式,你可以使用ButterKnife.bind(this, activity)在Controller中進行繫結

  • 使用ButterKnife.bind(this)繫結一個佈局的子佈局到變數上。如果你在佈局中使用了<merge>標籤並且在自定義的控制元件構造時inflate這個佈局,你可以在inflate之後立即呼叫它。或者,你可以在onFinishInflate()回撥中使用它。

View 列表

你可以一次性將多個views繫結到一個List或陣列中:

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

使用這種繫結時,你可以使用apple函式。該函式相當於將在這個列表中每一個元素上進行呼叫:

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也可以使用到apple方法中:

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

監聽器繫結

在Butter Knife中,監聽器也自動地配置到方法上:

@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!");
}

同時指定多個id的控制元件到同一個事件監聽上:

@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繫結注意:

Fragment的生命週期與Activity不同。在Fragment中,我們可能會在onCreateView中繫結一個佈局,並在onDestroyView中設定所有view為null.此時,你就需要Butter Knife提供的undind函式來做此事。

public class FancyFragment extends Fragment {
  @Bind(R.id.button1) Button button1;
  @Bind(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;
  }

  @Override public void onDestroyView() {
    super.onDestroyView();
    ButterKnife.unbind(this);
  }
}

可選的繫結:

在預設情況下, @bind和監聽器的繫結都是需要的,如果目標view沒有找到的話,Butter Knife將會丟擲一個異常。

如果你並不想接收到這樣的資訊,那麼就在你的方法或變數上使用@Nullable吧。

這個註解來自於Android的"support-annotations"庫,使用這些註解對你的程式碼有好處,關於該庫的詳情,可以點選此處:Android Tools Project

使用該註解後的程式碼如下:

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

@Nullable @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 ...
}

簡單的findViewById

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

使用

Butter Knife的程式碼和例子都可以在GitHub上找到:Butter Knife GitHub

檢視Butter Knife的API文件現在也可以檢視:Butter Knife Javadoc

MAVEN

宣告以下依賴庫:

<dependency>
  <groupId>com.jakewharton</groupId>
  <artifactId>butterknife</artifactId>
  <version>(insert latest version)</version>
</dependency>

GRADLE

compile 'com.jakewharton:butterknife:(insert latest version)'

注意在build.gradle中取消lint的如下警告:

lintOptions {
  disable 'InvalidPackage'
}

你也有可能需要如下配置:

packagingOptions {
  exclude 'META-INF/services/javax.annotation.processing.Processor'
}

ProGuard

Butter Knife使用動態生成的程式碼,這可能使ProGuard認為這些程式碼是無用的。為了避免這些程式碼被混淆,你可以新增如下程式碼到你的ProGuard中:

-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }

-keepclasseswithmembernames class * {
    @butterknife.* <fields>;
}

-keepclasseswithmembernames class * {
    @butterknife.* <methods>;
}

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.