ViewPager2具有不同项目高度和WRAP_CONTENT

38

有一些帖子介绍了如何使用不同高度项来使ViewPager正常工作,这些帖子都围绕着扩展 ViewPager 本身以修改其 onMeasure 以支持此功能。

然而,鉴于 ViewPager2 被标记为final类,我们无法对其进行扩展。

是否有人知道如何使它正常工作的方法?


例如,假设我有两个视图:

View1 = 200dp

View2 = 300dp

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

但是,当我滚动到View2时,高度仍为200dp;View2的最后100dp被裁剪掉了。


2
你是否尝试过调用setOffscreenPageLimit将两个视图保留在层次结构中? - Sandi
1
嗨 - 虽然这可以解决问题,但使用 setOffscreenPageLimit 的问题在于我不希望较小的视图下面有大量的空白/空白。 - Meetarp
请使用此解决方案 https://dev59.com/0EkGtIcB2Jgan1znEsd7#64235840 - Tarique Anowar
19个回答

26

只需在 ViewPager2 中为所需的 Fragment 执行以下操作:

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

喷气背包:binding.root.requestLayout()(感谢 @syed-zeeshan 提供的具体信息)


3
这个非常好用。我只是在onResume中调用了'binding.root.requestLayout()'。 - Syed Zeeshan
1
这是我情况的完美解决方案...使用FragmentStateAdapter。 需要在所有片段中添加onResume代码。哇!!! - skafle
1
如果我在ViewPager2中没有使用片段,该怎么办? - ProjectDelta

25

解决方案是注册一个 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


18
你如何// ...获取视图 - xxfast
@xxfast 你需要将项目传递到你的适配器中 - 所以我只是从我传递的视图列表中获取它。 - Meetarp
这在查看视图时不起作用。请参考此链接:https://dev59.com/EFMH5IYBdhLWcg3wvyA4#60410925 - VenomVendor
2
如果我没有向适配器传递任何项,我该如何“// ...获取视图”?我正在使用FragmentStateAdapter并在那里实例化片段。因为大小始终为两个,所以我只需使用静态值(getItemCount的2)。 - Richard
由于ViewPager2中的内部RecyclerView是实现细节,无法访问,因此我使用以下代码来获取项目视图:fun getViewAtPosition(position: Int): View = (viewPager.getChildAt(0) as RecyclerView).layoutManager?.findViewByPosition(position) ?: error("No layout manager set or no view found at position $position") - Thomas Keller
显示剩余6条评论

12

在我的情况下,在 onPageSelected 中添加 adapter.notifyDataSetChanged() 有所帮助。


这个答案很容易实现,而且效果非常好。 - Paul Souteyrat
1
哇,这真的起作用了。我讨厌我们不得不绕过基本的功能来进行黑客攻击... Android... - Vucko
好的,它可以工作。当您从小项目滚动到较大项目时会有轻微的减速,同时还会出现调整大小的情况。 - CoolMind
2
它工作得很晚...例如,我的第一个视图是300dp,第二个是200dp,第三个是200dp...当我从第一个到第二个时,它仍然是300dp,直到我到第三个...有什么解决方案吗? - Abdul-Aziz-Niazi
1
不起作用,大小仍然相同,最小的一个。 - user924
对我也不起作用 - undefined

6

只需在 ViewPager2 的所有片段中添加此小代码即可:

@Override
    public void onResume() {
        super.onResume();
        binding.getRoot().requestLayout();
    }

这对我来说完美地运作了(如果你没有使用binding,那么只需获取根布局实例即可代替binding)。

6

我也曾遇到类似的问题,但是是针对片段进行处理。相比于接受的答案中调整视图大小,我决定将视图包裹在ConstraintLayout中。这需要你指定ViewPager2的大小,而不使用wrap_content。

因此,我们不能改变viewpager的大小,它必须是它所处理的最大视图的最小大小。

作为一个新手,不知道这是否是一个好的解决方案,但对我来说确实可以完成工作。

换句话说:

<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"
  >
    <!-- Adding transparency above your view due to wrap_content -->
    <androidx.constraintlayout.widget.ConstraintLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      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实现。 - VenomVendor
很高兴能帮忙! :) - Miniskurken
2
只有在事先知道您的项目高度时,此方法才有效。 - Jeff Padgett

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

4

在 ViewPager2 中使用的 Fragment 类中的 onResume() 方法中,只需调用布局根视图的 .requestLayout() 即可。


1
这真的很有效,在我的每个被ViewPager使用的Fragment中,我添加了许多子项(Fragment)。此外,我的应用程序还具有某种“加载更多”功能,您可以单击以加载更多数据,并且它可以正常工作。不错。 - bobby I.

2

我遇到了类似的问题,并按以下方式解决。 在我的情况下,我使用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。


1

只需将此代码添加到每个片段中:

override fun onResume() {
    super.onResume()
    binding.root.requestLayout()
}

1

最终@Mephoros的答案对我有用。我在一个片段中使用了带分页(v3)的Recyclerview,但是它在页面加载时表现得非常奇怪。以下是一个可行的代码片段,基于这个答案,以防任何人在获取和清理视图方面遇到问题。

viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            var view : View? = null
            private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
                view?.let {
                    updatePagerHeightForChild(it)
                }
            }

            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                // ... IMPORTANT: remove the global layout listener from other view
                view?.viewTreeObserver?.removeOnGlobalLayoutListener(layoutListener)
                view = (viewPager[0] as RecyclerView).layoutManager?.findViewByPosition(position)
                view?.viewTreeObserver?.addOnGlobalLayoutListener(layoutListener)
            }

            private fun updatePagerHeightForChild(view: View) {
                view.post {
                    val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
                    val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
                    view.measure(wMeasureSpec, hMeasureSpec)
                    if (viewPager.layoutParams.height != view.measuredHeight) {
                        viewPager.layoutParams = (viewPager.layoutParams)
                            .also { lp -> lp.height = view.measuredHeight }
                    }
                }
            }
        })

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接