使 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 或最小高度。例如:

您可以在布局最后一项后在 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,它将列表的最后一项移动到屏幕底部(如果它还不存在)。通过覆盖 layoutDecoratedWithMarginsLinearLayouyManager 告诉我们项目 应该 去哪里,但我们可以将其与父身高相匹配。

注意: 这不会 "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>