【问题标题】:Make last Item / ViewHolder in RecyclerView fill rest of the screen or have a min height使 RecyclerView 中的最后一个 Item / ViewHolder 填充屏幕的其余部分或具有最小高度
【发布时间】:2016-12-21 01:30:36
【问题描述】:

我正在为 RecyclerView 苦苦挣扎。我使用回收站视图来显示我的模型类的详细信息。

//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 中的最后一项填满屏幕上的剩余空间并保持最小高度。大小应为较大的值:剩余空间或最小高度。例如:

  • 模型有几个值,总共占用 600dp 屏幕的 100dp -> 地图高度应为 500dp
  • 模型有很多值,总共占600dp屏幕的500dp -> 地图高度应该是200dp的最小值
  • 模型没有值 -> 地图填满整个屏幕

【问题讨论】:

  • “剩余空间”的概念在像 RecyclerView 这样的滚动视图的上下文中没有多大意义。但是,您可以使用 View.setMinimumHeight() 设置最小高度,并使用 View.setLayoutParams() 将高度设置为匹配父级。这必须在绑定过程中完成,具体取决于您拥有的物品数量。
  • 确实如此,这就是为什么我也有我的 minHeight。看,如果 recyclerview 的内容占的空间比屏幕少,我想拉伸地图以覆盖空白空间。如果屏幕上无法容纳的内容过多,显然没有剩余的内容可以填充,因此应该使用 minHeight

标签: android google-maps android-recyclerview adapter android-adapter


【解决方案1】:

您可以在布局最后一项后在 RecyclerView 中找到剩余空间,并将剩余空间添加到最后一项的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 才能正常工作(我想这很明显)。

【讨论】:

  • 所以也许我遗漏了一些东西,但对我来说,设置最小高度不会导致单元格重绘。因为它首先被渲染以便它可以获得适当的值来计算,所以当它实际设置最小高度时,它已经绘制了一次。有没有办法可以强制它重新绘制?
  • @Kyle 这很奇怪。如果您查看 View.java 中的 setMinimumHeight 源代码,它实际上调用了requestLayout(),这将使布局无效并且布局传递将再次完成。也许您可以尝试在设置高度后手动调用requestLayout() lastItemView.post{ requestLayout() } 。如果这不起作用,请尝试也致电invalidate()requestLayout 将强制视图重新测量并放置。 invalidate 将强制重绘其内容。
  • doOnLayout 对我不起作用。我改用post
  • 正如@SoroushLotfi 提到的,在我的情况下使用post 但当项目在屏幕外时bottom 为零并且计算错误,因此项目采用parent 高度作为它的最小高度。我解决了它检查heightDifference > 0 && heightDifference < recyclerViewHeight
【解决方案2】:

您可以扩展LinearLayoutManager 以自己布局最后一项。

这是一个FooterLinearLayoutManager,它将列表的最后一项移动到屏幕底部(如果它不存在的话)。通过覆盖layoutDecoratedWithMargins LinearLayouyManager 调用我们与项目应该去哪里,但我们可以将其与父高度匹配。

注意:这不会“调整”视图的大小,因此花哨的背景或类似的可能不起作用,它只会将最后一项移动到屏幕底部。

/**
 * 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)
        }
    }
}

【讨论】:

    【解决方案3】:

    我使用 muthuraj 解决方案来解决类似的问题,如果前面的项目填满页面的高度或更高的高度,我希望最后一个项目通常显示在最后,但如果上一个项目没有填满高度,我希望最后一项显示在页面底部。

    当前一项 + specialItem 占据所有位置时。
    --------------
    项目
    项目
    项目
    特殊物品
    --------------

    当前一项 + specialItem 超过高度时
    --------------
    项目
    项目
    项目
    项目
    项目
    项目
    特殊物品
    --------------

    当前一项 + specialItem 小于高度时
    --------------



    特殊物品
    --------------

    --------------
    项目


    特殊物品
    --------------

    要存档,我使用此代码

    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 毫秒的延迟运行代码,直到我找到更好的工作。

    【讨论】:

    • 为了避免postDelayed,可以使用lastItemView.post { /* Code inside post delayed*/}。并将view.height 实例替换为view.measuredHeight
    【解决方案4】:

    这对我有用,让回收器查看 layout_height="0dp",并将顶部约束放在相应的上述项目的底部,将底部约束放在父级,然后它将与剩余空间一起调整大小:

    <?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>
    

    【讨论】:

      猜你喜欢
      • 2013-07-25
      • 2021-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-10
      相关资源
      最近更新 更多