RecyclerViewを使うときに起きた問題を解決する方法


Android Lには`ListView`の進化型である`RecyclerView`というウィジットが追加されました。
`RecyclerView`について調べると、`ListView`に比べると

* パフォーマンスの向上
* 使いやすさの向上

といった記事が目につきます。

実際にどうなのか、使うまでの手順や気がついたことを記していきます。

※ 本記事は以下の環境で実施されました。
> IDE: Android Studio 0.8.8
> Java: 1.6.0_65

※ Android Developersには`RecyclerView`のトレーニングとして [Creating Lists and Cards](https://developer.android.com/training/material/lists-cards.html) というのがあります。

## compileSdkVersion android-L の回避方法
Android Studioで新しいProjectで `Minimum SDK` を `API 20+: Android L` に設定する。

作成したときの build.gradle を確認すると次の通りです。

```XML:build.gradle
android {
    compileSdkVersion 'android-L'
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "jp.co.samril.myapplication"
        minSdkVersion 'L'
        targetSdkVersion 'L'
        versionCode 1
        versionName "1.0"
    }
	...
}
```

実は、このまま作成されたProjectをコンパイルすると次のようなエラーが発生する。
> Error:compileSdkVersion android-L requires compiling

調べてみると、バグらしいですね。
> [[How-to] Use the v21 Support Libs on Older Versions & Target L While Remaining Backwards-Compatible](http://www.reddit.com/r/androiddev/comments/297xli/howto_use_the_v21_support_libs_on_older_versions/)

build.gradle を次のように修正すればOKです。

```XML:build.gradle
android {
    compileSdkVersion 20 // ★ここを整数に
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "jp.co.samril.myapplication"
        minSdkVersion 'L'
        targetSdkVersion 'L'
        versionCode 1
        versionName "1.0"
    }
	...
}
```

## 低いSDKでコンパイルする方法
`RecyclerView`はAndroid Lから追加されたウィジットのため、SDK Versionが 20 以上でないといけません。

build.gradle の `dependencies` に次をセットし、

```XML:build.gradle
dependencies {
    compile 'com.android.support:support-v4:20.+'
    compile 'com.android.support:recyclerview-v7:+'
}
```

実際に低いSDKを設定すると、次のようなコンパイルエラーが発生します。
> Error:Execution failed for task '...'.
> \> Manifest merger failed : uses-sdk:minSdkVersion 14 cannot be smaller than version L declared in library com.android.support:support-v4:21.0.0-rc1

これを解除するには AndroidManifest.xml に `uses-sdk` の設定を追加してやればよい。

```XML:AndroidManifest.xml
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="jp.co.homes.android3" >

    <uses-sdk tools:node="replace" />

    ...
```
`xmlns:tools="http://schemas.android.com/tools"` と `<uses-sdk tools:node="replace" />` を追記しました。
これで Gradle のビルドは通るようになります。

## レイアウトへの配置したときにプレビューが見れない
レイアウトファイルには、通常の`ListView`と同じ記述で書くことができます。

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

    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
```

ただし、Preview を確認しようとすると`java.lang.NullPointerException`が発生します。
> Rendering Problems
> The new RecyclerView does not yet work in Studio. We are working on a fix. ([Open Issue 72117](b.android.com/72117), Show Exception)

リンク先に書いてあるとおり、なんと`RecyclerView`は Android Studio ではまだ動作しないようです。（まじっすか...

## 表示したときに発生するNullPointerExceptionを回避する方法
`RecyclerView`を埋め込み、無事ビルドまでパスします。
では実機やGenymotionで動作を確認してみようと、画面を表示すると`java.lang.NullPointerException`でクラッシュしました。

```クラッシュログの一部
java.lang.NullPointerException
        at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:1310)
        at android.view.View.measure(View.java:16497)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1404)
    ...
```

調べてみると、これもAndroid Studioのバグだとか...
> [Using CardView and RecyclerView in my layout files throws an exception](http://stackoverflow.com/questions/24458538/using-cardview-and-recyclerview-in-my-layout-files-throws-an-exception)

原因は`RecyclerView`を初期化できていないらしい。
回避方法はこちら

```Java:***Activity.java
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.my_recycler_view);
recyclerView.setHasFixedSize(true); // RecyclerViewのサイズを維持し続ける
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setItemAnimator(new DefaultItemAnimator());
```
この実装は`onCreate()` or `onCreateView()`に追記します。
これで NullPointerException を回避できます。

## 行間に区切り線を表示させる方法
`RecyclerView ` で `android.R.layout.simple_list_item_1` を描画した場合、行間の区切り線が表示されません。
> ![Genymotion_for_personal_use_-_Samsung_Galaxy_S5_-_4_4_2_-_API_19_-_1080x1920__1080x1920__480dpi__-_192_168_56_101.png](https://qiita-image-store.s3.amazonaws.com/0/33505/563150ce-4e22-3289-8e93-ff404a526f72.png "Genymotion_for_personal_use_-_Samsung_Galaxy_S5_-_4_4_2_-_API_19_-_1080x1920__1080x1920__480dpi__-_192_168_56_101.png")

これを回避するには [RecyclerView.ItemDecoration](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemDecoration.html) を使います。

RecyclerView.ItemDecoration とは
> Adapterでセットする特定ビューへの描画やレイアウトの追加ができるものです
> 例えば、今回のような区切り線やハイライトなど、様々な表現を可能にできます

https://gist.github.com/alexfu/0f464fc3742f134ccd1e を参考に次のクラスを作成します。

```Java:MyItemDecoration.java
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;

/**
 * Created by samukaak on 2014/11/20.
 */
public class MyItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };

    private Drawable mDivider;

    public MyItemDecoration(final Context context) {
        final TypedArray array = context.obtainStyledAttributes(ATTRS);
        mDivider = array.getDrawable(0);
        array.recycle();
    }

    @Override
    public void getItemOffsets(final Rect outRect, final int itemPosition, final RecyclerView parent) {
        outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
    }

    @Override
    public void onDraw(final Canvas c, final RecyclerView parent) {
        drawVertical(c, parent);
    }

    public void drawVertical(final Canvas c, final RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }
}
```
それを `RecyclerView#addItemDecoration(android.support.v7.widget.RecyclerView.ItemDecoration decor)` を使って設定します。

```Java:***Activity.java
recyclerView.addItemDecoration(new MyItemDecoration(getActivity()));
```
![Genymotion_for_personal_use_-_Samsung_Galaxy_S5_-_4_4_2_-_API_19_-_1080x1920__1080x1920__480dpi__-_192_168_56_101.png](https://qiita-image-store.s3.amazonaws.com/0/33505/e0568c98-d05a-5725-55a2-8192cdffe548.png "Genymotion_for_personal_use_-_Samsung_Galaxy_S5_-_4_4_2_-_API_19_-_1080x1920__1080x1920__480dpi__-_192_168_56_101.png")

##### 参考

* [compileSdkVersion を 'android-L' にすると INSTALL_FAILED_OLDER_SDK が発生する](http://kokufu.blogspot.jp/2014/07/compilesdkversion-android-l.html)
* [RecyclerViewをICSで使う方法](http://justplayer-dev.blogspot.jp/2014/07/recyclerviewics.html)
* [RecyclerView in Android: The basics](http://antonioleiva.com/recyclerview/)
* https://github.com/baoyongzhang/RecyclerViewDemo/wiki/Introduce
* [RecyclerViewで行間に区切り線を表示する](http://qiita.com/wasabeef_jp/items/6399432b899cb5613fb1)