Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Droidcon NYC 2016: Expandable RecyclerView and You

Amanda Hill
September 29, 2016

Droidcon NYC 2016: Expandable RecyclerView and You

Amanda Hill

September 29, 2016
Tweet

More Decks by Amanda Hill

Other Decks in Programming

Transcript

  1. Because the Android SDK doesn’t provide an out of the

    box solution and my client really really wanted it
  2. THE CHILD ! public class Artist { private String title;

    public Artist(String title) { this.title = title; } }
  3. THE GROUP ! public class ExpandableGroup<T> { private String title;

    private List<T> items; public ExpandableGroup(String title, List<T> items) { this.title = title; this.items = items; } }
  4. THE GROUP ! public class Genre extends ExpandableGroup<Artist> { public

    Genre(String title, List<Artist> items) { super(title, items); } }
  5. THE CHILD ! public class ArtistViewHolder extends RecyclerView.ViewHolder { private

    TextView childTextView; public ArtistViewHolder(View itemView) { super(itemView); childTextView = (TextView) itemView.findViewById(R.id.list_item_artist_name); } }
  6. THE CHILD ! - public class ArtistViewHolder extends RecyclerView.ViewHolder {

    + public class ArtistViewHolder extends ChildViewHolder { private TextView childTextView; public ArtistViewHolder(View itemView) { super(itemView); childTextView = (TextView) itemView.findViewById(R.id.list_item_artist_name); } }
  7. THE GROUP ! public class GenreViewHolder extends RecyclerView.ViewHolder { private

    TextView bandName; private ImageView arrow; private ImageView icon; public GenreViewHolder(View itemView) { super(itemView); bandName = (TextView) itemView.findViewById(R.id.list_item_genre_name); arrow = (ImageView) itemView.findViewById(R.id.list_item_genre_arrow); icon = (ImageView) itemView.findViewById(R.id.list_item_genre_icon); }
  8. THE GROUP ! - public class GenreViewHolder extends RecyclerView.ViewHolder {

    + public class GenreViewHolder extends GroupViewHolder { private TextView bandName; private ImageView arrow; private ImageView icon; public GenreViewHolder(View itemView) { super(itemView); bandName = (TextView) itemView.findViewById(R.id.list_item_genre_name); arrow = (ImageView) itemView.findViewById(R.id.list_item_genre_arrow); icon = (ImageView) itemView.findViewById(R.id.list_item_genre_icon); }
  9. FOUR METHODS: public GVH onCreateGroupViewHolder(ViewGroup parent, int viewType); public CVH

    onCreateChildViewHolder(ViewGroup parent, int viewType); public void onBindGroupViewHolder(GVH holder, int flatPosition, ExpandableGroup group);
  10. FOUR METHODS: public GVH onCreateGroupViewHolder(ViewGroup parent, int viewType); public CVH

    onCreateChildViewHolder(ViewGroup parent, int viewType); public void onBindGroupViewHolder(GVH holder, int flatPosition, ExpandableGroup group); public void onBindChildViewHolder(CVH holder, int flatPosition, ExpandableGroup group, int childIndex);
  11. GENRE ADAPTER public class GenreAdapter extends ExpandableRecyclerViewAdapter<GenreViewHolder, ArtistViewHolder> { {...}

    @Override public GenreViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.list_item_genre, parent, false); return new GenreViewHolder(view); } {...} {...} {...} }
  12. GENRE ADAPTER public class GenreAdapter extends ExpandableRecyclerViewAdapter<GenreViewHolder, ArtistViewHolder> { {...}

    {...} @Override public ArtistViewHolder onCreateChildViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.list_item_artist, parent, false); return new ArtistViewHolder(view); } {...} {...} }
  13. GENRE ADAPTER public class GenreAdapter extends ExpandableRecyclerViewAdapter<GenreViewHolder, ArtistViewHolder> { {...}

    {...} {...} @Override public void onBindChildViewHolder(ArtistViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) { final Artist artist = ((Genre) group).getItems().get(childIndex); holder.bindArtist(artist); } {...} }
  14. GENRE ADAPTER public class GenreAdapter extends ExpandableRecyclerViewAdapter<GenreViewHolder, ArtistViewHolder> { {...}

    {...} {...} {...} @Override public void onBindGroupViewHolder(GenreViewHolder holder, int flatPosition, ExpandableGroup group) { holder.bindGenre((Genre) group); } }
  15. GENRE ADAPTER public class GenreAdapter extends ExpandableRecyclerViewAdapter<GenreViewHolder, ArtistViewHolder> { public

    GenreAdapter(ListGenre> groups) { super(groups); } @Override public GenreViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.list_item_genre, parent, false); return new GenreViewHolder(view); } @Override public ArtistViewHolder onCreateChildViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.list_item_artist, parent, false); return new ArtistViewHolder(view); } @Override public void onBindChildViewHolder(ArtistViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) { final Artist artist = ((Genre) group).getItems().get(childIndex); holder.bindArtist(artist); } @Override public void onBindGroupViewHolder(GenreViewHolder holder, int flatPosition, ExpandableGroup group) { holder.bindGenre((Genre) group); } }
  16. public class GenreActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable

    Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_genre); getSupportActionBar().setTitle("Genres"); } }
  17. public class GenreActivity extends AppCompatActivity { + public GenreAdapter adapter;

    @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_genre); getSupportActionBar().setTitle("Genres"); + RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + adapter = new GenreAdapter(makeGenres()); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(adapter); } }
  18. !

  19. !

  20. public class GenreViewHolder extends GroupViewHolder { private TextView bandName; private

    ImageView arrow; private ImageView icon; public GenreViewHolder(View itemView) { super(itemView); bandName = (TextView) itemView.findViewById(R.id.list_item_genre_name); arrow = (ImageView) itemView.findViewById(R.id.list_item_genre_arrow); icon = (ImageView) itemView.findViewById(R.id.list_item_genre_icon); } }
  21. public class GenreViewHolder extends GroupViewHolder { private TextView bandName; private

    ImageView arrow; private ImageView icon; public GenreViewHolder(View itemView) { super(itemView); bandName = (TextView) itemView.findViewById(R.id.list_item_genre_name); arrow = (ImageView) itemView.findViewById(R.id.list_item_genre_arrow); icon = (ImageView) itemView.findViewById(R.id.list_item_genre_icon); } + @Override + public void expand() { + animateExpand(); + } + @Override + public void collapse() { + animateCollapse(); + } }
  22. !

  23. !

  24. “[RecyclerView] Adapters provide a binding from an app-specific data set

    to views that are displayed within a RecyclerView”
  25. public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); public abstract void

    onBindViewHolder(VH holder, int position); public abstract int getItemCount();
  26. SINGLE DIMENSIONAL DATA 0 -[ "rock", 1 -[ "jazz", 2

    -[ "classic", 3 -[ "salsa", 4 -[ "bluegrass",
  27. TWO DIMENSIONAL DATA 0 -[ "rock", "artists":[ 1 -[ "queen",

    2 -[ "styx", 3 -[ "reoSpeedwagon", 4 -[ "boston", ], 5 -[ "jazz", 6 -[ "classical", 7 -[ "salsa", 8 -[ "bluegrass"
  28. TWO DIMENSIONAL DATA 0 -[ "rock", "artists":[ 1 -[ "queen",

    2 -[ "styx", 3 -[ "reoSpeedwagon", 4 -[ "boston", ], 5 -[ "jazz", 6 -[ "classical", 7 -[ "salsa", 8 -[ "bluegrass"
  29. !

  30. THE PROBLEM WE HAVE TWO DIMENSIONAL DATA !, BUT THE

    RECYCLERVIEW.ADAPTER WORKS WITH SINGLE DIMENSIONAL DATA "
  31. THE PROBLEM WE HAVE TWO DIMENSIONAL DATA !, BUT THE

    RECYCLERVIEW.ADAPTER WORKS WITH SINGLE DIMENSIONAL ! flat list positions
  32. FLAT LIST POSITION THE POSITION OF AN ITEM RELATIVE TO

    ALL THE OTHER visible ITEMS ON THE SCREEN
  33. ExpandableList public ExpandableListPosition getUnflattenedPosition(int flPos) { int groupItemCount; int adapted

    = flPos; for (int i = 0; i < groups.size(); i++) { groupItemCount = numberOfVisibleItemsInGroup(i); if (adapted == 0) { return ExpandableListPosition.obtain(ExpandableListPosition.GROUP, i, -1, flPos); } else if (adapted < groupItemCount) { return ExpandableListPosition.obtain(ExpandableListPosition.CHILD, i, adapted - 1, flPos); } adapted -= groupItemCount; } throw new RuntimeException("Unknown state"); }
  34. ExpandableRecyclerViewAdapter @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch

    (viewType) { case ExpandableListPosition.GROUP: GVH gvh = onCreateGroupViewHolder(parent, viewType); return gvh; case ExpandableListPosition.CHILD: CVH cvh = onCreateChildViewHolder(parent, viewType); return cvh; default: throw new IllegalArgumentException("viewType is not valid"); } }
  35. ExpandableRecyclerViewAdapter @Override public void onBindViewHolder(ViewHolder holder, int position) { ExpandableListPosition

    listPos = expandableList.getUnflattenedPosition(position); ExpandableGroup group = expandableList.getExpandableGroup(listPos); switch (listPos.type) { case ExpandableListPosition.GROUP: onBindGroupViewHolder((GVH) holder, position, group); break; case ExpandableListPosition.CHILD: onBindChildViewHolder((CVH) holder, position, group, listPos.childPos); break; } }
  36. !

  37. !

  38. THE CHILD ! public class Artist { private String title;

    public Artist(String title) { this.title = title; } }
  39. THE CHILD ! public class Artist { private String title;

    + private boolean isFavorite; public Artist(String title, boolean isFavorite) { this.title = title; + this.isFavorite = isFavorite; } }
  40. THE FAVORITE CHILD ! ❤ public class FavoriteArtistViewHolder extends ChildViewHolder

    { private TextView favoriteArtistName; public FavoriteArtistViewHolder(View itemView) { super(itemView); favoriteArtistName = (TextView) itemView.findViewById(R.id.list_item_favorite_artist_name); } public void bindArtist(Artist artist) { favoriteArtistName.setText(artist.getTitle()); } }
  41. MULTITYPE GENRE ADAPTER public class MultiTypeGenreAdapter extends MultiTypeExpandableRecyclerViewAdapter<GenreViewHolder, ChildViewHolder> {

    public static final int FAVORITE_ARTIST_VIEW_TYPE = 3; public static final int ARTIST_VIEW_TYPE = 4; }
  42. MULTITYPE GENRE ADAPTER public class MultiTypeGenreAdapter extends MultiTypeExpandableRecyclerViewAdapter<GenreViewHolder, ChildViewHolder> {

    {...} @Override public int getChildViewType(int position, ExpandableGroup group, int childIndex) { if (((Genre) group).getItems().get(childIndex).isFavorite()) { return FAVORITE_ARTIST_VIEW_TYPE; } else { return ARTIST_VIEW_TYPE; } } }
  43. MULTITYPE GENRE ADAPTER public class MultiTypeGenreAdapter extends MultiTypeExpandableRecyclerViewAdapter<GenreViewHolder, ChildViewHolder> {

    {...} {...} @Override public boolean isChild(int viewType) { return viewType == FAVORITE_ARTIST_VIEW_TYPE || viewType == ARTIST_VIEW_TYPE; }
  44. MULTITYPE GENRE ADAPTER public class MultiTypeGenreAdapter extends MultiTypeExpandableRecyclerViewAdapter<GenreViewHolder, ChildViewHolder> {

    public static final int FAVORITE_ARTIST_VIEW_TYPE = 3; public static final int ARTIST_VIEW_TYPE = 4; @Override public boolean isChild(int viewType) { return viewType == FAVORITE_ARTIST_VIEW_TYPE || viewType == ARTIST_VIEW_TYPE; } @Override public int getChildViewType(int position, ExpandableGroup group, int childIndex) { if (((Genre) group).getItems().get(childIndex).isFavorite()) { return FAVORITE_ARTIST_VIEW_TYPE; } else { return ARTIST_VIEW_TYPE; } }
  45. GOOD OL’ VIEWTYPES public int getChildViewType(int position, ExpandableGroup group, int

    childIndex) public int getGroupViewType(int position, ExpandableGroup group) public boolean isGroup(int viewType) public boolean isChild(int viewType)
  46. MULTITYPE EXPANDABLE RECYCLERVIEW ADAPTER @Override public int getItemViewType(int position) {

    // return expandableList.getUnflattenedPosition(position).type; ExpandableListPosition listPosition = expandableList.getUnflattenedPosition(position); ExpandableGroup group = expandableList.getExpandableGroup(listPosition); int viewType = listPosition.type; switch (viewType) { case ExpandableListPosition.GROUP: return getGroupViewType(position, group); case ExpandableListPosition.CHILD: return getChildViewType(position, group, listPosition.childPos); default: return viewType; } }
  47. THE GROUP ! public class Genre extends ExpandableGroup<Artist> { public

    Genre(String title, List<Artist> items) { super(title, items); } }
  48. THE CHECK GROUP ✔ ! - public class Genre extends

    ExpandableGroup<Artist> { + public class Genre extends SingleCheckExpandableGroup<Artist> { public Genre(String title, List<Artist> items) { super(title, items); } }
  49. !

  50. public class SingleCheckExpandableGroup extends CheckedExpandableGroup { @Override public void onChildClicked(int

    childIndex, boolean checked) { if (checked) { for (int i = 0; i < getItemCount(); i++) { unCheckChild(i); } checkChild(childIndex); } }
  51. THE CHILD ! public class ArtistViewHolder extends ChildViewHolder { private

    TextView childTextView; public ArtistViewHolder(View itemView) { super(itemView); childTextView = (TextView) itemView.findViewById(R.id.list_item_artist_name); } }
  52. THE CHECKABLE CHILD ✔ ! + public class MultiCheckArtistViewHolder extends

    CheckableChildViewHolder { - public class ArtistViewHolder extends ChildViewHolder { private TextView childTextView; public ArtistViewHolder(View itemView) { super(itemView); childTextView = (TextView) itemView.findViewById(R.id.list_item_artist_name); } }
  53. THE CHECKABLE CHILD ✔ ! + public class MultiCheckArtistViewHolder extends

    CheckableChildViewHolder { private TextView childTextView; public ArtistViewHolder(View itemView) { super(itemView); childTextView = (TextView) itemView.findViewById(R.id.list_item_artist_name); } + @Override + public Checkable getCheckable() { + + } }
  54. THE CHECKABLE CHILD ✔ ! + public class MultiCheckArtistViewHolder extends

    CheckableChildViewHolder { + private CheckedTextView childTextView; public ArtistViewHolder(View itemView) { super(itemView); childTextView = (TextView) itemView.findViewById(R.id.list_item_artist_name); } + @Override + public Checkable getCheckable() { + return childTextView; + } }
  55. THE CHECKABLE CHILD ✔ ! + public class MultiCheckArtistViewHolder extends

    CheckableChildViewHolder { + private CheckedTextView childTextView; public ArtistViewHolder(View itemView) { super(itemView); - childTextView = (TextView) itemView.findViewById(R.id.list_item_artist_name); + childTextView = (CheckedTextView) itemView.findViewById(R.id.list_item_artist_name); } + @Override + public Checkable getCheckable() { + + return childTextView; + } }