androidannotations虎の巻

これぐらいわかれば使えそうだということをまとめてみた。

Android Studioでの環境設定

前に書いた。

http://blog.choilabo.com/20140605/368

xmlと変数とのヒモ付

以下のように簡単にかける。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imgView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/txtView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ListView
        android:id="@+id/lstView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

こういうxmlファイルが有った場合に

@EActivity(R.layout.activity_main)
public class MainAcitvity extends Acitvity {

    // R.idで指定できる
    @ViewById(R.id.imgView)
    protected ImageView imgView;

    // 指定を除外した場合は変数名=idとして紐付けられる
    @ViewById
    protected TextView txtView;

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

    @AfterViews
    protected void init() {
        // findViewByIdで@ViewByIdで指定された変数に値が入った後に
        // ここが呼ばれる。
        // onCreateで変数を使うとぬるぽで落ちるので注意
    }

    @Click(R.id.btn)
    protected void btnClick(View view) {
        // クリックイベントのバインドも簡単にできる。
        // 裏ではR.id.btnのsetOnClickListenerが呼ばれる
    }

    @ItemClick(R.id.lstView)
    protected void lstViewItemClick(MyItem clickedItem) {
        // リストViewのItemClickもできる
    }
}

こうかける。上はアクティビティの例だけど、Fragment(@EFragment)、ViewGroup(@EViewGroup)でも同じようなことが出来る。LinearLayoutとか作るときに便利。

AndroidManifest.xmlの書き方

acitivtyの指定は自動生成されるソースのファイル名を書く必要がある。そのためActivity名に_がついたものを書く必要がある。注意するのはそれぐらい。

    <!-- MainActivityが自動生成されるとMainActivity_となる -->
    <activity
        android:name=".activity.MainActivity_"
        android:screenOrientation="portrait" />

Background / UiThread

ちょっとした非同期実装ならアノテーションで書くことが出来る。EActivity等で使用することが出来る。

    @Background
    protected void getHttp() {
        // ここはバックグラウンドで実行される
        // Http通信とかよくやると思う
        String result = getHttp();
        finishHttp(result);
    }

    @UiThread
    protected void finishHttp(String result) {
        // ここはUIスレッドになる。
        // 画面操作等はここでやる。Toastを出すのも問題ない。
        Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
    }

Activityの起動

起動する場合は以下のように記載する


    new MainActivity_.IntentBuilder_(this).start();

結果が欲しい場合はstartForResultを使う。


    new MainActivity_.IntentBuilder_(this).startForResult(REQUEST_CODE);

Activityに値を渡したい場合は@Extraを使うと便利


@EActivity(R.layout.activity_main)
public class MainAcitvity extends Acitvity {

    // 型は色々指定することが出来る
    @Extra
    protected int extInt;
    @Extra
    protected String extString

こういう定義をした場合、呼び出し時に指定することが出来る


    new MainActivity_
        .IntentBuilder_(this)
        .extInt(intValue)
        .extString(stringValue)
        .start();


Fragmentのビルド

ビルダーを使ってビルドする

    SampleFragment_.builder().build();

値を渡したい場合は受け取り側で@FragmentArgで指定しておけば受け取れる。


@EFragment(R.layout.fragment_sample)
public class SampleFragment extends Fragment {

    // 型は色々指定することが出来る
    @FragmentArg
    protected int extInt;
    @FragmentArg
    protected String extString

こういう定義をした場合、呼び出し時に指定することが出来る


    SampleFragment_
        .builder()
        .extInt(intValue)
        .extString(stringValue)
        .build();

ViewGroupのビルド

ビルダーを使ってビルドする

    LinearLayoutView_.build(this);

値の設定とかできないから、publicメソッドでも作ってデータを紐付けすればいいと思う。

Preferenceの使用

プリファレンスもインターフェースを作るだけで簡単に触れる。Editorとかkeyとか指定しなくていいから簡単。

@SharedPref(value = SharedPref.Scope.UNIQUE)
public interface SharedPreferenceInf {

    // 関数として書く必要がある
    int intValue();
    String stringValue();

使う時の定義は@Prefで指定する


    @Pref
    protected SharedPreferenceInf_ pref;

値にアクセスするときはget()、put()を使用する


    // getで取得
    int intValue = pref.intValue().get();

    // putで編集
    pref.stringValue().put("hogehoge");


まとめ

これぐらいわかってれば色々捗ると思う。特に@Backgroundと@UiThreadは非常に便利。これを使うためにArrayAdapterが作成するViewをAndroidAnnotationsを使ってビルドする価値はあると思う。僕が使ってる時はprogardがうまく動かなかったけど、今のバージョンだと出来るようになってるのかなぁ。

ButterknifeでfindViewByIdを自動化する

Androidでxmlを書いてidつけてソースでfindViewByIdっていちいち書くのは非常にだるい。前に書いたandroidannotaionsだと重厚すぎるという場合にはButterknifeを使うと捗る

http://jakewharton.github.io/butterknife/

インポート

build.gradleのdependenciesに以下を記載

compile 'com.jakewharton:butterknife:6.0.0'

使い方

InjectViewアノテーションでひもづけるIdを指定してあげればいい

    @InjectView(R.id.txtView)
    protected TextView txtView;
    @InjectView(R.id.imgView)
    protected ImageView imgView;

クリックにリスナーを付けたい場合も簡単に書ける

    @OnClick(R.id.btn)
    protected void btnClick() {
        // ここがonClickで呼ばれる
    }

まとめ

findViewByIdの紐付けって毎回書くのがだるいのでこれ使うと楽にできるからいいと思う。ArrayAdapterのViewHolderを作る場合も使える。例は最初のURLに書いてあるから省くけど非常に簡単に書くことが出来る。AndroidAnnotationsだとProGardがうまく動かなかったり出来上がるものが重厚すぎるのでちょっとしたアプリならButterknifeのほうがおすすめ。是非使ってみてね。

Tinder風のUIを簡単に作れるライブラリ

SwipeCardsというライブラリが非常に便利

https://github.com/Diolor/Swipecards

English Study Cardもこのライブラリを使って作っている

https://play.google.com/store/apps/details?id=com.choilabo.android.englishstudycard

インポート

build.gradleのdependenciesに以下を追加してsync

compile 'com.lorentzos.swipecards:library:1.0.7@aar'

layout

    <com.lorentzos.flingswipe.SwipeFlingAdapterView
        android:id="@+id/frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

ソース

SwipeFlingAdapterViewにArrayAdapterを設定するだけで動く。リストをカスタムするのと同じようにArrayAdapterのgetViewを書き換えれば簡単にTinder風のUIが再現できる。拾える各種イベントは以下の様なもの。

ArrayAdapter adapter = new ArrayAdapter(this);
flingContainer.setFlingListener(new SwipeFlingAdapterView.onFlingListener() {
    @Override
    public void removeFirstObjectInAdapter() {
        // 一番上のカードがめくれた時に呼ばれる

        // 一番上のアイテムをここで入れ替えてあげる必要がある
        adapter.remove(adapter.getItem(0));
    }

    @Override
    public void onLeftCardExit(Object dataObject) {
        // 左にフリックされた時に呼ばれる
    }

    @Override
    public void onRightCardExit(Object dataObject) {
        // 右にフリックされた時に呼ばれる
    }

    @Override
    public void onAdapterAboutToEmpty(int itemsInAdapter) {
        // Adapterに入ってるアイテムが無くなった時に呼ばれる
    }
});

動き

動きとしてはカードがフリックされた時にremoveFirstObjectInAdapter()が呼び出されるので、ここで表示されるものを変更してあげる必要がある。例ではadapterの一番上のitemをremoveすることで次のやつが出てくるようにしている。

感想

すごく便利だけど、カードをフリックしている途中にどれだけ移動したかのイベントは存在しないため、移動しながら画面が変化するとか作ることができない。簡単なアプリなら使えるけど、複雑なアプリを作るなら自分で実装したほうが早いと思う。FlyingCardっていうのを簡単に使えるしたライブラリらしいのでそっちをカスタマイズしたらいいかも。

平均点を上げると飛び抜けて面白いものはできない

最近「有吉弘行のSUNDAY NIGHT DREAMER」をよく聞いてるんだけど、アンジャッシュの小嶋さんがゲストの回での話が興味深かった。

話の流れとしては、有吉さんが人力車の芸人に面白い人間が多いと小嶋さんに話を振ったところ、最近は面白い芸人が出てこなくなってきたと小嶋さんが嘆いていたところから話が始まる。その理由は芸人を管理している人間が変わったことによる弊害らしく、面白いか面白く無いかの判断だけをする人だったのが、ネタに対して変えたほうが良い点も指摘する人間に変わったのが原因だそうだ。なぜそうなるかというと、ネタをダメ出しされた芸人がダメ出しされた箇所をちゃんと変更してくるせいで今まで誰もやったことがないような飛び抜けたネタを作ってくる人間がいなくなってしまったらしい。

これって非常に興味深い話だと思う。

万人が面白いものって実は存在しなくて誰かが面白ければ誰かが面白くないし、どこかの層に強烈に突き刺さるネタっていうのはどこかの層に強烈に嫌われるんだと思う。誰かのフィルターを通せば通すほど丸くなって突き刺さるものは削られて万人が50点をつけるものが出来上がるんだろう。

万人が50点をつけるものが目指すのがいいのか、それとも誰かが100点をつけるけど誰かが−100点をつけるものがいいのかはケースバイケースなんだろうけど、飛び抜けて面白いものっていうのは万人が50点をつけるものではないんだろう。平均点を上げようと頑張れば頑張るほど尖って面白かったものが丸くなって面白くなくなり、結局平均的で汎用な面白くないものができてしまうというのは感慨深いものだと思う。

ListViewでタップした時の反応を消す

listSelectorに透明を設定すると反応していないように見える。

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:listSelector="@android:color/transparent" />

ソフトウェアキーボードでレイアウトを変える

ちょっとめんどくさかったので覚書

やりたいこと

画面の下に常にボタンを表示なければならないのだが、画面には入力ボタンがあり画面全体の高さは変更してはいけない。高さを変更できないのでScrollViewは使えないので、FrameLayoutで全体を括ってソフトウェアキーボードが表示されたタイミングでボタンの位置をずらして画面下に表示しなければならない。

コード

frmWrapper.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        Rect r = new Rect();
        frmWrapper.getWindowVisibleDisplayFrame(r);
        int heightDiff = frmWrapper.getRootView().getHeight() - (r.bottom - r.top);
        lnrButtonWrapper.layout(
                lnrButtonWrapper.getLeft(),
                r.bottom - r.top - getSupportActionBar().getHeight() - lnrButtonWrapper.getHeight(),
                lnrButtonWrapper.getRight(),
                r.bottom - r.top - getSupportActionBar().getHeight());
    }
});

解説

全体を括っているFrameLayoutにLayoutListenerを追加する。こうすることで、ソフトウェアキーボードが表示されるとFrameLayoutの高さが変更されるのでこの中に入ってくる。getRootViewの高さを取得すると画面全体の高さが取得できるので、そこからアクションバー等の高さを引いてあげることで画面下にボタンが入っているLinearLayoutを表示することが出来る。

まとめ

ソフトウェアキーボードの表示、非表示を取得したいだけであれば、画面の高さから全体を括っている高さを引いた値が100を超えていたらソフトウェアキーボードが表示されていると判断していいらしい。Viewを拡張してonMeasureをOverrideする例が引っかかるけどそのためだけにカスタムビューを作るのは非常に面倒なのでこっちのほうがらくだと思う。

おまけ

出てるか出てないかの取得はこうやる

    DisplayMetrics displayMetrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    windowHeight = displayMetrics.heightPixels - getSupportActionBar().getHeight();
    lnrWrapper.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            Rect r = new Rect();
            lnrWrapper.getWindowVisibleDisplayFrame(r);
            if ((windowHeight - (r.bottom - r.top)) > 100) {
                // ソフトウェアキーボード出てる
            } else {
                // ソフトウェアキーボード出てない
            }
        }
    });

参考URL

http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android