droidgirls recyclerview
TRANSCRIPT
• blog : Y.A.M の雑記帳
• y-anz-m.blogspot.com
• twitter : @yanzm (やんざむ)
• uPhyca Inc. (株式会社ウフィカ)
あんざいゆき
RecyclerView
RecyclerView は サポートライブラリ
https://developer.android.com/topic/libraries/support-library/features.html#v7-recyclerview
• RecyclerView
• データを表示するためのスクロール可能なView
• RecyclerView.LayoutManager
• アイテム用のビューのサイズを計算し、配置する
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 ⚪ ×
*
* android.support.v7.widget.DividerItemDecoration が 25.0.0 で追加されました
ListView vs RecyclerView
ListView RecyclerView
Filter ⚪ 自分で実装
FadingEdge ⚪ 自分で実装
Header, Footer ⚪ 自分で実装
StaggeredGrid × ⚪
ListView vs RecyclerView
ListView RecyclerView
追加・削除の アニメーション
自分で実装 ⚪
Swipe to Dismiss 自分で実装 ⚪
Drag & Drop 自分で実装 ⚪
横スクロール配置 × ⚪
最低限必要なもの
• RecyclerView
• RecyclerView.LayoutManager
• RecyclerView.Adapter
• RecyclerView.ViewHolder
最低限必要なもの
• RecyclerView
• RecyclerView.LayoutManager
• RecyclerView.Adapter
• RecyclerView.ViewHolder
<android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutManager="LinearLayoutManager"/>
RecyclerView + LayoutManager
<android.support.v7.widget.RecyclerView 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
<android.support.v7.widget.RecyclerView 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
public class ViewHolder extends RecyclerView.ViewHolder { private static final int LAYOUT_ID = android.R.layout.simple_list_item_1; @NonNull public static ViewHolder create(@NonNull LayoutInflater inflater, ViewGroup parent) { return new ViewHolder(inflater.inflate(LAYOUT_ID, parent, false)); } final TextView textView; private ViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(android.R.id.text1); }}
ViewHolder
public class SimpleAdapter extends RecyclerView.Adapter<ViewHolder> { @NonNull private final List<String> data; public SimpleAdapter(@NonNull List<String> data) { this.data = data; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); return ViewHolder.create(inflater, parent); } @Override public void onBindViewHolder(ViewHolder holder, int position) { final String text = data.get(position); holder.textView.setText(text); } @Override public int getItemCount() { return data.size(); }}
Adapter
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); 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(data); recyclerView.setAdapter(adapter); }}
Activity
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
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
DividerItemDecoration
• v25.0.0 で DividerItemDecoration が追加されたのでそれを使うのが簡単
• https://developer.android.com/reference/android/support/v7/widget/DividerItemDecoration.html
• 一番最後のアイテムの下にも描画されてしまう
recyclerView.addItemDecoration( new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
ItemDecoration
• 装飾を行うためのクラス
• アイテム用のViewのoffsetを指定
• onDraw()でRecyclerViewの下に描画
• onDrawOver()でRecyclerVieの上に描画
アイテム用のViewのOffsetを指定
final int offset = (int) (8 * getResources().getDisplayMetrics().density); 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を指定
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);
public 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) { final int position = ((RecyclerView.LayoutParams) view.getLayoutParams()) .getViewLayoutPosition(); // 位置が2番目以降なら上部にdividerを描画したいので、 // divider分だけ上をあける int top = position == 0 ? 0 : dividerHeight; outRect.set(0, top, 0, 0); }
Dividerを描画
@Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(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()を入れないと // 追加・削除のアニメーション時の位置が変になる 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
• 方法がいくつかある
• ViewHolderのitemViewにView.setOnClickListener
• ItemDecorationでタップされたアイテムの位置にselectorを描画
OnItemClick
• 方法がいくつかある
• ViewHolderのitemViewにView.setOnClickListener
• ItemDecorationでタップされたアイテムの位置にselectorを描画
v17 leanback library はこっち
public class SimpleAdapter extends RecyclerView.Adapter<ViewHolder> { … protected void onItemClicked(String text) { } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); final ViewHolder holder = ViewHolder.create(inflater, parent); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final int position = holder.getAdapterPosition(); final String text = data.get(position); onItemClicked(text); } }); return holder; } …}
OnItemClick
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); … final SimpleAdapter adapter = new SimpleAdapter(data) { @Override protected void onItemClicked(String text) { super.onItemClicked(text); Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show(); } }; recyclerView.setAdapter(adapter);
OnItemClick
notify**
• ArrayAdapterに相当するものは用意されていない
• データの追加・削除・変更時にはnotifyItem**()を呼ぶ
• notifyDataSetChanged()はそれ以外のときだけにする
notify**
• notifyItemChanged(int position)
• positionの位置のアイテムの変更された
• notifyItemInserted(int position)
• posiitonの位置にアイテムが追加された
• notifyItemRemoved(int position)
• positionの位置のアイテムが削除された
notify**
• notifyItemMoved(int fromPosition, int toPosition)
• fromPositionにあったアイテムがtoPositionに移動した
• notifyItemRangeChanged(int positionStart, int itemCount)
• positionStartからitemCount個のアイテムが変更された
• notifyItemRangeInserted(int positionStart, int itemCount)
• positionStartにitemCount個のアイテムが追加された
notify**
• notifyItemRangeRemoved(int positionStart, int itemCount)
• positionStartからitemCount個のアイテムが削除された
• notifyDataSetChanged()
• データセットが変更された
public abstract class ArrayAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> { private final Object lock = new Object(); private final List<T> objects; public ArrayAdapter() { this(new ArrayList<T>()); } public ArrayAdapter(List<T> objects) { this.objects = objects; } public void add(@NonNull T object) { final int position; synchronized (lock) { position = objects.size(); objects.add(object); } notifyItemInserted(position); }
ArrayAdapter的なRecyclerView用Adapter
public void addAll(@NonNull Collection<? extends T> collection) { final int itemCount = collection.size(); final int positionStart; synchronized (lock) { positionStart = objects.size(); 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) { final int position = objects.indexOf(object); synchronized (lock) { objects.remove(object); } notifyItemRemoved(position); }}
ArrayAdapter的なRecyclerView用Adapter
getItemViewType()
• レイアウトの種類(ViewHolder の種類)に応じて別の値を返すようにする
• データの種類ではない
• 同じ itemViewType で ViewHolder が再利用される
Header と Footer をつくる
• レイアウトの種類(ViewHolder の種類)に応じて別の値を返すようにする
• データの種類ではない
• 同じ itemViewType で ViewHolder が再利用される
Header と Footer をつくる
public class SimpleAdapter2 extends RecyclerView.Adapter { private static final int VIEW_TYPE_HEADER = 0; private static final int VIEW_TYPE_FOOTER = 1; private static final int VIEW_TYPE_ITEM = 2; … @Override public int getItemCount() { return data.size() + 2; // header + footer } @Override public int getItemViewType(int position) { if (position == 0) { return VIEW_TYPE_HEADER; } if (position == getItemCount() - 1) { return VIEW_TYPE_FOOTER; } return VIEW_TYPE_ITEM; }}
Header と Footer をつくる
public class SimpleAdapter2 extends RecyclerView.Adapter { … @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); switch (viewType) { case VIEW_TYPE_HEADER: { return HeaderViewHolder.create(inflater, parent); } case VIEW_TYPE_FOOTER: { return FooterViewHolder.create(inflater, parent); } case VIEW_TYPE_ITEM: { final ViewHolder holder = ViewHolder.create(inflater, parent); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final int position = holder.getAdapterPosition(); final String text = data.get(position); onItemClicked(text); } }); return holder; } } return null; }
Header と Footer をつくる
public class SimpleAdapter2 extends RecyclerView.Adapter { … @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof ViewHolder) { final ViewHolder viewHolder = (ViewHolder) holder; final String text = data.get(position); viewHolder.textView.setText(text); } }