DiffUtil で簡単に RecyclerView を更新する

list_mail

例えば, RecyclerView で一覧を作成して表示しているとして


1. 田中一郎
2. 佐藤二郎
3. 鈴木三郎
....

サーバなどに問い合わせて取得された以下データ


1. 田中一郎
2. ジローラモ
3. 鈴木三郎
...

を一覧画面に反映させたい場合.

RecyclerView.Adapter に更新を通知する場合,

RecyclerView.Adapter | Android Developers

notify*() のうちアイテムの更新に関するものは


notifyDataSetChanged()
notifyItemChanged(int position, Object payload)
notifyItemChanged(int position)
notifyItemRangeChanged(int positionStart, int itemCount, Object payload)
notifyItemRangeChanged(int positionStart, int itemCount)

ですが, それぞれ以下の面倒な部分がありました.


notifyDataSetChanged()

→ 変更部分のみに関してのアニメーションが表示されない.


notifyItemChanged(int position, Object payload)
notifyItemChanged(int position)
notifyItemRangeChanged(int positionStart, int itemCount, Object payload)
notifyItemRangeChanged(int positionStart, int itemCount)

→ アニメーションは表示されるが, 変更部分の position を計算して渡す必要がある.

その他 hasStableIds() と Loader を使ったりなど面倒でしたよね.

DiffUtil を使う

support-library-v7 24.2.0 で登場の DiffUtil を使いましょう.

DiffUtil | Android Developers

コールバッククラス作成後, それを使った更新メソッドを Adapter 内に記述します.

MyDiffUtilCallback.java


public class MyDiffUtilCallback extends DiffUtil.Callback{

    List<Person> oldPersons;
    List<Person> newPersons;

    public MyDiffUtilCallback(List<Person> oldPersons, List<Person> newPersons) {
        this.oldPersons = oldPersons;
        this.newPersons = newPersons;
    }

    @Override
    public int getOldListSize() {
        return oldPersons.size();
    }

    @Override
    public int getNewListSize() {
        return newPersons.size();
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return oldPersons.get(oldItemPosition).id == newPersons.get(newItemPosition).id;
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        return oldPersons.get(oldItemPosition).equals(newPersons.get(newItemPosition));
    }

    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
	// some additional information
        return super.getChangePayload(oldItemPosition, newItemPosition);
    }
}

MyRecyclerViewAdapter.java


public void updateList(ArrayList<Person> newList) {
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffUtilCallback(this.persons, newList));
        diffResult.dispatchUpdatesTo(this);
}

これで, アイテムのポジションの計算はおまかせにして, 同時に綺麗なアニメーションが変更部分に適用されます.

実際, データ量が多い場合は厳しいようなので, RxJava なら メインスレッドにて以下.


@Override
public void onNext(DiffUtil.DiffResult result) {
    result.dispatchUpdatesTo(mProductAdapter);
}

kotlinなら同様に以下。


internal class DiffUtilCallback(
    private val oldItems: List<Item>,
    private val newItems: List<Item>
) : DiffUtil.Callback() {

  override fun getOldListSize() = oldItems.size

  override fun getNewListSize() = newItems.size

  override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int)
      = oldItems[oldItemPosition].id == newItems[newItemPosition].id

  override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int)
      = oldItems[oldItemPosition] == newItems[newItemPosition]
}

以下のように使うと便利かもしれません.

リアクティブにデータ更新検知して自動画面更新させる


RecyclerView アイテム内ある要素の クリックイベント

いろんな実装が可能なのですが.


public class ItemsAdapter extends RecyclerView.Adapter<ItemsAdapter.ViewHolder> {

  // ...

  private static ItemsAdapter.OnItemClickListener listener;

  public void setOnItemClickListener(ItemsAdapter.OnItemClickListener listener) {
    ItemsAdapter.listener = listener;
  }

  public interface OnItemClickListener {
    void onItemClick(View view, int position);
  }

  // ...

  public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    public ViewHolder(final View itemView) {
      super(itemView);

      name = (TextView) itemView.findViewById(R.id.name);
      age = (TextView) itemView.findViewById(R.id.age);
      button = (Button) itemView.findViewById(R.id.button);

      name.setOnClickListener(this);
      age.setOnClickListener(this);
      button.setOnClickListener(this)
    }

    @Override
    public void onClick(View v) {
      if (listener != null) {
        listener.onItemClick(v, getLayoutPosition());
      }
    }

  }
}

UI.


public class MainFragment extends Fragment
    implements ItemsAdapter.OnItemClickListener {

  // ...

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
   
    // ...
    mAdapter.setOnItemClickListener(this);
  }

  @Override
  public void onItemClick(View view, int position) {
    switch (view.getId()) {
      case R.id.name:
        // ...
        break;  
        // ...
  }

    // ...
}

ここらなんだろうな.

java - Why doesn't RecyclerView have onItemClickListener()? And how RecyclerView is different from Listview? - Stack Overflow


「Android-L」を実装するために見ておくページs

とりあえず一般リリースまで集めていく.

Material_Design___Android_Developers

Material Designは静止画で見るとフラットデザインっぽいですが、思想や使い心地は異なったものです

ウェブやiOSにもサービスを提供している場合は、どのようにMaterial Designを取り入れるのがユーザにとって一番良いのか考える必要があります

Android L Developer Previewは、まだプレビュー感が強いのでただちに移行するということはないが、ウォッチはしていた方が良いです

Material Designの目指すところとAndroid側の変更点 - クックパッド開発者ブログ

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_enabled="true"
        android:state_pressed="true">
        <objectAnimator
            android:duration="@android:integer/config_shortAnimTime"
            android:propertyName="translationZ"
            android:valueFrom="@dimen/button_elevation"
            android:valueTo="@dimen/button_press_elevation"
            android:valueType="floatType" />
    </item>
    <item>
        <objectAnimator
            android:duration="@android:integer/config_shortAnimTime"
            android:propertyName="translationZ"
            android:valueFrom="@dimen/button_press_elevation"
            android:valueTo="@dimen/button_elevation"
            android:valueType="floatType" />
    </item>
</selector>

Floating Action Button (requires Android-L preview)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">
 
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
 
</RelativeLayout>

RecyclerView example | Code Random

Palette palette = Palette.generate(photo); // photo は Bitmap

// get○○○Color()で任意のパレット色が取得できる

// VibrantColor
palette.getLightVibrantColor().getRgb();    // 明るい
palette.getVibrantColor().getRgb();
palette.getDarkVibrantColor().getRgb();     // 暗い

// MutedColor
palette.getLightMutedColor().getRgb();      // 明るい
palette.getMutedColor().getRgb();
palette.getDarkMutedColor().getRgb();       // 暗い

[Android] android.support.v7.graphics.Palette がいい感じかも - adakoda

1.material design specification.をみる
2.マテリアルテーマをアプリに適用する
3.マテリアルテーマをカスタマイズするためにスタイルの定義を加える
4.マテリアルデザインガイドラインに従ってレイアウトを作る
5.影を適用するために、viewにelevationを指定する
6.リストやカードなどのcomplex viewに新しいWidgetを作る
7.新しいAPIを使ってアニメーションをカスタマイズする

L Developer Preview マテリアルデザインを始める - Firespeed

compile 'com.android.support:cardview-v7:+'
compile 'com.android.support:recyclerview-v7:+'
compile 'com.android.support:palette-v7:+'

For Android Material support libraries, where can I find CardView and RecyclerView? - Stack Overflow

RecyclerView を使用すると、アイテムのサイズが固定長の場合に、従来よりも良いパフォーマンスが得られる(※2)ほか、
アイテムの追加、削除時などのアニメーション(※3)についても、デフォルトで利用することができます。

[Android] RecyclerView を使ってみました - adakoda
とりあえず, やみくもに集める.
ある程度たまったらまとめよう.

(随時更新中...)