具有不同项目高度和 WRAP_CONTENT 的 ViewPager2

Posted

技术标签:

【中文标题】具有不同项目高度和 WRAP_CONTENT 的 ViewPager2【英文标题】:ViewPager2 with differing item heights and WRAP_CONTENT 【发布时间】:2020-02-17 22:14:27 【问题描述】:

有几篇文章介绍了如何让 ViewPager 处理不同高度的项目,这些项目围绕扩展 ViewPager 本身以修改其 onMeasure 以支持这一点。

但是,鉴于 ViewPager2 被标记为最终类,我们无法扩展它。

有人知道有没有办法解决这个问题?


例如假设我有两种观点:

View1 = 200dp

View2 = 300dp

当 ViewPager2 (layout_height="wrap_content") 加载时 -- 查看 View1,它的高度将为 200dp。

但是当我滚动到 View2 时,高度仍然是 200dp; View2 的最后 100dp 被截断。

【问题讨论】:

您是否尝试通过调用setOffscreenPageLimit 将两个视图保留在层次结构中? 嗨 - 虽然这可以解决问题,但使用 setOffscreenPageLimit 的问题是我不希望较小的视图下面有一堆空白/空白。 【参考方案1】:

解决办法是注册一个PageChangeCallback,让孩子重新测量自己后,调整ViewPager2LayoutParams

pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() 
    override fun onPageSelected(position: Int) 
        super.onPageSelected(position)
        val view = // ... get the view
        view.post 
            val wMeasureSpec = MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY)
            val hMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
            view.measure(wMeasureSpec, hMeasureSpec)

            if (pager.layoutParams.height != view.measuredHeight) 
                // ParentViewGroup is, for example, LinearLayout
                // ... or whatever the parent of the ViewPager2 is
                pager.layoutParams = (pager.layoutParams as ParentViewGroup.LayoutParams)
                    .also  lp -> lp.height = view.measuredHeight 
            
        
    
)

或者,如果您的视图的高度可能会在某些时候发生变化,例如异步数据加载,然后改用全局布局监听器:

pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() 
    private val listener = ViewTreeObserver.OnGlobalLayoutListener 
        val view = // ... get the view
        updatePagerHeightForChild(view)
    

    override fun onPageSelected(position: Int) 
        super.onPageSelected(position)
        val view = // ... get the view
        // ... IMPORTANT: remove the global layout listener from other views
        otherViews.forEach  it.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener) 
        view.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
    

    private fun updatePagerHeightForChild(view: View) 
        view.post 
            val wMeasureSpec = MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY)
            val hMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
            view.measure(wMeasureSpec, hMeasureSpec)

            if (pager.layoutParams.height != view.measuredHeight) 
                // ParentViewGroup is, for example, LinearLayout
                // ... or whatever the parent of the ViewPager2 is
                pager.layoutParams = (pager.layoutParams as ParentViewGroup.LayoutParams)
                    .also  lp -> lp.height = view.measuredHeight 
            
        
    

在此处查看讨论:

https://issuetracker.google.com/u/0/issues/143095219

【讨论】:

你怎么// ... get the view @xxfast 你必须将项目传递给你的适配器——所以我只是从我传入的视图列表中获取它 @xxfast 我最后做得超级难看,但它有效。首先,我一直引用从 onAttachToRecyclerView(recyclerView) 获取的 recyclerview,然后我得到如下视图:(viewPager2.adapter as Adapter).recyclerView.layoutManager?.findViewByPosition(position) 当然首先你必须将 Adapter 实例分配给 viewpager2 (setAdapter) This doesn't work when views are peeked. Refer this https://***.com/a/60410925/1008278 如果我没有将任何项目传递给适配器,我该如何// ... get the view?我正在使用FragmentStateAdapter 并在那里实例化片段。因为大小始终为 2,所以我只使用一个静态值(getItemCount 为 2)。【参考方案2】:

就我而言,在onPageSelected 中添加adapter.notifyDataSetChanged() 会有所帮助。

【讨论】:

这个答案很容易实现,就像一个魅力 哇这真的有效。我讨厌我们必须四处乱窜才能获得基本的功能...... android...... 嗯,它有效。当您从小项目滚动到大项目时,会出现小幅减速,并且可以看到调整大小。 它工作得晚..例如,我的第一个视图是 300dp,第二个是 200dp,第三个 200dp .. 当我从第一个到第二个时,它仍然是 300dp,直到我去第三个 .. 任何解决方案? 不行,大小还是一样,最小的一个【参考方案3】:

只需在ViewPager2 中为所需的Fragment 执行此操作即可:

override fun onResume() 
     super.onResume()
     layoutTaskMenu.requestLayout()

Jetpack:binding.root.requestLayout()(感谢@syed-zeeshan 提供详细信息)

【讨论】:

这很好用。我只是在 onResume 'binding.root.requestLayout()' 中调用了这个 这对于我的情况来说是完美的解决方案......使用 FragmentStateAdapter。需要在所有片段中添加 onResume 代码。瞧!!!【参考方案4】:

我自己偶然发现了这个案例,但是有碎片。 我决定将视图包装在 ConstraintLayout 中,而不是调整视图的大小作为接受的答案。这需要您指定 ViewPager2 的大小,而不是使用 wrap_content。

因此,与其更改我们的 viewpager 的大小,不如将其设置为它处理的最大视图的最小尺寸。

对 Android 有点新,所以不知道这是否是一个好的解决方案,但它对我有用。

换句话说:

<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_
  android:layout_
  >
    <!-- Adding transparency above your view due to wrap_content -->
    <androidx.constraintlayout.widget.ConstraintLayout
      android:layout_
      android:layout_
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      >

      <!-- Your view here -->

    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

【讨论】:

这是正确的解决方案,我有 viewpager,它的两边都窥视了一些项目。因此registerOnPageChangeCallback 是无用的,因为它是在用户交互时调用的。但是,我能够通过单个 ConstraintLayout 实现 很高兴我能帮上忙! :) 这只有在您提前知道物品的高度时才有效。【参考方案5】:

对我来说这很完美:


    viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() 
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) 
                super.onPageScrolled(position,positionOffset,positionOffsetPixels)
                if (position>0 && positionOffset==0.0f && positionOffsetPixels==0)
                    viewPager2.layoutParams.height =
                        viewPager2.getChildAt(0).height
                
            
        )

【讨论】:

【参考方案6】:

只需在 ViewPager2 中使用的 Fragment 类的 onResume() 中调用 .requestLayout() 到布局的根视图

【讨论】:

这实际上是有效的,在 ViewPager 使用的每个 Fragment 中,我添加了许多子项(Fragment)。此外,我的应用程序具有某种加载更多功能,您可以在其中单击以加载更多数据,并且它可以工作。不错的一个【参考方案7】:

没有发布的答案完全适用于我的情况 - 事先不知道每个页面的高度 - 所以我使用 ConstraintLayout 通过以下方式解决了不同的 ViewPager2 页面高度: p>

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_
    android:layout_
    >

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_
        android:layout_
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        >

        <!-- ... -->

    </com.google.android.material.appbar.AppBarLayout>

    <!-- Wrapping view pager into constraint layout to make it use maximum height for each page. -->
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/viewPagerContainer"
        android:layout_
        android:layout_
        app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/appBarLayout"
        >

        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/viewPager"
            android:layout_
            android:layout_
            android:orientation="horizontal"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigationView"
        android:layout_
        android:layout_
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/bottom_navigation_menu"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

【讨论】:

【参考方案8】:

@Mephoros 代码在视图之间滑动时可以完美运行,但在第一次查看视图时无法正常运行。刷卡后按预期工作。

所以,以编程方式滑动 viewpager:

binding.viewpager.setCurrentItem(1)
binding.viewpager.setCurrentItem(0) //back to initial page 

【讨论】:

是的,它有效。但是,这不是一个好主意。【参考方案9】:

@sonique 在这里有一个非常详细的答案和解释。

https://***.com/a/64235840/2867351

基本上,您应该尝试做的是在onPageSelected() 时动态计算高度。

您可以使用childFragmentManager 轻松访问您在ViewPager2 中的视图。

确保您的子片段layout_heightwrap_content。否则,当视图尝试测量其高度时,您会看到奇怪的行为。

我学得很辛苦。

viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() 
    override fun onPageSelected(position: Int) 
        super.onPageSelected(position)
        if (childFragmentManager.fragments.size > position) 
            val fragment = childFragmentManager.fragments.get(position)
            fragment.view?.let 
                updatePagerHeightForChild(it, viewPager)
            
        
    

    fun updatePagerHeightForChild(view: View, pager: ViewPager2) 
        view.post 
            val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
                view.width, View.MeasureSpec.EXACTLY
            )
            val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
                0, View.MeasureSpec.UNSPECIFIED
            )
            view.measure(widthMeasureSpec, heightMeasureSpec)
            if (pager.layoutParams.height != view.measuredHeight) 
                pager.layoutParams = (pager.layoutParams).also 
                    it.height = view.measuredHeight
                
            
        
    
)

【讨论】:

如果 childFrament 有多个片段,此解决方案将失败。【参考方案10】:

我也被这个问题困住了。我正在为带有帐户信息的几个选项卡实现 TabLayout 和 ViewPager2。这些标签必须具有不同的高度,例如:View1 - 300dp、View2 - 200dp、Tab3 - 500dp。高度被锁定在第一个视图的高度内,其他的被剪切或延伸到(示例)300dp。像这样:

因此,经过两天的搜索,我没有任何帮助(或者我不得不尝试更好),但我放弃了并使用 NestedScrollView 来查看我的所有视图。当然,现在我没有效果,配置文件的标题在 3 个视图中滚动显示信息,但至少它现在以某种方式工作。 希望这个对某人有所帮助!如果您有任何建议,请随时回复!

附:我很抱歉我的英语不好。

【讨论】:

我检查了您的答案,但ViewPager2 将应用最高片段来处理其他较短的片段。所以在这种情况下这并没有真正的帮助。【参考方案11】:

我遇到了类似的问题并解决了如下。 就我而言,我让 ViewPager2 与 TabLayout 一起使用不同高度的片段。 在 onResume() 方法的每个片段中,我添加了以下代码:

@Override
public void onResume() 
    super.onResume();
    setProperHeightOfView();


private void setProperHeightOfView() 
    View layoutView = getView().findViewById( R.id.layout );
    if (layoutView!=null) 
        ViewGroup.LayoutParams layoutParams = layoutView.getLayoutParams();
        if (layoutParams!=null) 
            layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
            layoutView.requestLayout();
        
    

R.id.layout 是特定片段的布局。 我希望我有所帮助。 最好的祝福, T.

【讨论】:

【参考方案12】:
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() 
  override fun onPageSelected(position: Int) 
    super.onPageSelected(position)
    val view = (viewPager[0] as RecyclerView).layoutManager?.findViewByPosition(position)

    view?.post 
      val wMeasureSpec = MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY)
      val hMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
      view.measure(wMeasureSpec, hMeasureSpec)

      if (viewPager.layoutParams.height != view.measuredHeight) 
        viewPager.layoutParams = (viewPager.layoutParams).also  lp -> lp.height = view.measuredHeight 
      
    
  
)

【讨论】:

以上是关于具有不同项目高度和 WRAP_CONTENT 的 ViewPager2的主要内容,如果未能解决你的问题,请参考以下文章

将视图放置在高度为 wrap_content 的相对布局的底部

UICollectionViewCell 有 2 个单元格,每个单元格都有“wrap_content”高度

异常的 WRAP_CONTENT 行为

具有不同项目高度和宽度的网格

如何将回收器高度设置为 recyclerView 中的最高项目?

安卓 |滚动视图 |高度 wrap_content