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 Slide

  2. THOUGHTBOT

    View Slide

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

    View Slide

  4. COLLAPSED

    View Slide

  5. EXPANDED

    View Slide

  6. View Slide

  7. THE WHY

    View Slide

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

    View Slide

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

    View Slide

  10. YUP.

    View Slide

  11. THE HOW (DO I USE IT)

    View Slide

  12. View Slide

  13. 1. MODELING

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. 2. THE VIEW (HOLDERS)

    View Slide

  18. 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 Slide

  19. 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 Slide

  20. 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 Slide

  21. 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 Slide

  22. 3. THE ADAPTER

    View Slide

  23. ExpandableRecyclerViewAdapter

    View Slide

  24. ExpandableRecyclerViewAdapter extends RecyclerView.Adapter

    View Slide

  25. ExpandableRecyclerViewAdapter extends RecyclerView.Adapter

    View Slide

  26. THE CONSTRUCTOR

    View Slide

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

    View Slide

  28. FOUR ABSTRACT METHODS

    View Slide

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

    View Slide

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

    View Slide

  31. 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 Slide

  32. 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 Slide

  33. GENRE ADAPTER

    View Slide

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

    View Slide

  35. 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 Slide

  36. 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 Slide

  37. 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 Slide

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

    View Slide

  39. 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 Slide

  40. 4. THE ACTIVITY

    View Slide

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

    View Slide

  42. 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 Slide

  43. !

    View Slide

  44. View Slide

  45. !

    View Slide

  46. 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 Slide

  47. 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 Slide

  48. !

    View Slide

  49. View Slide

  50. !

    View Slide

  51. THE HOW (DOES IT WORK)

    View Slide

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

    View Slide

  53. !
    DATA and VIEWS

    View Slide

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

    View Slide

  55. View Slide

  56. 1. getItemCount()

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  62. !

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  66. ROCK
    ?

    View Slide

  67. ROCK
    0

    View Slide

  68. CLASSIC
    ?

    View Slide

  69. CLASSIC
    6

    View Slide

  70. SAY HELLO
    ExpandableList

    View Slide

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

    View Slide

  72. SHOW ME THE MONEY CODE

    View Slide

  73. 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 Slide

  74. SAY HELLO
    ExpandableListPosition

    View Slide

  75. ExpandableListPosition

    View Slide

  76. ExpandableListPosition
    ▸ int type

    View Slide

  77. ExpandableListPosition
    ▸ int type
    ▸ int groupPosition

    View Slide

  78. ExpandableListPosition
    ▸ int type
    ▸ int groupPosition
    ▸ int childPosition

    View Slide

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

    View Slide

  80. ExpandableRecyclerViewAdapter

    View Slide

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

    View Slide

  82. 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 Slide

  83. 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 Slide

  84. !

    View Slide

  85. THE WHAT IF (I WANTED MORE)?

    View Slide

  86. SAY HELLO
    MultiTypeExpandableRecyclerViewAdapter

    View Slide

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

    View Slide

  88. View Slide

  89. !

    View Slide

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

    View Slide

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

    View Slide

  92. 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 Slide

  93. GENRE ADAPTER

    View Slide

  94. MULTITYPE GENRE ADAPTER

    View Slide

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

    View Slide

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

    View Slide

  97. 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 Slide

  98. 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 Slide

  99. 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 Slide

  100. 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 Slide

  101. HOW IT'S MADE !

    View Slide

  102. 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 Slide

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

    View Slide

  104. 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 Slide

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

    View Slide

  106. SAY HELLO
    ExpandableCheckRecyclerView

    View Slide

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

    View Slide

  108. View Slide

  109. TEACH ME HOW TO DOUGIE IMPLEMENT THAT

    View Slide

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

    View Slide

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

    View Slide

  112. !

    View Slide

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

    View Slide

  114. 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 Slide

  115. BACK TO BUILDING !

    View Slide

  116. 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 Slide

  117. 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 Slide

  118. 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 Slide

  119. 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 Slide

  120. 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 Slide

  121. View Slide

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

    View Slide