Master of
RecyclerView
Yuki Anzai @ ABC 2015 Summer
• blog : Y.A.M の雑記帳
• y-anz-m.blogspot.com
• twitter : @yanzm (やんざむ)
• uPhyca Inc. (株式会社ウフィカ)
あんざいゆき
New
今日話す内容は
TechBoosterの
夏コミ本に
執筆してます
http://techbooster.github.io/c88/
事前のお知らせ
• 今日の資料 → 公開されます
• 今日の公演 → 録画&公開されます
RecyclerView
A flexible view for
providing a limited window
into a large data set.
RecyclerView
大規模なデータセットに、
限定されたウィンドウを
提供するための柔軟なビュー
RecyclerView
大規模なデータセットの一部を
ビューを再利用しながら
表示するためのコンポーネント
RecyclerView
ListViewとかGridView
みたいなやつ
RecyclerViewの構成
• RecyclerView
• データを表示するためのスクロール可能な
View
• RecyclerView
• データを表示するためのスクロール可能な
View
• RecyclerView.LayoutManager
• アイテム用のビューのサイズを計算し、配
置する
RecyclerViewの構成
• RecyclerView.Adapter
• RecyclerViewに表示するデータセットを
管理し、アイテム用のViewにデータを紐
づける
RecyclerViewの構成
• RecyclerView.Adapter
• RecyclerViewに表示するデータセットを
管理し、アイテム用のViewにデータを紐
づける
• RecyclerView.ViewHolder
• アイテム用のビューとメタデータを保持す
る
RecyclerViewの構成
RecyclerViewの構成
RecyclerView LayoutManager
Adapter
子ビュー配置
子ビュー
(ViewHolder)
にデータ紐付
Recycler
ViewHolderを再利用
再利用する
ViewHolder
ListViewの構成
RecyclerView LayoutManager
Adapter
子ビュー配置
子ビュー
(ViewHolder)
にデータ紐付
Recycler
ViewHolderを再利用
再利用する
ViewHolder
ListView
ListAdapter
ListViewの構成
ListView =
RecyclerView + LayoutManager + Recycler
+ その他もろもろの機能
ListAdapter = Adapter + ViewHolder
ListViewの構成
ListView =
RecyclerView + LayoutManager + Recycler
+ その他もろもろの機能
ListAdapter = Adapter + ViewHolder
ListView vs RecyclerView
ListView RecyclerView
区切り線 ⚪ 自分で実装
listSelector ⚪
onItemClick ⚪ 自分で実装
choiceMode ⚪
ListView vs RecyclerView
ListView RecyclerView
Filter ⚪ 自分で実装
FadingEdge ⚪ 自分で実装
Header, Footer ⚪ 自分で実装
StaggeredGrid ⚪
ListView vs RecyclerView
ListView RecyclerView
追加・削除の
アニメーション
自分で実装 ⚪
Swipe to Dismiss 自分で実装 ⚪
Drag & Drop 自分で実装 ⚪
横スクロール配置 ⚪
RecyclerViewの
一番シンプルな
使い方
最低限必要なもの
• RecyclerView
• RecyclerView.LayoutManager
• RecyclerView.Adapter
• RecyclerView.ViewHolder
最低限必要なもの
• RecyclerView
• RecyclerView.LayoutManager
• RecyclerView.Adapter
• RecyclerView.ViewHolder
<?xml version="1.0" encoding="utf-8"?>

<android.support.v7.widget.RecyclerView 

xmlns:android="http://schemas.android.com/apk/res/a
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/recycler_view"

android:layout_width="match_parent"

android:layout_height="match_parent"

app:layoutManager="LinearLayoutManager" />
RecyclerView + LayoutManager
<?xml version="1.0" encoding="utf-8"?>

<android.support.v7.widget.RecyclerView

xmlns:android="http://schemas.android.com/apk/res/a
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/recycler_view"

android:layout_width="match_parent"

android:layout_height="match_parent"

app:layoutManager

="android.support.v7.widget.LinearLayoutManager
RecyclerView + LayoutManager
RecyclerView + LayoutManager
<?xml version="1.0" encoding="utf-8"?>

<android.support.v7.widget.RecyclerView

xmlns:android="http://schemas.android.com/apk/res/a
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/recycler_view"

android:layout_width="match_parent"

android:layout_height="match_parent"

app:layoutManager

=“net.yanzm.sample.MyLayoutManager” />
最低限必要なもの
• RecyclerView
• RecyclerView.LayoutManager
• RecyclerView.Adapter
• RecyclerView.ViewHolder
private static class ViewHolder extends

RecyclerView.ViewHolder {

static final int LAYOUT_ID

= android.R.layout.simple_list_item_1;



final TextView textView;



public ViewHolder(View itemView) {

super(itemView);

textView = (TextView)

itemView.findViewById(android.R.id.text1);

}

}
ViewHolder
private static class SimpleAdapter extends

RecyclerView.Adapter<ViewHolder> {



private final LayoutInflater inflater;

private final List<String> data;



private SimpleAdapter(Context context, List<String> data) {
this.inflater = LayoutInflater.from(context);

this.data = data;

}



@Override

public ViewHolder onCreateViewHolder(ViewGroup parent,

int viewType) {

return new ViewHolder(inflater

.inflate(ViewHolder.LAYOUT_ID, parent, false));
}



@Override

Adapter


private SimpleAdapter(Context context, List<String> data) {
this.inflater = LayoutInflater.from(context);

this.data = data;

}



@Override

public ViewHolder onCreateViewHolder(ViewGroup parent,

int viewType) {

return new ViewHolder(inflater

.inflate(ViewHolder.LAYOUT_ID, parent, false));
}



@Override

public void onBindViewHolder(ViewHolder holder, 

int position) {

String text = data.get(position);

holder.textView.setText(text);

}



@Override

public int getItemCount() {

return data.size();

}

}
Adapter
public class SimpleSampleActivity extends Activity {



@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_simple_sample);



RecyclerView recyclerView = (RecyclerView)

findViewById(R.id.recycler_view);

recyclerView.setHasFixedSize(true);



List<String> data = new ArrayList<>();

for (int i = 0; i < 30; i++) {

data.add("Item : " + i);

}



final SimpleAdapter adapter =

Activity
setContentView(R.layout.activity_simple_sample);



RecyclerView recyclerView = (RecyclerView)

findViewById(R.id.recycler_view);

recyclerView.setHasFixedSize(true);



List<String> data = new ArrayList<>();

for (int i = 0; i < 30; i++) {

data.add("Item : " + i);

}



final SimpleAdapter adapter =

new SimpleAdapter(this, data);

recyclerView.setAdapter(adapter);

}




private static class ViewHolder extends

RecyclerView.ViewHolder {…}
private static class SimpleAdapter extends

RecyclerView.Adapter<ViewHolder> {…}
}

Activity
LinearLayoutManager
GridLayoutManager
StaggeredGridLayoutManager
LinearLayoutManager
• 設定項目
• orientation
• reverseLayout
• stackFromEnd
orientation
• デフォルトはLinearLayoutManager.VERTICAL
<?xml version="1.0" encoding="utf-8"?>

<android.support.v7.widget.RecyclerView

…

android:orientation="horizontal"

app:layoutManager="LinearLayoutManager" />
new LinearLayoutManager(this,
LinearLayoutManager.HORIZONTAL, false);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
or
or
app:reverseLayout="true" app:stackFromEnd="true"
GridLayoutManager
• 設定項目
• spanCount
• orientation
• reverseLayout
• stackFromEnd
spanCount
• 列数、デフォルトは1
<?xml version="1.0" encoding="utf-8"?>

<android.support.v7.widget.RecyclerView

…

app:layoutManager="GridLayoutManager"

app:spanCount="2" />
new GridLayoutManager(this, 2);
layoutManager.setSpanCount(2);
or
or
StaggeredGridLayoutManager
• 設定項目
• spanCount
• orientation
• reverseLayout
ItemDecorationで
Dividerをつける
ItemDecoration
• 装飾を行うためのクラス
• アイテム用のViewのoffsetを指定
• onDraw()でRecyclerViewの下に描画
• onDrawOver()でRecyclerVieの上に描画
アイテム用のViewのOffsetを指定
final int offset =
(int) (8 * getResources().getDisplayMetrics().density);



final RecyclerView.ItemDecoration itemDecoration =

new RecyclerView.ItemDecoration() {

@Override

public void getItemOffsets(Rect outRect,

View view,

RecyclerView parent,

RecyclerView.State state) {

outRect.set(offset, offset, offset, offset);

}

};

recyclerView.addItemDecoration(itemDecoration);
アイテム用のViewのOffsetを指定
final RecyclerView.ItemDecoration itemDecoration =

new RecyclerView.ItemDecoration() {

@Override

public void getItemOffsets(Rect outRect,

View view,

RecyclerView parent,

RecyclerView.State state) {

int position = ((RecyclerView.LayoutParams)
view.getLayoutParams()).getViewLayoutPosition();

if (position == 0) {

outRect.set(offset, offset, offset, offset);

} else {

outRect.set(offset, 0, offset, offset);

}

}

};


recyclerView.addItemDecoration(itemDecoration);
private static class DividerDecoration extends

RecyclerView.ItemDecoration {



private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final int dividerHeight;



public DividerDecoration(Resources res) {

paint.setColor(Color.GRAY);

dividerHeight

= (int) (4 * res.getDisplayMetrics().density);

}



@Override

public void getItemOffsets(Rect outRect,

View view,

RecyclerView parent,

RecyclerView.State state) {

int position = ((RecyclerView.LayoutParams)

view.getLayoutParams()).getViewLayoutPosition();

Dividerを描画
dividerHeight

= (int) (4 * res.getDisplayMetrics().density);

}



@Override

public void getItemOffsets(Rect outRect,

View view,

RecyclerView parent,

RecyclerView.State state) {

int position = ((RecyclerView.LayoutParams)

view.getLayoutParams()).getViewLayoutPosition();

// 位置が2番目以降なら上部にdividerを描画したいので、

// divider分だけ上をあける

int top = position == 0 ? 0 : dividerHeight;

outRect.set(0, top, 0, 0);

}



@Override

public void onDrawOver(Canvas c, RecyclerView parent,

RecyclerView.State state) {

super.onDrawOver(c, parent, state);

// アイテムのビューより上に描画される

}
Dividerを描画
@Override

public void onDraw(Canvas c, RecyclerView parent,

RecyclerView.State state) {

super.onDraw(c, parent, state);

// アイテムのビューより下に描画される



final RecyclerView.LayoutManager manager =

parent.getLayoutManager();

final int left = parent.getPaddingLeft();

final int right = parent.getWidth()

- parent.getPaddingRight();

final int childCount = parent.getChildCount();

for (int i = 1; i < childCount; i++) {

final View child = parent.getChildAt(i);

final RecyclerView.LayoutParams params =

(RecyclerView.LayoutParams) child.getLayoutParams()
if (params.getViewLayoutPosition() == 0) {

continue;

}

// ViewCompat.getTranslationY()を入れないと

Dividerを描画


final RecyclerView.LayoutManager manager =

parent.getLayoutManager();

final int left = parent.getPaddingLeft();

final int right = parent.getWidth()

- parent.getPaddingRight();

final int childCount = parent.getChildCount();

for (int i = 1; i < childCount; i++) {

final View child = parent.getChildAt(i);

final RecyclerView.LayoutParams params =

(RecyclerView.LayoutParams) child.getLayoutParams()
if (params.getViewLayoutPosition() == 0) {

continue;

}

// ViewCompat.getTranslationY()を入れないと

// 追加・削除のアニメーション時の位置が変になる

final int top = manager.getDecoratedTop(child)

- params.topMargin

+ Math.round(ViewCompat.getTranslationY(child))
final int bottom = top + dividerHeight;

c.drawRect(left, top, right, bottom, paint);

}

}

}
Dividerを描画
onItemClick
OnItemClick
• 方法がいくつかある
• ViewHolderのitemViewに
View.setOnClickListener
• ItemDecorationでタップされたアイテムの
位置にselectorを描画
OnItemClick
• 方法がいくつかある
• ViewHolderのitemViewに
View.setOnClickListener
• ItemDecorationでタップされたアイテムの
位置にselectorを描画
v17 leanback
library はこっち
// selectableItemBackgroundに指定されている
// リソースIDの値を取得しておく

TypedValue val = new TypedValue();

if (getTheme() != null) {

getTheme().resolveAttribute(

android.R.attr.selectableItemBackground,
val, true);

}

final int backgroundResId = val.resourceId;



final SimpleAdapter adapter = new SimpleAdapter(this, data) {

@Override

public ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {

final ViewHolder viewHolder
= super.onCreateViewHolder(parent, viewType);

viewHolder.itemView.setBackgroundResource(backgroundRes
viewHolder.itemView.setOnClickListener(
OnItemClick
final SimpleAdapter adapter = new SimpleAdapter(this, data) {

@Override

public ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {

final ViewHolder viewHolder
= super.onCreateViewHolder(parent, viewType);

viewHolder.itemView.setBackgroundResource(backgroundRes
viewHolder.itemView.setOnClickListener(
new View.OnClickListener() {

@Override

public void onClick(View v) {

int position = viewHolder

.getAdapterPosition();

Toast.makeText(v.getContext(),

"Position = " + position,

Toast.LENGTH_SHORT).show();

}

});

return viewHolder;

}

};

recyclerView.setAdapter(adapter);
OnItemClick
データの追加・削除・変更
notify**
• ArrayAdapterに相当するものは用意されていない
• データの追加・削除・変更時にはnotifyItem**()を呼ぶ
• notifyDataSetChanged()はそれ以外のときだけにする
notify**
• notifyItemChanged(int position) : positionの位置のアイテ
ムの変更された
• notifyItemInserted(int position) : posiitonの位置にアイテム
が追加された
• notifyItemRemoved(int position) : positionの位置のアイテ
ムが削除された
• notifyItemMoved(int fromPosition, int toPosition) :
fromPositionにあったアイテムがtoPositionに移動した
notify**
• notifyItemRangeChanged(int positionStart, int itemCount) :
positionStartからitemCount個のアイテムが変更された
• notifyItemRangeInserted(int positionStart, int itemCount) :
positionStartにitemCount個のアイテムが追加された
• notifyItemRangeRemoved(int positionStart, int
itemCount) : positionStartからitemCount個のアイテムが削
除された
• notifyDataSetChanged() : データセットが変更された
public abstract class RecyclerArrayAdapter<T, VH extends
RecyclerView.ViewHolder>

extends RecyclerView.Adapter<VH> {



private final Object lock = new Object();

private final Context context;

private final List<T> objects;



public RecyclerArrayAdapter(Context context) {

this(context, new ArrayList<T>());

}



public RecyclerArrayAdapter(Context context, List<T> objects
this.context = context;

this.objects = objects;

}



public void add(@NonNull T object) {

final int position = objects.size();

ArrayAdapter的なRecyclerView用Adapter


public RecyclerArrayAdapter(Context context, List<T> objects
this.context = context;

this.objects = objects;

}



public void add(@NonNull T object) {

final int position = objects.size();

synchronized (lock) {

objects.add(object);

}

notifyItemInserted(position);

}



public void addAll(@NonNull Collection<? extends T> collecti
final int positionStart = objects.size();

final int itemCount = collection.size();

synchronized (lock) {

objects.addAll(collection);

}

notifyItemRangeInserted(positionStart, itemCount);

}



public void insert(@NonNull T object, int index) {

synchronized (lock) {

ArrayAdapter的なRecyclerView用Adapter
objects.addAll(collection);

}

notifyItemRangeInserted(positionStart, itemCount);

}



public void insert(@NonNull T object, int index) {

synchronized (lock) {

objects.add(index, object);

}

notifyItemInserted(index);

}



public void remove(@NonNull T object) {

int position = getPosition(object);

synchronized (lock) {

objects.remove(object);

}

notifyItemRemoved(position);

}



public void clear() {

final int itemCount = objects.size();

synchronized (lock) {

objects.clear();

}

notifyItemRangeRemoved(0, itemCount);

ArrayAdapter的なRecyclerView用Adapter
SwipeToDismiss
と
Drag & Drop
ItemTouchHelper
• RecyclerViewに swipe to dismiss と drag & drop による並
び替え機能を追加するためにユーティリティクラス
ItemTouchHelper.Callback callback = …;

ItemTouchHelper itemTouchHelper =
new ItemTouchHelper(callback);

itemTouchHelper.attachToRecyclerView(recyclerView);
swipe to dismiss
• ItemTouchHelper.Callbackのコンストラクタの第2引数で
スワイプ方向を指定
• スワイプされたらonSwiped()が呼ばれる
swipe to dismiss
int swipeDirs = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;

ItemTouchHelper.Callback callback =
new ItemTouchHelper.SimpleCallback(0, swipeDirs) {

…



@Override

public void onSwiped(RecyclerView.ViewHolder viewHolder,
int direction) {

int position = viewHolder.getAdapterPosition();

adapter.remove(adapter.getItem(position));

}

};
drag and drop
• ItemTouchHelper.Callbackのコンストラクタの第1引数で
ドラッグ方向を指定
• ドロップされたらonMove()が呼ばれる
• ドラッグが開始できるようになったタイミングで
onSelectedChanged()が呼ばれる
• ドラッグを終了するときにclearView()が呼ばれる
int dragDirs = ItemTouchHelper.UP | ItemTouchHelper.DOWN;

ItemTouchHelper.Callback callback = new
ItemTouchHelper.SimpleCallback(dragDirs, 0) {

@Override

public boolean onMove(RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {

int from = viewHolder.getAdapterPosition();

int to = target.getAdapterPosition();

adapter.move(from, to);

return true;

}



…

};
drag and drop
int dragDirs = ItemTouchHelper.UP | ItemTouchHelper.DOWN;

ItemTouchHelper.Callback callback = new
ItemTouchHelper.SimpleCallback(dragDirs, 0) {

@Override

public boolean onMove(RecyclerView recyclerView,

RecyclerView.ViewHolder viewHolder,

RecyclerView.ViewHolder target) {

int from = viewHolder.getAdapterPosition();

int to = target.getAdapterPosition();

adapter.move(from, to);

return true;

}



@Override

public void onSelectedChanged(RecyclerView.ViewHolder viewHo
int actionState) {

if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {

viewHolder.itemView.setBackgroundColor(Color.LTGRAY)
}

drag and drop
int to = target.getAdapterPosition();

adapter.move(from, to);

return true;

}



@Override

public void onSelectedChanged(RecyclerView.ViewHolder viewHo
int actionState) {

if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {

viewHolder.itemView.setBackgroundColor(Color.LTGRAY)
}

super.onSelectedChanged(viewHolder, actionState);

}



@Override

public void clearView(RecyclerView recyclerView,

RecyclerView.ViewHolder viewHolder) {

super.clearView(recyclerView, viewHolder);

viewHolder.itemView.setBackgroundColor(Color.TRANSPARENT
}



…

}

};
drag and drop
独自のLayoutManager
独自のLayoutManager
1. RecyclerView.LayoutManagerを継承
2. generateDefaultLayoutParams() で
RecyclerView.LayoutParams() を返す
3. onLayoutChildren()で子ビューを配置
4. scrollVerticallyBy(), scrollHorizontallyBy()でスクロール分
だけ子ビューを移動&足りない分のビューを追加
onLayoutChildren()で子ビュー配置
1. detachAndScrapAttachedViews(recycler)で現在のビュー
をリサイクル対象にする
2. recycler.getViewForPosition(i)でアイテム用のビューを取得
3. addView()
4. measureChildWithMargins()でビューのサイズを計算
5. layoutDecorated(v, left, top, right, bottom)で配置
public class SimpleListLayoutManager extends
RecyclerView.LayoutManager {



@Override

public RecyclerView.LayoutParams generateDefaultLayoutParams()
return new RecyclerView.LayoutParams(

ViewGroup.LayoutParams.MATCH_PARENT,

ViewGroup.LayoutParams.WRAP_CONTENT);

}



@Override

public void onLayoutChildren(RecyclerView.Recycler recycler,
RecyclerView.State state) {
// 現在表示されている一番上のビューの位置を保持しておく

final View lastTopView = getChildCount() > 0 ?
getChildAt(0) : null;

final int lastTop = lastTopView != null ?
lastTopView.getTop() : getPaddingTop();

final int firstPosition = lastTopView != null ?
getPosition(lastTopView) : 0;

独自のLayoutManager
final int lastTop = lastTopView != null ?
lastTopView.getTop() : getPaddingTop();

final int firstPosition = lastTopView != null ?
getPosition(lastTopView) : 0;



// 現在のビューをスクラップにする

detachAndScrapAttachedViews(recycler);



int top = lastTop;

int bottom;

final int parentLeft = getPaddingLeft();

final int parentRight = getWidth() - getPaddingRight();

final int parentBottom = getHeight() - getPaddingBottom();



final int count = state.getItemCount();

for (int i = 0; firstPosition + i < count &&
top < parentBottom; i++, top = bottom) {

View v = recycler.getViewForPosition(firstPosition + i)
addView(v, i);

measureChildWithMargins(v, 0, 0);

bottom = top + getDecoratedMeasuredHeight(v);

layoutDecorated(v, parentLeft, top, parentRight, bottom
}

}

}
独自のLayoutManager
まとめ
まとめ
ListViewやGridViewで十分要求が満たせる場合は
無理にRecyclerViewを使う必要はありません
まとめ
• ListViewやGridViewでは実現できない配置にしたい
• スクロールの振る舞い(スピードや時間)をコントロール
したい
• アイテムが追加/削除されたときのアニメーションをコン
トロールしたい
• アイテムの選択をフォーカスでやりたい、フォーカスを細
かく制御したい
まとめ
• ListViewやGridViewでは実現できない配置にしたい
• スクロールの振る舞い(スピードや時間)をコントロール
したい
• アイテムが追加/削除されたときのアニメーションをコン
トロールしたい
• アイテムの選択をフォーカスでやりたい、フォーカスを細
かく制御したい
RecyclerView ならそれ簡単にできますよ
New
TechBoosterの
夏コミ本で
RecyclerViewに
ついて
書きます。
ありがとうございました。
http://techbooster.github.io/c88/

Master of RecyclerView