ExpandableListView 可扩展列表在其高度为 wrap_content 且下方有内容时不展开
ExpandableListView expandable list not expanding when its height is wrap_content, and something is below it
这是我的布局代码。当我单击可展开列表视图时,它不会展开。但是,如果我增加列表的高度,它会扩展。有没有办法通过代码动态增加列表的高度?我可以让它在我的布局中间展开吗?因为如果我增加高度,它只会在折叠列表中添加不必要的白色 space。
代码:
<ScrollView 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">
<RelativeLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.pocketcash.pocketcash.HomeActivity">
<Switch
android:id="@+id/detailedCashInfoSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:onClick="cashToggleSwitchClicked"
android:text="Toggle Detailed Cash Info" />
<Button
android:id="@+id/addCashButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/expandableListView2"
android:layout_centerHorizontal="true"
android:onClick="launchAddCash"
android:text="Add cash" />
<Button
android:id="@+id/addExpenditureButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/addCashButton"
android:layout_centerHorizontal="true"
android:layout_marginTop="25dp"
android:onClick="launchAddExpenditure"
android:text="Add expenditure" />
<TextView
android:id="@+id/recentLogsText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/addExpenditureButton"
android:layout_marginTop="50dp"
android:text="Recent Logs:"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/logText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/recentLogsText"
android:layout_marginTop="0dp"
android:text="Small Text"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/recentViewAllText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/recentLogsText"
android:layout_marginLeft="10dp"
android:layout_toEndOf="@+id/recentLogsText"
android:layout_toRightOf="@+id/recentLogsText"
android:onClick="recentViewLogClicked"
android:text="(view all)"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/addViewAllText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/addCashButton"
android:layout_centerHorizontal="true"
android:onClick="addViewLogClicked"
android:text="(view logs)"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/expenseViewAllText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/addExpenditureButton"
android:layout_centerHorizontal="true"
android:onClick="expenseViewLogClicked"
android:text="(view logs)"
android:textAppearance="?android:attr/textAppearanceSmall" />
<ImageView
android:id="@+id/userImage"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/detailedCashInfoSwitch"
android:layout_marginTop="34dp"
android:background="@drawable/default_user" />
<TextView
android:id="@+id/nameText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/userImage"
android:layout_marginLeft="25dp"
android:layout_toEndOf="@+id/userImage"
android:layout_toRightOf="@+id/userImage"
android:text="Name"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/logoutText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/nameText"
android:layout_alignStart="@+id/nameText"
android:layout_below="@+id/nameText"
android:clickable="true"
android:onClick="logUserOut"
android:text="logout"
android:textAppearance="?android:attr/textAppearanceSmall" />
<ImageButton
android:id="@+id/settingsButton"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_alignTop="@+id/nameText"
android:layout_marginLeft="25dp"
android:layout_toEndOf="@+id/nameText"
android:layout_toRightOf="@+id/nameText"
android:background="@drawable/settingsicon"
android:onClick="launchSettingsActivity" />
<fragment
android:id="@+id/fragment_currentBalanceInfo"
android:name="com.pocketcash.pocketcash.CurrentBalanceInfoFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/userImage"
android:layout_centerHorizontal="true"
tools:layout="@layout/fragment_current_balance_info" />
<ExpandableListView
android:id="@+id/expandableListView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/fragment_currentBalanceInfo"
android:layout_centerHorizontal="true"
android:clickable="true" />
</RelativeLayout>
</ScrollView>
As Romain Guy(Google 工程师在 UI 工具包上工作)在他的 post
中说
通过将宽度设置为 wrap_content
,您可以告诉 ListView 与其子项中最宽的一样宽。因此,ListView 必须测量其项目并获取它必须在适配器上调用 getView()
的项目。这可能会发生多次,具体取决于布局遍数、父布局的行为等。
因此,如果您将 ListView
的布局宽度或布局高度设置为 wrap_content
,ListView
将尝试测量附加到它的每个视图 - 这绝对是不是你想要的。
切记:始终避免为ListViews
或GridViews
或ExpandableListView
设置wrap_content,为了更多详情看这个Google I/O video 说说listview的世界
所以让ExpandableListView
变成match_parent
在对这个主题进行了大量研究之后,我只能得出这样的结论:像可扩展列表视图这样的可滚动对象不能在另一个可滚动视图中声明。
但是,我对 java 代码进行了一些调整,以在运行时解决此问题。
expListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
@Override
public void onGroupExpand(int groupPosition) {
int height = 0;
for (int i = 0; i < expListView.getChildCount(); i++) {
height += expListView.getChildAt(i).getMeasuredHeight();
height += expListView.getDividerHeight();
}
expListView.getLayoutParams().height = (height+6)*10;
}
});
// Listview Group collapsed listener
expListView.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
@Override
public void onGroupCollapse(int groupPosition) {
expListView.getLayoutParams().height = 61;
}
});
这样我在展开或折叠时动态调整了小部件的高度。它不是一个可靠的方法,它在点击和试用时有效,所以我最终不得不将这个小部件移动到一个全新的 activity。然而,这确实完美无缺。但如果有人找到更好的答案,请分享。
我知道问题已经得到解答,但我有另一个适用于更多情况的解决方案:我通过计算项目数量来设置列表视图的初始高度,然后在 collapse/expand 通过检索 expanded/collapsed 组的子项数来确定组的数量。这是一个示例代码:
int item_size = 50; // 50px
int sub_item_size = 40; // 40px
expListView.getLayoutParams().height = item_size*myAdapter.getGroupCount();
expenseListView.setAdapter(myAdapter);
// ListView Group Expand Listener
expenseListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
@Override
public void onGroupExpand(int groupPosition) {
int nb_children = myAdapter.getChildrenCount(groupPosition);
expenseListView.getLayoutParams().height += sub_item_size*nb_children;
}
});
// Listview Group collapsed listener
expenseListView.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
@Override
public void onGroupCollapse(int groupPosition) {
int nb_children = myAdapter.getChildrenCount(groupPosition);
expenseListView.getLayoutParams().height -= sub_item_size*nb_children;
}
});
通常我们不能在Scrollview中使用ListView、GridView或ExpandableListView。如果您需要它,那么您必须为可扩展的 ListView 提供固定高度。
private void setListViewHeight(final ExpandableListView listView, final int groupPosition) {
final ExpandableListAdapter listAdapter = (ExpandableListAdapter) listView.getExpandableListAdapter();
int totalHeight = 0;
// be careful with unspecified width measure spec, it will ignore all layout params and even
// screen dimensions, so to get correct height, first get maximum width View can use and
// call measure() this way
final int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.EXACTLY);
for (int i = 0; i < listAdapter.getGroupCount(); i++) {
final View groupItem = listAdapter.getGroupView(i, false, null, listView);
groupItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
totalHeight += groupItem.getMeasuredHeight();
// count only expanded other groups or the clicked collapsed one
if (((listView.isGroupExpanded(i)) && (i != groupPosition))
|| ((!listView.isGroupExpanded(i)) && (i == groupPosition))) {
for (int j = 0, childrenNumber = listAdapter.getChildrenCount(i); j < childrenNumber; j++) {
final View listItem = listAdapter.getChildView(i, j, j == childrenNumber - 1, null, listView);
listItem.measure(desiredWidth, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
totalHeight += listItem.getMeasuredHeight();
}
}
}
final ViewGroup.LayoutParams params = listView.getLayoutParams();
final int height = totalHeight
+ (listView.getDividerHeight() * (listAdapter.getGroupCount() - 1));
params.height = height;
listView.setLayoutParams(params);
listView.requestLayout();
}
并将此方法分配给列表组点击:
expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(final ExpandableListView expandableListView, final View view, final int groupPosition, final long id) {
setListViewHeight(expandableListView, groupPosition);
return false;
}
});
重要:虽然此代码在大多数情况下应该有效,但当子列表项是带有 android:layout_height="wrap_content"
的 TextView 时我遇到了一些问题,因此它们的高度取决于它们的宽度,这可能与此问题有关:ViewGroup{TextView,...}.getMeasuredHeight gives wrong value is smaller than real height。如果您设法修复它,请发表评论。
不幸的是,似乎没有办法使用 wrap_content
作为 ExpandableListView
的高度。相反,设置固定高度和 android:nestedScrollingEnabled="true"
对我有用。
我找到了一种更好的解决方案来实现这种现象,使用以下不太复杂但效率更高的 Kotlin 代码。
before all keep some points in mind :
- Used fixed
height
Parent and Child views.
- Extracted the heights of both Parent and Child into
dimens.xml
file.
- Set
divider='@null'
, scrollbars="none"
, groupIndicator="@null"
(for customGroupIndicator, otherwise ignore) in ExpandableListView.
- Never use
paddingVertical
including paddingTop
and paddingBottom
, replace them with layoutMargins
for views having dynamic height.
Code Logic (For me this chunk is a part of a ViewModel Function named initUi(uiObject)
)
//Here `ui` is a DataBinding object.
//You can use ViewBinding or simple Android findViewById as per your comfort.
val data: List<ModelClass> = listObject
val context = ui.root.context
ui.expandableList.apply {
val mAdapter = MyExpandableListAdapter(data, context)
setAdapter(mAdapter)
//fetching parent height from dimens.xml
val parentViewHeight = ceil(
context.resources.getDimension(R.dimen.layout_menu_parent_height)
).toInt()
//fetching child height from dimens.xml
val childViewHeight = ceil(
context.resources.getDimension(R.dimen.layout_menu_child_height)
).toInt()
//Setting up the initial height of view when All parents are collapsed
layoutParams.height = data.size * parentViewHeight
setOnGroupExpandListener { groupPosition ->
//Increasing the height for Expanded SubCategory
layoutParams.height += mAdapter.getChildrenCount(groupPosition) * childViewHeight
}
setOnGroupCollapseListener { groupPosition ->
//Decreasing the height for Collapsed SubCategory
layoutParams.height -= mAdapter.getChildrenCount(groupPosition) * childViewHeight
}
}
MyExpandableListAdapter.kt
class FragmentMenuExpandableListAdapter(
val data: List<Data>, val context: Context
): BaseExpandableListAdapter() {
//Declaring inflater Once can save a lot of CPU and RAM uses...
private val inflater = LayoutInflater.from(context)
override fun getGroupCount(): Int = data.size
override fun getChildrenCount(groupPosition: Int): Int = data[groupPosition].subcategories.size
override fun getGroup(groupPosition: Int): Data = data[groupPosition]
override fun getChild(groupPosition: Int, childPosition: Int) = data[groupPosition].subcategories[childPosition]
override fun getGroupId(groupPosition: Int): Long = groupPosition.toLong()
override fun getChildId(groupPosition: Int, childPosition: Int): Long = "$groupPosition$childPosition0".toLong()
override fun hasStableIds(): Boolean = true
override fun getGroupView(
groupPosition: Int,
isExpanded: Boolean,
convertView: View?,
parent: ViewGroup
): View {
val binding = LayoutMenuParentBinding.inflate(inflater, parent, false)
binding.apply {
val temp = getGroup(groupPosition)
title.text = temp.category_name
//here I used a custom ImageView as GroupIndicator
//So following code chunk is Optional
if(temp.subcategories.isEmpty()) dropdownIcon.visibility = View.GONE
else dropdownIcon.let { icon ->
val view = parent as ExpandableListView
icon.setImageResource(
if(view.isGroupExpanded(groupPosition)) {
icon.setOnClickListener { view.collapseGroup(groupPosition) }
R.drawable.ic_state_expanded
}
else {
icon.setOnClickListener {
view.expandGroup(groupPosition)
}
R.drawable.ic_state_collapsed
}
)
}
root.setOnClickListener {
TODO("Do on Parent click")
}
}
return binding.root
}
override fun getChildView(
groupPosition: Int,
childPosition: Int,
isLastChild: Boolean,
convertView: View?,
parent: ViewGroup?
): View {
val binding = LayoutMenuChildBinding.inflate(
inflater, parent, false
).apply {
val temp = getChild(groupPosition, childPosition)
title.text = temp.categoryName
root.setOnClickListener {
TODO("Do on Child click")
}
}
return binding.root
}
override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean = true
}
这是我的布局代码。当我单击可展开列表视图时,它不会展开。但是,如果我增加列表的高度,它会扩展。有没有办法通过代码动态增加列表的高度?我可以让它在我的布局中间展开吗?因为如果我增加高度,它只会在折叠列表中添加不必要的白色 space。
代码:
<ScrollView 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">
<RelativeLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.pocketcash.pocketcash.HomeActivity">
<Switch
android:id="@+id/detailedCashInfoSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:onClick="cashToggleSwitchClicked"
android:text="Toggle Detailed Cash Info" />
<Button
android:id="@+id/addCashButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/expandableListView2"
android:layout_centerHorizontal="true"
android:onClick="launchAddCash"
android:text="Add cash" />
<Button
android:id="@+id/addExpenditureButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/addCashButton"
android:layout_centerHorizontal="true"
android:layout_marginTop="25dp"
android:onClick="launchAddExpenditure"
android:text="Add expenditure" />
<TextView
android:id="@+id/recentLogsText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/addExpenditureButton"
android:layout_marginTop="50dp"
android:text="Recent Logs:"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/logText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/recentLogsText"
android:layout_marginTop="0dp"
android:text="Small Text"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/recentViewAllText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/recentLogsText"
android:layout_marginLeft="10dp"
android:layout_toEndOf="@+id/recentLogsText"
android:layout_toRightOf="@+id/recentLogsText"
android:onClick="recentViewLogClicked"
android:text="(view all)"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/addViewAllText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/addCashButton"
android:layout_centerHorizontal="true"
android:onClick="addViewLogClicked"
android:text="(view logs)"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/expenseViewAllText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/addExpenditureButton"
android:layout_centerHorizontal="true"
android:onClick="expenseViewLogClicked"
android:text="(view logs)"
android:textAppearance="?android:attr/textAppearanceSmall" />
<ImageView
android:id="@+id/userImage"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/detailedCashInfoSwitch"
android:layout_marginTop="34dp"
android:background="@drawable/default_user" />
<TextView
android:id="@+id/nameText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/userImage"
android:layout_marginLeft="25dp"
android:layout_toEndOf="@+id/userImage"
android:layout_toRightOf="@+id/userImage"
android:text="Name"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/logoutText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/nameText"
android:layout_alignStart="@+id/nameText"
android:layout_below="@+id/nameText"
android:clickable="true"
android:onClick="logUserOut"
android:text="logout"
android:textAppearance="?android:attr/textAppearanceSmall" />
<ImageButton
android:id="@+id/settingsButton"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_alignTop="@+id/nameText"
android:layout_marginLeft="25dp"
android:layout_toEndOf="@+id/nameText"
android:layout_toRightOf="@+id/nameText"
android:background="@drawable/settingsicon"
android:onClick="launchSettingsActivity" />
<fragment
android:id="@+id/fragment_currentBalanceInfo"
android:name="com.pocketcash.pocketcash.CurrentBalanceInfoFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/userImage"
android:layout_centerHorizontal="true"
tools:layout="@layout/fragment_current_balance_info" />
<ExpandableListView
android:id="@+id/expandableListView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/fragment_currentBalanceInfo"
android:layout_centerHorizontal="true"
android:clickable="true" />
</RelativeLayout>
</ScrollView>
As Romain Guy(Google 工程师在 UI 工具包上工作)在他的 post
中说通过将宽度设置为 wrap_content
,您可以告诉 ListView 与其子项中最宽的一样宽。因此,ListView 必须测量其项目并获取它必须在适配器上调用 getView()
的项目。这可能会发生多次,具体取决于布局遍数、父布局的行为等。
因此,如果您将 ListView
的布局宽度或布局高度设置为 wrap_content
,ListView
将尝试测量附加到它的每个视图 - 这绝对是不是你想要的。
切记:始终避免为ListViews
或GridViews
或ExpandableListView
设置wrap_content,为了更多详情看这个Google I/O video 说说listview的世界
所以让ExpandableListView
变成match_parent
在对这个主题进行了大量研究之后,我只能得出这样的结论:像可扩展列表视图这样的可滚动对象不能在另一个可滚动视图中声明。 但是,我对 java 代码进行了一些调整,以在运行时解决此问题。
expListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
@Override
public void onGroupExpand(int groupPosition) {
int height = 0;
for (int i = 0; i < expListView.getChildCount(); i++) {
height += expListView.getChildAt(i).getMeasuredHeight();
height += expListView.getDividerHeight();
}
expListView.getLayoutParams().height = (height+6)*10;
}
});
// Listview Group collapsed listener
expListView.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
@Override
public void onGroupCollapse(int groupPosition) {
expListView.getLayoutParams().height = 61;
}
});
这样我在展开或折叠时动态调整了小部件的高度。它不是一个可靠的方法,它在点击和试用时有效,所以我最终不得不将这个小部件移动到一个全新的 activity。然而,这确实完美无缺。但如果有人找到更好的答案,请分享。
我知道问题已经得到解答,但我有另一个适用于更多情况的解决方案:我通过计算项目数量来设置列表视图的初始高度,然后在 collapse/expand 通过检索 expanded/collapsed 组的子项数来确定组的数量。这是一个示例代码:
int item_size = 50; // 50px
int sub_item_size = 40; // 40px
expListView.getLayoutParams().height = item_size*myAdapter.getGroupCount();
expenseListView.setAdapter(myAdapter);
// ListView Group Expand Listener
expenseListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
@Override
public void onGroupExpand(int groupPosition) {
int nb_children = myAdapter.getChildrenCount(groupPosition);
expenseListView.getLayoutParams().height += sub_item_size*nb_children;
}
});
// Listview Group collapsed listener
expenseListView.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
@Override
public void onGroupCollapse(int groupPosition) {
int nb_children = myAdapter.getChildrenCount(groupPosition);
expenseListView.getLayoutParams().height -= sub_item_size*nb_children;
}
});
通常我们不能在Scrollview中使用ListView、GridView或ExpandableListView。如果您需要它,那么您必须为可扩展的 ListView 提供固定高度。
private void setListViewHeight(final ExpandableListView listView, final int groupPosition) {
final ExpandableListAdapter listAdapter = (ExpandableListAdapter) listView.getExpandableListAdapter();
int totalHeight = 0;
// be careful with unspecified width measure spec, it will ignore all layout params and even
// screen dimensions, so to get correct height, first get maximum width View can use and
// call measure() this way
final int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.EXACTLY);
for (int i = 0; i < listAdapter.getGroupCount(); i++) {
final View groupItem = listAdapter.getGroupView(i, false, null, listView);
groupItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
totalHeight += groupItem.getMeasuredHeight();
// count only expanded other groups or the clicked collapsed one
if (((listView.isGroupExpanded(i)) && (i != groupPosition))
|| ((!listView.isGroupExpanded(i)) && (i == groupPosition))) {
for (int j = 0, childrenNumber = listAdapter.getChildrenCount(i); j < childrenNumber; j++) {
final View listItem = listAdapter.getChildView(i, j, j == childrenNumber - 1, null, listView);
listItem.measure(desiredWidth, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
totalHeight += listItem.getMeasuredHeight();
}
}
}
final ViewGroup.LayoutParams params = listView.getLayoutParams();
final int height = totalHeight
+ (listView.getDividerHeight() * (listAdapter.getGroupCount() - 1));
params.height = height;
listView.setLayoutParams(params);
listView.requestLayout();
}
并将此方法分配给列表组点击:
expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(final ExpandableListView expandableListView, final View view, final int groupPosition, final long id) {
setListViewHeight(expandableListView, groupPosition);
return false;
}
});
重要:虽然此代码在大多数情况下应该有效,但当子列表项是带有 android:layout_height="wrap_content"
的 TextView 时我遇到了一些问题,因此它们的高度取决于它们的宽度,这可能与此问题有关:ViewGroup{TextView,...}.getMeasuredHeight gives wrong value is smaller than real height。如果您设法修复它,请发表评论。
不幸的是,似乎没有办法使用 wrap_content
作为 ExpandableListView
的高度。相反,设置固定高度和 android:nestedScrollingEnabled="true"
对我有用。
我找到了一种更好的解决方案来实现这种现象,使用以下不太复杂但效率更高的 Kotlin 代码。
before all keep some points in mind :
- Used fixed
height
Parent and Child views.- Extracted the heights of both Parent and Child into
dimens.xml
file.- Set
divider='@null'
,scrollbars="none"
,groupIndicator="@null"
(for customGroupIndicator, otherwise ignore) in ExpandableListView.- Never use
paddingVertical
includingpaddingTop
andpaddingBottom
, replace them withlayoutMargins
for views having dynamic height.
Code Logic (For me this chunk is a part of a ViewModel Function named
initUi(uiObject)
)
//Here `ui` is a DataBinding object.
//You can use ViewBinding or simple Android findViewById as per your comfort.
val data: List<ModelClass> = listObject
val context = ui.root.context
ui.expandableList.apply {
val mAdapter = MyExpandableListAdapter(data, context)
setAdapter(mAdapter)
//fetching parent height from dimens.xml
val parentViewHeight = ceil(
context.resources.getDimension(R.dimen.layout_menu_parent_height)
).toInt()
//fetching child height from dimens.xml
val childViewHeight = ceil(
context.resources.getDimension(R.dimen.layout_menu_child_height)
).toInt()
//Setting up the initial height of view when All parents are collapsed
layoutParams.height = data.size * parentViewHeight
setOnGroupExpandListener { groupPosition ->
//Increasing the height for Expanded SubCategory
layoutParams.height += mAdapter.getChildrenCount(groupPosition) * childViewHeight
}
setOnGroupCollapseListener { groupPosition ->
//Decreasing the height for Collapsed SubCategory
layoutParams.height -= mAdapter.getChildrenCount(groupPosition) * childViewHeight
}
}
MyExpandableListAdapter.kt
class FragmentMenuExpandableListAdapter(
val data: List<Data>, val context: Context
): BaseExpandableListAdapter() {
//Declaring inflater Once can save a lot of CPU and RAM uses...
private val inflater = LayoutInflater.from(context)
override fun getGroupCount(): Int = data.size
override fun getChildrenCount(groupPosition: Int): Int = data[groupPosition].subcategories.size
override fun getGroup(groupPosition: Int): Data = data[groupPosition]
override fun getChild(groupPosition: Int, childPosition: Int) = data[groupPosition].subcategories[childPosition]
override fun getGroupId(groupPosition: Int): Long = groupPosition.toLong()
override fun getChildId(groupPosition: Int, childPosition: Int): Long = "$groupPosition$childPosition0".toLong()
override fun hasStableIds(): Boolean = true
override fun getGroupView(
groupPosition: Int,
isExpanded: Boolean,
convertView: View?,
parent: ViewGroup
): View {
val binding = LayoutMenuParentBinding.inflate(inflater, parent, false)
binding.apply {
val temp = getGroup(groupPosition)
title.text = temp.category_name
//here I used a custom ImageView as GroupIndicator
//So following code chunk is Optional
if(temp.subcategories.isEmpty()) dropdownIcon.visibility = View.GONE
else dropdownIcon.let { icon ->
val view = parent as ExpandableListView
icon.setImageResource(
if(view.isGroupExpanded(groupPosition)) {
icon.setOnClickListener { view.collapseGroup(groupPosition) }
R.drawable.ic_state_expanded
}
else {
icon.setOnClickListener {
view.expandGroup(groupPosition)
}
R.drawable.ic_state_collapsed
}
)
}
root.setOnClickListener {
TODO("Do on Parent click")
}
}
return binding.root
}
override fun getChildView(
groupPosition: Int,
childPosition: Int,
isLastChild: Boolean,
convertView: View?,
parent: ViewGroup?
): View {
val binding = LayoutMenuChildBinding.inflate(
inflater, parent, false
).apply {
val temp = getChild(groupPosition, childPosition)
title.text = temp.categoryName
root.setOnClickListener {
TODO("Do on Child click")
}
}
return binding.root
}
override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean = true
}