This is an HarmonyOS library with Custom BaseItemProvider for expanding and collapsing groups.
This library is inspired by version 1.5 of ExpandableRecyclerView library.
This library allows us to add child items within the group items in a List View. It also demonstrates how we can add our favourite, single checker and multichecker child Items.
- For using ExpandableRecyclerView module in sample app, include the source code and add the below dependencies in entry/build.gradle to generate hap/expandablerecyclerview.har.
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.har'])
testImplementation 'junit:junit:4.13'
implementation project(':expandablerecyclerview')
}
- For using ExpandableRecyclerView in separate application using har file, add the har file in the entry/libs folder and add the dependencies in entry/build.gradle file.
dependencies {
implementation fileTree(dir: 'libs', include: ['*.har'])
testImplementation 'junit:junit:4.13'
}
Let's say you are a rock star 🎸 and you want to build an app to show a list of your favorite Genres with a list of their top Artists.
First create the Ability Slice and call the respective controller class by passing view and context as arguments and then call the initViews
method of that class.
public class ExpandAbilitySlice extends AbilitySlice {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
ComponentContainer rootView = (ComponentContainer) LayoutScatter.getInstance(this)
.parse(ResourceTable.Layout_ability_expandlist, null, false);
super.setUIContent(rootView);
ExpandAbilityController controller = new ExpandAbilityController(this, rootView);
controller.initViews();
}
}
Each controller class extends the common helper class. Inside the controller class, initViews
method will call initializeViews
method of the parent class to initialize the view components.
public void initViews() {
super.initializeViews(ResourceTable.Id_root_expand, ResourceTable.Id_lcGroupItems_expand);
toggleBtn = (Button) rootView.findComponentById(ResourceTable.Id_toogle);
this.prepareExpandableListAdapter();
}
protected void initializeViews(int rootViewId, int containerId) {
ScrollView parentLayout = (ScrollView) rootView.findComponentById(rootViewId);
parentLayout.setBackground(getShapeElement(ResUtil.getColor(context, ResourceTable.Color_white)));
mGroupContainer = (ExpandableListContainer) rootView.findComponentById(containerId);
getGroupItems();
getGroupIcons();
}
Then, Add all the Genre items in mGroupNameItem
and their corresponding image in mGroupImageItem
. We are maintaining a copy of the group items for finding the child items of each groups from a Hashmap by using their index value in mFinalGroupNameItem
list.
private void getGroupItems() {
mGroupNameItem.add(new ParentChild(null, ResUtil.getString(context, ResourceTable.String_item_Rock)));
mGroupNameItem.add(new ParentChild(null, ResUtil.getString(context, ResourceTable.String_item_Jazz)));
mGroupNameItem.add(new ParentChild(null, ResUtil.getString(context, ResourceTable.String_item_Classic)));
mGroupNameItem.add(new ParentChild(null, ResUtil.getString(context, ResourceTable.String_item_Salsa)));
mGroupNameItem.add(new ParentChild(null, ResUtil.getString(context, ResourceTable.String_item_Bluegrass)));
mFinalGroupNameItem.add(ResUtil.getString(context, ResourceTable.String_item_Rock));
mFinalGroupNameItem.add(ResUtil.getString(context, ResourceTable.String_item_Jazz));
mFinalGroupNameItem.add(ResUtil.getString(context, ResourceTable.String_item_Classic));
mFinalGroupNameItem.add(ResUtil.getString(context, ResourceTable.String_item_Salsa));
mFinalGroupNameItem.add(ResUtil.getString(context, ResourceTable.String_item_Bluegrass));
}
private void getGroupIcons() {
mGroupImageItem.add(ResourceTable.Media_rock);
mGroupImageItem.add(ResourceTable.Media_jazz);
mGroupImageItem.add(ResourceTable.Media_classic);
mGroupImageItem.add(ResourceTable.Media_salsa);
mGroupImageItem.add(ResourceTable.Media_bluegrass);
}
Now the binding of the data to the view is done inside the prepareExpandableListAdapter
method. Here, we will decide whether the item is a child or a group item by calling handleListItem
and checking whether it is present in mTempChildNameItem
or not. After this, we will set the adapter and OnItemClicklistener.
protected void prepareExpandableListAdapter() {
expandableListAdapter = new ExpandableListAdapter<ParentChild>(context,
mGroupNameItem, mGroupImageItem, ResourceTable.Layout_ability_listview_item) {
@Override
protected void bind(ViewHolder holder, ParentChild text, Integer image, int position) {
handleListItem(holder, text, image);
}
};
//setting the adapter
setAdapter(expandableListAdapter);
//setting the OnItemClickListener
setListener(expandableListAdapter);
}
private void setAdapter(ExpandableListAdapter<ParentChild> expandableListAdapter) {
mGroupContainer.setItemProvider(expandableListAdapter);
}
If the item is not present in mTempChildNameItem
, then it is a parent/group item, otherwise it is a child item.
private void handleListItem(ExpandableListAdapter.ViewHolder holder, ParentChild text, Integer image) {
if (!mTempChildNameItem.contains(text.getChildItem())) {
handleParentItem(holder, text, image);
} else {
handleChildItem(holder, text);
}
}
private void handleParentItem(ExpandableListAdapter.ViewHolder holder, ParentChild text, Integer image) {
// Set background for parent/Group
holder.makeInvisibleButton(ResourceTable.Id_checkbtn);
holder.makeInvisibleImage(ResourceTable.Id_childstar);
holder.setGroupItemBackground(ResourceTable.Id_groupContainer, ResourceTable.Color_white);
holder.setText(ResourceTable.Id_tvGroupTitle, text.getChildItem(), Color.GRAY,
ResUtil.getIntDimen(context, ResourceTable.Float_group_text_size));
holder.setGroupImage(ResourceTable.Id_ivGroupIcon, image,
ShapeElement.RECTANGLE, Image.ScaleMode.STRETCH, ResourceTable.Color_white);
if (!mTempGroupNameItem.contains(text.getChildItem())) {
// Set arrow down icon
holder.setGroupImage(ResourceTable.Id_ArrowIcon, ResourceTable.Media_arrow_Down,
ShapeElement.OVAL, Image.ScaleMode.CENTER, ResourceTable.Color_white);
} else {
// Set arrow up icon
holder.setGroupImage(ResourceTable.Id_ArrowIcon, ResourceTable.Media_arrow_Up,
ShapeElement.OVAL, Image.ScaleMode.CENTER, ResourceTable.Color_white);
}
}
handleChildItem
method will be different for different ability. For expand ability-
protected void handleChildItem(ExpandableListAdapter.ViewHolder holder, ParentChild text) {
// Add child items to list
holder.makeInvisibleButton(ResourceTable.Id_checkbtn);
holder.makeInvisibleImage(ResourceTable.Id_ArrowIcon);
holder.setText(ResourceTable.Id_tvGroupTitle, text.getChildItem(), Color.GRAY,
ResUtil.getIntDimen(context, ResourceTable.Float_child_text_size));
}
Inside the setOnItemClickListener
, we call the handleClickedItem
method to check if the clickedItem is a Group item (i.e. Genre) or the Child item (i.e. Artist).
private void setListener(ExpandableListAdapter<ParentChild> expandableListAdapter) {
expandableListAdapter.setOnItemClickListener((component, position) ->
handleClickedItem(expandableListAdapter, position));
}
protected void handleClickedItem(ExpandableListAdapter<ParentChild> expandableListAdapter, int position) {
ParentChild value = mGroupNameItem.get(position);
String clickedItem = value.getChildItem();
if (!mTempChildNameItem.contains(clickedItem)) {
handleClickedParentItem(expandableListAdapter, position);
} else {
handleClickedChildItem(expandableListAdapter, value);
}
}
private void handleClickedParentItem(ExpandableListAdapter<ParentChild> expandableListAdapter, int position) {
checkChild(position);
expandableListAdapter.setData(mGroupNameItem);
}
handleClickedChildItem
method will be different for different ability slice. For expand ability-
@Override
protected void handleClickedChildItem(ExpandableListAdapter<ParentChild> expandableListAdapter, ParentChild value) {
super.showToast();
}
mTempGroupNameItem
contains all the Group item that are in expand state and mTempChildNameItem
will contains child of such GroupItems.
While collapsing the group, we will remove the group items from mTempGroupNameItem
and their child items from mTempChildNameItem
.
private void checkChild(int position) {
ParentChild value = mGroupNameItem.get(position);
String clickedItem = value.getChildItem();
if (mTempGroupNameItem.contains(clickedItem)) {
int actualItemPosition = mFinalGroupNameItem.indexOf(clickedItem);
removeChildItems(actualItemPosition, position);
mTempGroupNameItem.remove(clickedItem);
} else {
int actualItemPosition = mFinalGroupNameItem.indexOf(clickedItem);
addChildItems(actualItemPosition, clickedItem, position);
mTempGroupNameItem.add(clickedItem);
}
}
If it is the Group item and it is not there in mTempGroupNameItem
, that means we have to expand it and then add it to mTempGroupNameItem
and their corresponding child items to mTempChildNameItem
.
private void addChildItems(int index, String clickedItem, int itemPositionFromGroup) {
String[] childItems = childItems().get(index);
for (String item : childItems) {
itemPositionFromGroup = itemPositionFromGroup + 1;
mGroupNameItem.add(itemPositionFromGroup, new ParentChild(clickedItem, item));
mTempChildNameItem.add(item);
mGroupImageItem.add(itemPositionFromGroup, ResourceTable.Media_star);
}
}
If the clickedItem
is already there in mTempGroupNameItem
, then we have to collapse it and then remove it from mTempGroupNameItem
and the
corresponding child items from mTempChildNameItem
.
private void removeChildItems(int position, int itemPositionFromGroup) {
String[] items = childItems().get(position);
for (String name : items) {
mGroupNameItem.remove(itemPositionFromGroup + 1);
mGroupImageItem.remove(itemPositionFromGroup + 1);
mTempChildNameItem.remove(name);
}
}
We can maintain our favorite child items, i.e. favourite Artists, by adding them to an ArrayList mFavouriteItem
.
protected void getFavouriteItems() {
mFavouriteItem.add(ResUtil.getString(context, ResourceTable.String_item_child_Rock1));
mFavouriteItem.add(ResUtil.getString(context, ResourceTable.String_item_child_Rock4));
mFavouriteItem.add(ResUtil.getString(context, ResourceTable.String_item_child_Jazz1));
mFavouriteItem.add(ResUtil.getString(context, ResourceTable.String_item_child_Jazz2));
mFavouriteItem.add(ResUtil.getString(context, ResourceTable.String_item_child_Classic2));
mFavouriteItem.add(ResUtil.getString(context, ResourceTable.String_item_child_Salsa1));
mFavouriteItem.add(ResUtil.getString(context, ResourceTable.String_item_child_Bluegrass1));
}
We are maintaining mSelectedChild, which is a HashMap, and will map the checked item to its parent item. We are updating this HashMap whenever any list item is getting clicked.
@Override
protected void handleClickedChildItem(ExpandableListAdapter<ParentChild> expandableListAdapter, ParentChild value) {
String parentGroup = value.getParentItem();
String clickedItem = value.getChildItem();
if (mSelectedChild.containsKey(parentGroup)) {
mSelectedChild.remove(parentGroup);
}
mSelectedChild.put(parentGroup, clickedItem);
expandableListAdapter.setData(mGroupNameItem);
}
To handle the case where a child item can be under multiple groups, we have created a class ParentChild
that will store both the parent and the child name for each of the list items. While rendering the view, we will check only those items whose parent child pair is present in mSelectedChild
HashMap.
if (mSelectedChild.containsKey(text.getParentItem()) && mSelectedChild.get(text.getParentItem())
.equals(text.getChildItem())) {
holder.setChecked(ResourceTable.Id_checkbtn);
} else {
holder.setUnChecked(ResourceTable.Id_checkbtn);
}
This will have a list of Group Items (i.e. Genre) and on clicking on it, the group will expand and their corresponding child items (i.e Artist) will be visible.
we also have TOGGLE CLASSIC GROUP
button that can automatically expand and collapse the Classic group item.
In this, we will have our favourite child items. While rendering the view we can add the star image in front of each of those items that are present in mFavouriteItem
.
This will allow the us to select one Artist among the other artists of a particulat Genre. It also provides us with the CLEAR SELECTIONS
button that will clear all our previous choices.
This will allow the us to select multiple Artist of a particulat Genre. It also provides us with the PROGRAMMATICALLY CHECK BOSTON
button that will automatically check the boston Artist of Rock Genre.
ExpandableRecyclerView is Copyright (c) 2016 thoughtbot, inc. It is free software, and may be redistributed under the terms specified in the LICENSE file.