Edited at

Butter Knifeの紹介

More than 3 years have passed since last update.

※v7がリリースされたため、ちょっと情報が古いです。 Butter Knife v7への移行 もあわせて参照してください

Android用のView Injectionライブラリである Butter Knife について解説します (といいますか、サイトに書いてあることをほとんどそのまま日本語にしただけです) 。


概要

Butter Knifeは ActionBarSherlock などでお馴染みの Square のJake WhartonさんによるAndroid用のView Injectionライブラリです。


使い方

このライブラリの目的が、


  • Activity, ViewのfindViewByIdを楽に書く

  • ViewのonClickListenerなどを楽に書く

というシンプルなものなので、簡単に理解できると思います。以下は見慣れたActivityのコードです。

public class MainActivity extends Activity {

TextView mTextView;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.activity_main_textview);
}
}

Viewが1つや2つであればよいのですが、増えていくとsetContentViewのあとにfindViewByIdがずらっとならんだ見辛いコードになります。

これを以下のように書くことができます。

public class MainActivity extends Activity {

@InjectView(R.id.activity_main_textview)
TextView mTextView;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);
ButterKnife.inject(this);
// 以下にmTextViewを使ったコード (mTextView.setText()とか)を
}
}

@InjectView(int id) でViewのResource Idを指定すると ButterKnife.inject(Activity)mTextView = (TextView) findViewById(R.id.activity_main_textview) と同じ処理が行われます。

また、

public class MainActivity extends Activity {

Button mButton;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.activity_main_button);
mButton.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
onClickButton();
}
});
}

void onClickButton() {
// ボタンが押されたときの処理
}
}

findViewByIdして、OnClickListenerをセットして…といういつものButtonの処理も、Buttonの数が増えてくるとたいへん見辛いコードになりがちですが、これも、

public class MainActivity extends Activity {

@OnClick(R.id.activity_main_button)
void onClickButton() {
// ボタンが押されたときの処理
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);
ButterKnife.inject(this);
}
}

ButtonのonClick時に呼びたいメソッドに @OnClick(int id) でViewのResource Idを指定しておくだけでよくなります。

複数のButtonのonClickイベントで共通する処理を書きたい時にも、

@OnClick({ R.id.activity_main_button1,

R.id.activity_main_button2,
R.id.activity_main_button3 })
void onClickButton(CustomButton button) {
if (button.isHoge()) {
// ...
}
}

このように書けるので便利です。@InjectView@OnClick もViewを継承したクラスであれば使うことができますのでカスタムViewでもOK。

また、Injectionの対象となるViewを指定することもできるので、Fragmentでも、

public class MainFragment extends Fragment {

@InjectView(R.id.fragment_main_textview)
TextView mTextView;

@Override
View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
ButterKnife.inject(this, view);
// 以下にmTextViewを使った処理を

return view;
}

@Override
void onDestroyView() {
super.onDestroyView();

ButterKnife.reset(this);
}
}

のように使えます (Fragmentの場合はonDestroyViewでButterKnife.resetを呼んでください)。

同様に、ListViewなどで使うViewHolderパターンも以下のように簡潔に書けます。

public class MyListAdapter 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.list_item, parent, false);
holder = new ViewHolder(view);
view.setTag(holder);
}

// 以下にViewHolderの中身を使った処理を

holder.mTitleView.setText("title");
holder.mNameView.setText("my name");

return convertView;
}

static class ViewHolder {

@InjectView(R.id.title)
TextView mTitleView;

@InjectView(R.id.name)
TextView mNameView;

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

}

その他にも以下を扱うことが出来ます。


  • ViewのOnLongClick

  • ListViewなどのOnItemClick, OnItemLongClick

  • TextViewのOnEditorAction

  • CompoundButtonのOnCheckedChanged


おまけ

同じようなコードをBaseActvityの中やHogeUtilの中に書いたりしている方も多いと思われる View#findViewById(int id)Activity#findViewById(int id) のヘルパーもあります。

View childView = getLayoutInflater().inflate(R.layout.child, null);

TextView childText = ButterKnife.findById(childView, R.id.child_text);
ImageView childImage = ButterKnife.findById(childeView, R.id.child_image);


IDEのセットアップ



  • Butter Knife のサイトからjarを落としてきます

  • jarをlibs以下においてJavaのBuild pathを通して…といういつもの設定をします

  • Annotation Processingの設定を行います


    • IntelliJ IDEA / Android Studioの場合はそのまま動くらしい(使っていないので試してない)です。動かない場合は こちら を参照

    • Eclipseの場合は こちらを参照



  • .apt_generated/ に XXX$$ViewInjector.java のようなファイルが生成されていればOKです。生成されていない場合はprojectをcleanしたりIDEを再起動するとうまくいったりします


ProGuard

ProGuardを使う場合は proguard-project.txt などで以下の設定をしておきます。

-dontwarn butterknife.internal.**

-keep class **$$ViewInjector { *; }
-keepnames class * { @butterknife.InjectView *;}


開発の経緯など

Jake WhartonさんのGoogle+の投稿. Mar 6, 2013

People really like the concept of view "injection" for Android 

and it often comes up when talking about Dagger.
Last night, I wrote a view injection library which uses
annotation processing as a little experiment and half-joke.

Dagger について他の開発者と話をしているときにViewのInjectionの話題がよく出てくるので、Annotation Processingで試しにつくってみました、ぐらいの感じでスタートしたようです。

RoboGuice uses an annotation in a different package and reflection 

at runtime whereas this uses code generation along with a static method call.
It uses the same annotation name so people feel at home (similar to how @Inject is a standard).
This is for people who use Dagger or Guice (or no dependency injection)
but can't bring themselves to write findViewById calls.

で、Guice (RoboGuice) との違いは、GuiceはAnnotationのルックアップやフィールドへの値の代入にReflectionを使っている(ので、遅い)けど、Butter Knifeはそうじゃなくでビルド時にコードが生成されるよ、と (Reflectionしているのは ButterKnife.inject ぐらい)。

Jake WhartonさんのGoogle+の投稿. Mar 26, 2013

Ondřej Kroupa:

Seems nice, but what is main advantage to android-annotations library?

Jake Wharton:
This is a tiny, purpose-built library whereas AA is an application framework.

AndroidAnnotations でも @ViewById@Click を使って同じことができるけど違いは? という質問に対しては、AndroidAnnotationsはいろいろなことができるアプリケーションフレームワークだけど、Butter KnifeはViewのInjectionだけに特化したシンプルなライブラリだよ、と。

他の方がButter Knifeの紹介記事を書かれているのですが、

5 Reasons You Should Use Butterknife For Android

3. You find AndroidAnnotations to be overkill for your project.

AndroidAnnotations is a cool idea. It has tons of features
and more are being added all the time.
Depending on which camp you are in, adding features can be both a blessing and curse.
If you want to use annotations to inject everything imaginable
then definitely give AndroidAnnotations a look.
I am not 100% sold on the annotating and injecting of everything just yet.
For now views, viewholders, and OnClickListeners is enough for me.

AndroidAnnotationsを使ってるならそれで良いし、もし、このプロジェクトはそこまでは必要ないなあと思ったらButter Knifeを使う、という使い分けをすればいいんじゃない、と。

AndroidAnnotationsを使うと、HogeActivityに対してInjectionされたHogeActivity_ というクラスが生成されるので、startActivity(new Intent(this, HogeActivity_.class)) と書いたり、AndroidManifest.xmlに <activity android:name=".HogeActivity_" />と書いたりしないといけないのが個人的にはちょっと嫌だなあというのがあります (そういえばAndroidAnnotationsを作った Pierre-Yves Ricau さんもSquareですね)

私もfindViewByIdをさぼるぐらいならあえて使う必要はないかなあと思ってたのですが、使い始めてみるとこれが大変楽なのでおすすめです。

最後に余談ですが、この "Butter Knife" というネーミングはとても気が利いていて良いなあと思いました。


  • Dagger (短刀) はいろいろなことができる。切ったり刺したり削ったり

  • Butter knife (バターナイフ) はパンにバターを塗ることぐらいしかできない。が、パンにバターを塗るときにはとても便利

ということで、なんでもできる(Dependency Injectorである) Dagger に対して、パンにバターを塗る(ViewをInjectする) ことしかできないけど、その用途であればとても便利なものだよ、という意味でButter Knifeという名前にしたんじゃないかなあと。

Butter Knife - View "injection" library for Android