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 というのがあります。

compileSdkVersion android-L の回避方法

Android Studioで新しいProjectで Minimum SDKAPI 20+: Android L に設定する。

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

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

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

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 に次をセットし、

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 の設定を追加してやればよい。

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と同じ記述で書くことができます。

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, 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

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

***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 を回避できます。

行間に区切り線を表示させる方法

RecyclerViewandroid.R.layout.simple_list_item_1 を描画した場合、行間の区切り線が表示されません。

これを回避するには RecyclerView.ItemDecoration を使います。

RecyclerView.ItemDecoration とは

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

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

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) を使って設定します。

***Activity.java
recyclerView.addItemDecoration(new MyItemDecoration(getActivity()));

参考