Sunday, October 4, 2020

Android - Expandable Listview inside scrollview

In Android application, if you want to show items in a list with expandable feature. The ExpandableListView is a option that show items in a vertically scrolling two level list. It is different from the listview by allowing two level groups which can individually be expanded to show its children.

In this post, we going to share a simple solution to increase and decrease height of ExpandableListView at run time when ExpandableListView expand or collapse. Sometime, it's required to increase height of ExpandableListView because you cannot use the wrap_content value for the height attribute of a ExpandableListView in XML if the parent’s size is not strictly specified. In other words, if the parent is the ScrollView then you could not specify wrap_content since it can be of any length.

Let's try to understand the problem.

Android - Expandable Listview inside scrollview

As you can see above, all three bottom button(Save As, Save in Progress, Exit) not visible when we expend all group of ExpandableListView. Even we put all views inside of scroll view.

Here, we have resolved output:

Android - Expandable Listview inside scrollview



Now you can see, when we scrolling the screen item bottom to up. It's showing all three button in the bottom of screen. We resolved with the help of following method.

 private fun setListViewHeight(listView: ExpandableListView, group: Int) {
val listAdapter = listView.expandableListAdapter as ExpandableListAdapter
var totalHeight = 0
val desiredWidth: Int = View.MeasureSpec.makeMeasureSpec(
listView.width,
View.MeasureSpec.EXACTLY
)
for (i in 0 until listAdapter.groupCount) {
val groupItem: View = listAdapter.getGroupView(i, false, null, listView)
groupItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED)
totalHeight += groupItem.getMeasuredHeight()
if (listView.isGroupExpanded(i) && i != group
|| !listView.isGroupExpanded(i) && i == group
) {
for (j in 0 until listAdapter.getChildrenCount(i)) {
val listItem: View = listAdapter.getChildView(
i, j, false, null,
listView
)
listItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED)
totalHeight += listItem.getMeasuredHeight()
}
}
}
val params = listView.layoutParams
var height = (totalHeight
+ listView.dividerHeight * (listAdapter.groupCount - 1))
if (height < 10) height = 200
params.height = height
listView.layoutParams = params
listView.requestLayout()
}

Now we going to screen a small Android application to understand it with a example.

 

Creating a new project

1. Create an Android Application and name it ExpandableListView. We using following array list that declared in the string file of the project.

<resources>
<string name="app_name">ExpendtableListView</string>

<string name="text_alcohol">Alcohol</string>
<string name="text_coffee">Coffee</string>
<string name="text_pasta">Pasta</string>
<string name="text_cold_drinks">Cold Drinks</string>
<string name="text_expanded">Expanded</string>
<string name="text_collapsed">Collapsed</string>

<string-array name="string_array_alcohol">
<item>Vodka</item>
<item>Rum</item>
<item>Whiskey</item>
<item>Gin</item>
<item>Tequila</item>
<item>Tequila</item>
<item>Tequila</item>
</string-array>
<string-array name="string_array_coffee">
<item>Espresso</item>
<item>Macchiato</item>
<item>Cappuccino</item>
<item>Cafe Latte</item>
<item>Ristretto</item>
<item>Lasagnette</item>
<item>Lasagnette</item>
</string-array>

</resources>

2. After that, update activity_main.xml file and add ExpandableListView as we have designed.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<ScrollView
android:id="@+id/activity_expandable_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">

<LinearLayout
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">


<ExpandableListView
android:id="@+id/activity_expandable_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

<Button
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Save As" />
<Button
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Save In Progress" />
<Button
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Exit" />

</LinearLayout>
</ScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>



3. Now create layout for child and group of
ExpandableListView adapter items.

<?xml version="1.0" encoding="utf-8"?>                                                   list_row_child.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/white"
android:padding="16dp">

<TextView
android:id="@+id/textViewChild"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:paddingLeft="?android:attr/expandableListPreferredChildPaddingLeft"
android:textSize="17dp" />

</LinearLayout>

layout for group:

 <?xml version="1.0" encoding="utf-8"?>                                                   list_row_group.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#778899"
android:padding="8dp">


<TextView
android:id="@+id/textViewGroup"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:textColor="#FFFFFF"
/>

</LinearLayout>

4. Here, we have adapter of ExpandableListView that display child and header of list.

package com.sun.expendtablelistview;

import android.content.Context;
import android.graphics.Typeface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;

import java.util.HashMap;
import java.util.List;

public class ExpandableListViewAdapter extends BaseExpandableListAdapter {

private Context context;
// group titles
private List<String> listDataGroup;
// child data
private HashMap<String, List<String>> listDataChild;
public ExpandableListViewAdapter(Context context, List<String> listDataGroup,
HashMap<String, List<String>> listChildData) {
this.context = context;
this.listDataGroup = listDataGroup;
this.listDataChild = listChildData;
}
@Override
public Object getChild(int groupPosition, int childPosititon) {
return this.listDataChild.get(this.listDataGroup.get(groupPosition))
.get(childPosititon);
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
final String childText = (String) getChild(groupPosition, childPosition);
if (convertView == null) {
LayoutInflater layoutInflater = (LayoutInflater) this.context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = layoutInflater.inflate(R.layout.list_row_child, null);
}
TextView textViewChild = convertView
.findViewById(R.id.textViewChild);
textViewChild.setText(childText);
return convertView;
}
@Override
public int getChildrenCount(int groupPosition) {
return this.listDataChild.get(this.listDataGroup.get(groupPosition))
.size();
}
@Override
public Object getGroup(int groupPosition) {
return this.listDataGroup.get(groupPosition);
}
@Override
public int getGroupCount() {
return this.listDataGroup.size();
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
String headerTitle = (String) getGroup(groupPosition);
if (convertView == null) {
LayoutInflater layoutInflater = (LayoutInflater) this.context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = layoutInflater.inflate(R.layout.list_row_group, null);
}
TextView textViewGroup = convertView
.findViewById(R.id.textViewGroup);
textViewGroup.setTypeface(null, Typeface.BOLD);
textViewGroup.setText(headerTitle);
return convertView;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}



5. Here, we have main business logic of this post. In this, we merging all the required resource to see the desire output.

 package com.sun.expendtablelistview

import android.os.Bundle
import android.view.View
import android.widget.ExpandableListAdapter
import android.widget.ExpandableListView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

private var expandableListView: ExpandableListView? = null
private var expandableListViewAdapter: ExpandableListViewAdapter? = null
private lateinit var listDataGroup: ArrayList<String>
private lateinit var listDataChild: HashMap<String, List<String>>


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
expandableListView = activity_expandable_list_view
// initializing the listeners
expandableListView!!.setOnGroupClickListener { parent, v, groupPosition, id ->
setListViewHeight(parent, groupPosition)
false
}
// initializing the objects
initObjects()
// preparing list data
initListData()
}


/**
* method to initialize the objects
*/
private fun initObjects() {
// initializing the list of groups
listDataGroup = ArrayList()
// initializing the list of child
listDataChild = HashMap()
// initializing the adapter object
expandableListViewAdapter = ExpandableListViewAdapter(this, listDataGroup, listDataChild)
// setting list adapter
expandableListView!!.setAdapter(expandableListViewAdapter)
}


/*
* Preparing the list data
*
* Dummy Items
*/
private fun initListData() {
// Adding group data
listDataGroup.add(getString(R.string.text_alcohol))
listDataGroup.add(getString(R.string.text_coffee))
// array of strings
// list of alcohol
val alcoholList: MutableList<String> = ArrayList()
var array: Array<String> = resources.getStringArray(R.array.string_array_alcohol)
for (item in array) {
alcoholList.add(item)
}
// list of coffee
val coffeeList: MutableList<String> = ArrayList()
array = resources.getStringArray(R.array.string_array_coffee)
for (item in array) {
coffeeList.add(item)
}

// Adding child data
listDataChild[listDataGroup[0]] = alcoholList
listDataChild[listDataGroup[1]] = coffeeList

// notify the adapter
expandableListViewAdapter!!.notifyDataSetChanged()
}


private fun setListViewHeight(
listView: ExpandableListView,
group: Int
) {
val listAdapter = listView.expandableListAdapter as ExpandableListAdapter
var totalHeight = 0
val desiredWidth: Int = View.MeasureSpec.makeMeasureSpec(
listView.width,
View.MeasureSpec.EXACTLY
)
for (i in 0 until listAdapter.groupCount) {
val groupItem: View = listAdapter.getGroupView(i, false, null, listView)
groupItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED)
totalHeight += groupItem.measuredHeight
if (listView.isGroupExpanded(i) && i != group
|| !listView.isGroupExpanded(i) && i == group
) {
for (j in 0 until listAdapter.getChildrenCount(i)) {
val listItem: View = listAdapter.getChildView(
i, j, false, null,
listView
)
listItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED)
totalHeight += listItem.measuredHeight
}
}
}
val params = listView.layoutParams
var height = (totalHeight
+ listView.dividerHeight * (listAdapter.groupCount - 1))
if (height < 10) height = 200
params.height = height
listView.layoutParams = params
listView.requestLayout()
}

}

 

If you have followed the above post carefully. You can see all bottom buttons visible when you scolling from bottomm to up even you have expend all group of ExpandableListView. But if you are facing any problem to build or run it and you have any quires, feel free to ask it from comment section below.

 

Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...