droidgirls recyclerview

70
RecyclerView droid girls meetup #2 Yuki Anzai (@yanzm)

Upload: yuki-anzai

Post on 14-Apr-2017

187 views

Category:

Technology


0 download

TRANSCRIPT

RecyclerView

droid girls meetup #2 Yuki Anzai (@yanzm)

• blog : Y.A.M の雑記帳

• y-anz-m.blogspot.com

• twitter : @yanzm (やんざむ)

• uPhyca Inc. (株式会社ウフィカ)

あんざいゆき

RecyclerView

A flexible view for providing a limited window

into a large data set.

RecyclerView

RecyclerView は サポートライブラリ

https://developer.android.com/topic/libraries/support-library/features.html#v7-recyclerview

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 ⚪ ×

*

* 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の 一番シンプルな 使い方

gradle の設定

dependencies { … compile 'com.android.support:recyclerview-v7:25.1.0'}

最低限必要なもの

• 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

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をつける

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

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

ItemViewType

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); } }

まとめ

• シンプルなList, Grid →

• ListView, GridView

• RecyclerView

• ListView, GridView では無理な配置 →

• RecyclerView

まとめ