具有不同项目高度和 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
,让孩子重新测量自己后,调整ViewPager2
的LayoutParams
。
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_height
是wrap_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”高度