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. EXPANDABLE RECYCLERVIEW AND YOU
    BY: AMANDA HILL

    View full-size slide

  2. ExpandableRecyclerView is an open source library for expanding and
    collapsing groups using RecyclerView.Adapter

    View full-size slide

  3. Because the Android SDK doesn’t provide an out of the box solution and
    my client really really wanted it

    View full-size slide

  4. BUT AREN’T THERE ALREADY
    A ZILLION OF LIBRARIES OUT
    THERE?

    View full-size slide

  5. THE HOW (DO I USE IT)

    View full-size slide

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

    View full-size slide

  7. THE GROUP !
    public class ExpandableGroup {
    private String title;
    private List items;
    public ExpandableGroup(String title, List items) {
    this.title = title;
    this.items = items;
    }
    }

    View full-size slide

  8. THE GROUP !
    public class Genre extends ExpandableGroup {
    public Genre(String title, List items) {
    super(title, items);
    }
    }

    View full-size slide

  9. 2. THE VIEW (HOLDERS)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. 3. THE ADAPTER

    View full-size slide

  15. ExpandableRecyclerViewAdapter

    View full-size slide

  16. ExpandableRecyclerViewAdapter extends RecyclerView.Adapter

    View full-size slide

  17. ExpandableRecyclerViewAdapter extends RecyclerView.Adapter

    View full-size slide

  18. THE CONSTRUCTOR

    View full-size slide

  19. public ExpandableRecyclerViewAdapter(List extends ExpandableGroup> groups) {
    super(groups);
    }

    View full-size slide

  20. FOUR ABSTRACT METHODS

    View full-size slide

  21. FOUR METHODS:
    public GVH onCreateGroupViewHolder(ViewGroup parent, int viewType);

    View full-size slide

  22. FOUR METHODS:
    public GVH onCreateGroupViewHolder(ViewGroup parent, int viewType);
    public CVH onCreateChildViewHolder(ViewGroup parent, int viewType);

    View full-size slide

  23. 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);

    View full-size slide

  24. 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);

    View full-size slide

  25. GENRE ADAPTER

    View full-size slide

  26. GENRE ADAPTER
    public class GenreAdapter extends ExpandableRecyclerViewAdapter {
    public GenreAdapter(List groups) {
    super(groups);
    }
    {...}
    {...}
    {...}
    {...}
    }

    View full-size slide

  27. GENRE ADAPTER
    public class GenreAdapter extends ExpandableRecyclerViewAdapter {
    {...}
    @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);
    }
    {...}
    {...}
    {...}
    }

    View full-size slide

  28. GENRE ADAPTER
    public class GenreAdapter extends ExpandableRecyclerViewAdapter {
    {...}
    {...}
    @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);
    }
    {...}
    {...}
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  31. GENRE ADAPTER
    public class GenreAdapter extends ExpandableRecyclerViewAdapter {
    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);
    }
    }

    View full-size slide

  32. 4. THE ACTIVITY

    View full-size slide

  33. public class GenreActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_genre);
    getSupportActionBar().setTitle("Genres");
    }
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  36. 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();
    + }
    }

    View full-size slide

  37. THE HOW (DOES IT WORK)

    View full-size slide

  38. “[RecyclerView] Adapters provide a binding from an
    app-specific data set to views that are displayed within a
    RecyclerView”

    View full-size slide

  39. !
    DATA and VIEWS

    View full-size slide

  40. public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
    public abstract void onBindViewHolder(VH holder, int position);
    public abstract int getItemCount();

    View full-size slide

  41. 1. getItemCount()

    View full-size slide

  42. 1. getItemCount()
    2. getItemViewType(int position)

    View full-size slide

  43. 1. getItemCount()
    2. getItemViewType(int position)
    3. onCreateViewHolder(ViewGroup parent, int
    viewType)

    View full-size slide

  44. SINGLE DIMENSIONAL DATA
    0 -[ "rock",
    1 -[ "jazz",
    2 -[ "classic",
    3 -[ "salsa",
    4 -[ "bluegrass",

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  48. THE PROBLEM
    WE HAVE TWO DIMENSIONAL DATA !, BUT
    THE RECYCLERVIEW.ADAPTER WORKS WITH
    SINGLE DIMENSIONAL !
    flat list positions

    View full-size slide

  49. FLAT LIST POSITION
    THE POSITION OF AN ITEM RELATIVE TO ALL
    THE OTHER visible ITEMS ON THE SCREEN

    View full-size slide

  50. SAY HELLO
    ExpandableList

    View full-size slide

  51. EXPANDABLELIST
    Translator BETWEEN THE FLAT LIST
    POSITION AND THE BACKING DATA

    View full-size slide

  52. SHOW ME THE MONEY CODE

    View full-size slide

  53. 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");
    }

    View full-size slide

  54. SAY HELLO
    ExpandableListPosition

    View full-size slide

  55. ExpandableListPosition

    View full-size slide

  56. ExpandableListPosition
    ▸ int type

    View full-size slide

  57. ExpandableListPosition
    ▸ int type
    ▸ int groupPosition

    View full-size slide

  58. ExpandableListPosition
    ▸ int type
    ▸ int groupPosition
    ▸ int childPosition

    View full-size slide

  59. ExpandableListPosition
    ▸ int type
    ▸ int groupPosition
    ▸ int childPosition
    ▸ int flatListPosition

    View full-size slide

  60. ExpandableRecyclerViewAdapter

    View full-size slide

  61. ExpandableRecyclerViewAdapter
    @Override
    public int getItemViewType(int position) {
    return expandableList.getUnflattenedPosition(position).type;
    }

    View full-size slide

  62. 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");
    }
    }

    View full-size slide

  63. 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;
    }
    }

    View full-size slide

  64. THE WHAT IF (I WANTED MORE)?

    View full-size slide

  65. SAY HELLO
    MultiTypeExpandableRecyclerViewAdapter

    View full-size slide

  66. MultiTypeExpandableRecyclerViewAdapter
    Allows subclasses to implement multiple different view types for both
    children and group

    View full-size slide

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

    View full-size slide

  68. THE CHILD !
    public class Artist {
    private String title;
    + private boolean isFavorite;
    public Artist(String title, boolean isFavorite) {
    this.title = title;
    + this.isFavorite = isFavorite;
    }
    }

    View full-size slide

  69. 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());
    }
    }

    View full-size slide

  70. GENRE ADAPTER

    View full-size slide

  71. MULTITYPE GENRE ADAPTER

    View full-size slide

  72. MULTITYPE GENRE ADAPTER
    public class MultiTypeGenreAdapter
    extends MultiTypeExpandableRecyclerViewAdapter<> {
    }

    View full-size slide

  73. MULTITYPE GENRE ADAPTER
    public class MultiTypeGenreAdapter
    extends MultiTypeExpandableRecyclerViewAdapter {
    }

    View full-size slide

  74. MULTITYPE GENRE ADAPTER
    public class MultiTypeGenreAdapter
    extends MultiTypeExpandableRecyclerViewAdapter {
    public static final int FAVORITE_ARTIST_VIEW_TYPE = 3;
    public static final int ARTIST_VIEW_TYPE = 4;
    }

    View full-size slide

  75. MULTITYPE GENRE ADAPTER
    public class MultiTypeGenreAdapter
    extends MultiTypeExpandableRecyclerViewAdapter {
    {...}
    @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;
    }
    }
    }

    View full-size slide

  76. MULTITYPE GENRE ADAPTER
    public class MultiTypeGenreAdapter
    extends MultiTypeExpandableRecyclerViewAdapter {
    {...}
    {...}
    @Override
    public boolean isChild(int viewType) {
    return viewType == FAVORITE_ARTIST_VIEW_TYPE || viewType == ARTIST_VIEW_TYPE;
    }

    View full-size slide

  77. MULTITYPE GENRE ADAPTER
    public class MultiTypeGenreAdapter
    extends MultiTypeExpandableRecyclerViewAdapter {
    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;
    }
    }

    View full-size slide

  78. HOW IT'S MADE !

    View full-size slide

  79. 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)

    View full-size slide

  80. EXPANDABLE RECYCLERVIEW ADAPTER
    @Override
    public int getItemViewType(int position) {
    return expandableList.getUnflattenedPosition(position).type;
    }
    }

    View full-size slide

  81. 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;
    }
    }

    View full-size slide

  82. THE WHAT IF (I WANTED Even MORE)?

    View full-size slide

  83. SAY HELLO
    ExpandableCheckRecyclerView

    View full-size slide

  84. ExpandableCheckRecyclerView
    An extension of expandablerecyclerview for checking single
    or multiple children within a group

    View full-size slide

  85. TEACH ME HOW TO DOUGIE IMPLEMENT THAT

    View full-size slide

  86. THE GROUP !
    public class Genre extends ExpandableGroup {
    public Genre(String title, List items) {
    super(title, items);
    }
    }

    View full-size slide

  87. THE CHECK GROUP ✔ !
    - public class Genre extends ExpandableGroup {
    + public class Genre extends SingleCheckExpandableGroup {
    public Genre(String title, List items) {
    super(title, items);
    }
    }

    View full-size slide

  88. public abstract class CheckedExpandableGroup extends ExpandableGroup {
    public abstract void onChildClicked(int childIndex, boolean checked);
    }

    View full-size slide

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

    View full-size slide

  90. BACK TO BUILDING !

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  93. 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() {
    +
    + }
    }

    View full-size slide

  94. 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;
    + }
    }

    View full-size slide

  95. 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;
    + }
    }

    View full-size slide

  96. THE CODE !
    https://github.com/thoughtbot/expandable-recycler-view
    THE PERSON !
    @mandybess

    View full-size slide