使 RecyclerView 中的最后一个 Item / ViewHolder 填充屏幕的其余部分或具有最小高度
Make last Item / ViewHolder in RecyclerView fill rest of the screen or have a min height
我正在为 RecyclerView 苦苦挣扎。我使用回收器视图来显示我的模型的详细信息 class。
//My model class
MyModel {
String name;
Double latitude;
Double longitude;
Boolean isOnline;
...
}
由于某些值可能不存在,我将 RecyclerView 与自定义视图类型(一个代表我的模型的每个值)一起使用。
//Inside my custom adapter
public void setModel(T model) {
//Reset values
itemCount = 0;
deviceOfflineViewPosition = -1;
mapViewPosition = -1;
//If device is offline, add device offline item
if (device.isOnline() == null || !device.isOnline()) {
deviceOfflineViewPosition = itemCount;
itemCount++;
}
//Add additional items if necessary
...
//Always add the map as the last item
mapViewPosition = itemCount;
itemCount++;
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
if (position == deviceOfflineViewPosition) {
return ITEM_VIEW_TYPE_OFFLINE;
} else if (position == mapViewPosition) {
return ITEM_VIEW_TYPE_MAP;
} else if (...) {
//Check for other view types
}
}
使用 RecyclerView,我可以在运行时轻松确定哪些值可用,并将相应的项目添加到 RecyclerView 数据源。我简化了代码,但我的模型有更多的值,我有更多的视图类型。
RecyclerView 中的最后一项始终是地图,并且始终存在。即使我的模型中没有任何价值,至少也会有一项,即地图。
问题: 我怎样才能让 RecyclerView 中的最后一项填满屏幕上剩余的 space 并且还有一个最小高度。大小应为较大的值:剩余的 space 或最小高度。例如:
- 模型有一些值,总计占 600dp 屏幕的 100dp -> 地图高度应为 500dp
- 模型有很多值,总共占用 600dp 屏幕的 500dp -> 地图高度应为最小值 200dp
- 模型没有值 -> 地图填满整个屏幕
您可以在布局最后一项后在 RecyclerView 中找到剩余的 space,并将剩余的 space 添加到最后一项的 minHeight
。
val isLastItem = getItemCount() - 1 == position
if (isLastItem) {
val lastItemView = holder.itemView
lastItemView.doOnLayout {
val recyclerViewHeight = recyclerView.height
val lastItemBottom = lastItemView.bottom
val heightDifference = recyclerViewHeight - lastItemBottom
if (heightDifference > 0) {
lastItemView.minimumHeight = lastItemView.height + heightDifference
}
}
}
在 onBindHolder
中使用 getItemCount() - 1 == position
检查该项目是否是最后一项。如果它是最后一个项目,通过用 lastItem 底部减去 recyclerView 高度来找到高度差(getBottom()
给出视图相对于它的父级的最底部像素。在这种情况下,我们的父级是 RecyclerView
).
如果差异大于0,则将其添加到最后一个视图的当前高度并将其设置为minHeight
。我们将其设置为 minHeight
而不是直接设置为 height
以支持最后一个视图的动态内容更改。
注:此代码为Kotlin,doOnLayout函数来自Android KTx。此外,您的 RecyclerView
身高应为 match_parent
才能正常工作(我想这很明显)。
我使用 muthuraj 解决方案解决了类似的问题,我希望在前一个项目填满页面高度或更高高度的情况下,最后一个项目正常显示在最后一个项目,以防前一个项目未填满提高高度,我希望最后一项显示在页面底部。
当上一个项目+特殊项目占据所有位置时。
--------------
项目
项目
项目
特殊物品
--------------
当上一个项目+特殊项目超过高度时
--------------
项目
项目
项目
项目
项目
项目
特殊物品
--------------
当上一个项目+特殊项目占用小于高度时
--------------
特殊物品
--------------
或者
--------------
项目
特殊物品
--------------
要存档,我使用此代码
val lastItemView = holder.itemView
//TODO use a better option instead of waiting for 200ms
Handler().postDelayed({
val lastItemTop = lastItemView.top
val remainingSpace = recyclerViewHeight() - lastItemTop
val heightToSet = Math.max(remainingSpace, minHeight)
if (lastItemView.height != heightToSet) {
val layoutParams = lastItemView.layoutParams
layoutParams.height = heightToSet
lastItemView.layoutParams = layoutParams
}
}, 200)
我使用 Handler().postDelayed 的原因是 doOnLayout 永远不会为我调用,我无法弄清楚为什么会这样 运行 延迟 200 毫秒的代码,直到我找到更好的方法.
您可以扩展 LinearLayoutManager
以自己布置最后一项。
这是一个 FooterLinearLayoutManager
,它将列表的最后一项移动到屏幕底部(如果它还不存在)。通过覆盖 layoutDecoratedWithMargins
,LinearLayouyManager
告诉我们项目 应该 去哪里,但我们可以将其与父身高相匹配。
注意: 这不会 "resize" 视图,因此花哨的背景或类似的可能不起作用,它只会将最后一项移动到底部屏幕。
/**
* Moves the last list item to the bottom of the screen.
*/
class FooterLinearLayoutManager(context: Context) : LinearLayoutManager(context) {
override fun layoutDecoratedWithMargins(child: View, left: Int, top: Int, right: Int, bottom: Int) {
val lp = child.layoutParams as RecyclerView.LayoutParams
if (lp.viewAdapterPosition < itemCount - 1)
return super.layoutDecoratedWithMargins(child, left, top, right, bottom)
val parentBottom = height - paddingBottom
return if (bottom < parentBottom) {
val offset = parentBottom - bottom
super.layoutDecoratedWithMargins(child, left, top + offset, right, bottom + offset)
} else {
super.layoutDecoratedWithMargins(child, left, top, right, bottom)
}
}
}
这对我有用,让回收器查看 layout_height="0dp",并将顶部约束放在上面相应项目的底部,将底部约束放在父项上,然后它将与剩余的 space:
一起调整大小
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ToggleButton
android:id="@+id/toggleUpcomingButton"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="top"
android:text="@string/upcoming"
app:layout_constraintEnd_toStartOf="@+id/togglePupularButton"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ToggleButton
android:id="@+id/togglePupularButton"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="top"
android:text="@string/popular"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/toggleUpcomingButton"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/popular_movies"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toggleUpcomingButton" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
我正在为 RecyclerView 苦苦挣扎。我使用回收器视图来显示我的模型的详细信息 class。
//My model class
MyModel {
String name;
Double latitude;
Double longitude;
Boolean isOnline;
...
}
由于某些值可能不存在,我将 RecyclerView 与自定义视图类型(一个代表我的模型的每个值)一起使用。
//Inside my custom adapter
public void setModel(T model) {
//Reset values
itemCount = 0;
deviceOfflineViewPosition = -1;
mapViewPosition = -1;
//If device is offline, add device offline item
if (device.isOnline() == null || !device.isOnline()) {
deviceOfflineViewPosition = itemCount;
itemCount++;
}
//Add additional items if necessary
...
//Always add the map as the last item
mapViewPosition = itemCount;
itemCount++;
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
if (position == deviceOfflineViewPosition) {
return ITEM_VIEW_TYPE_OFFLINE;
} else if (position == mapViewPosition) {
return ITEM_VIEW_TYPE_MAP;
} else if (...) {
//Check for other view types
}
}
使用 RecyclerView,我可以在运行时轻松确定哪些值可用,并将相应的项目添加到 RecyclerView 数据源。我简化了代码,但我的模型有更多的值,我有更多的视图类型。
RecyclerView 中的最后一项始终是地图,并且始终存在。即使我的模型中没有任何价值,至少也会有一项,即地图。
问题: 我怎样才能让 RecyclerView 中的最后一项填满屏幕上剩余的 space 并且还有一个最小高度。大小应为较大的值:剩余的 space 或最小高度。例如:
- 模型有一些值,总计占 600dp 屏幕的 100dp -> 地图高度应为 500dp
- 模型有很多值,总共占用 600dp 屏幕的 500dp -> 地图高度应为最小值 200dp
- 模型没有值 -> 地图填满整个屏幕
您可以在布局最后一项后在 RecyclerView 中找到剩余的 space,并将剩余的 space 添加到最后一项的 minHeight
。
val isLastItem = getItemCount() - 1 == position
if (isLastItem) {
val lastItemView = holder.itemView
lastItemView.doOnLayout {
val recyclerViewHeight = recyclerView.height
val lastItemBottom = lastItemView.bottom
val heightDifference = recyclerViewHeight - lastItemBottom
if (heightDifference > 0) {
lastItemView.minimumHeight = lastItemView.height + heightDifference
}
}
}
在 onBindHolder
中使用 getItemCount() - 1 == position
检查该项目是否是最后一项。如果它是最后一个项目,通过用 lastItem 底部减去 recyclerView 高度来找到高度差(getBottom()
给出视图相对于它的父级的最底部像素。在这种情况下,我们的父级是 RecyclerView
).
如果差异大于0,则将其添加到最后一个视图的当前高度并将其设置为minHeight
。我们将其设置为 minHeight
而不是直接设置为 height
以支持最后一个视图的动态内容更改。
注:此代码为Kotlin,doOnLayout函数来自Android KTx。此外,您的 RecyclerView
身高应为 match_parent
才能正常工作(我想这很明显)。
我使用 muthuraj 解决方案解决了类似的问题,我希望在前一个项目填满页面高度或更高高度的情况下,最后一个项目正常显示在最后一个项目,以防前一个项目未填满提高高度,我希望最后一项显示在页面底部。
当上一个项目+特殊项目占据所有位置时。
--------------
项目
项目
项目
特殊物品
--------------
当上一个项目+特殊项目超过高度时
--------------
项目
项目
项目
项目
项目
项目
特殊物品
--------------
当上一个项目+特殊项目占用小于高度时
--------------
特殊物品
--------------
或者
--------------
项目
特殊物品
--------------
要存档,我使用此代码
val lastItemView = holder.itemView
//TODO use a better option instead of waiting for 200ms
Handler().postDelayed({
val lastItemTop = lastItemView.top
val remainingSpace = recyclerViewHeight() - lastItemTop
val heightToSet = Math.max(remainingSpace, minHeight)
if (lastItemView.height != heightToSet) {
val layoutParams = lastItemView.layoutParams
layoutParams.height = heightToSet
lastItemView.layoutParams = layoutParams
}
}, 200)
我使用 Handler().postDelayed 的原因是 doOnLayout 永远不会为我调用,我无法弄清楚为什么会这样 运行 延迟 200 毫秒的代码,直到我找到更好的方法.
您可以扩展 LinearLayoutManager
以自己布置最后一项。
这是一个 FooterLinearLayoutManager
,它将列表的最后一项移动到屏幕底部(如果它还不存在)。通过覆盖 layoutDecoratedWithMargins
,LinearLayouyManager
告诉我们项目 应该 去哪里,但我们可以将其与父身高相匹配。
注意: 这不会 "resize" 视图,因此花哨的背景或类似的可能不起作用,它只会将最后一项移动到底部屏幕。
/**
* Moves the last list item to the bottom of the screen.
*/
class FooterLinearLayoutManager(context: Context) : LinearLayoutManager(context) {
override fun layoutDecoratedWithMargins(child: View, left: Int, top: Int, right: Int, bottom: Int) {
val lp = child.layoutParams as RecyclerView.LayoutParams
if (lp.viewAdapterPosition < itemCount - 1)
return super.layoutDecoratedWithMargins(child, left, top, right, bottom)
val parentBottom = height - paddingBottom
return if (bottom < parentBottom) {
val offset = parentBottom - bottom
super.layoutDecoratedWithMargins(child, left, top + offset, right, bottom + offset)
} else {
super.layoutDecoratedWithMargins(child, left, top, right, bottom)
}
}
}
这对我有用,让回收器查看 layout_height="0dp",并将顶部约束放在上面相应项目的底部,将底部约束放在父项上,然后它将与剩余的 space:
一起调整大小<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ToggleButton
android:id="@+id/toggleUpcomingButton"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="top"
android:text="@string/upcoming"
app:layout_constraintEnd_toStartOf="@+id/togglePupularButton"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ToggleButton
android:id="@+id/togglePupularButton"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="top"
android:text="@string/popular"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/toggleUpcomingButton"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/popular_movies"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toggleUpcomingButton" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_title" />
</androidx.constraintlayout.widget.ConstraintLayout>