Skip to content

Instantly share code, notes, and snippets.

@yyaammaa
Last active June 30, 2017 02:50
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save yyaammaa/7480613 to your computer and use it in GitHub Desktop.
Save yyaammaa/7480613 to your computer and use it in GitHub Desktop.
Butter Knifeの紹介

Butter Knifeの紹介

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment