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个回答

1

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

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

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        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_width="match_parent"
        android:layout_height="0dp"
        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_width="match_parent"
            android:layout_height="match_parent"
            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_width="match_parent"
        android:layout_height="wrap_content"
        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>

1

我正在使用来自这里ViewPager2ViewHeightAnimator


2
由于对片段视图使用全局布局侦听器的引用,它会导致内存泄漏。 - DennisVA

1

@Mephoros的代码在视图之间滑动时完美运行,但在首次查看视图时不起作用。在滑动后,它按预期工作。

因此,编程方式滑动viewpager:

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

1
是的,它可以工作。但是,这不是一个好主意。 - Morgan Koh

0

在ViewPager2中,只有adapter.notifyDataSetChanged()对我有效。我使用了以下Kotlin代码。

 viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                adapter.notifyDataSetChanged()
            }
        })

0
为什么不使用ViewPager2,而是通过替换来实现呢? 就像下面的代码一样:
private void fragmentController(Fragment newFragment){
    FragmentTransaction ft;
    ft = mainAct.getSupportFragmentManager().beginTransaction();
    ft.replace(R.id.relMaster, newFragment);
    ft.addToBackStack(null);
    ft.commitAllowingStateLoss();
}

其中relMaster是RelativeLayout。


0
如果您的每个页面都具有动态内容高度,并且从页面到页面不同,那么这里是处理基于实际页面高度和屏幕可用空间的页面高度的代码。
import android.graphics.Rect
import android.view.View
import androidx.core.view.doOnLayout
import androidx.core.view.updateLayoutParams
import androidx.viewpager2.widget.ViewPager2

fun updatePagerHeightForChild(view: View, pager: ViewPager2) {
  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)

    view.doOnLayout {
        val pageVisibleRect = Rect().also {
            view.getGlobalVisibleRect(it)
        }
        val pageVisibleHeight = pageVisibleRect.height()
        val pageActualHeight = view.measuredHeight

        val pagerVisibleRect = Rect().also {
            pager.getGlobalVisibleRect(it)
        }
        val pagerVisibleHeight = pagerVisibleRect.height()

        val rootVisibleRect = Rect().also {
            pager.rootView.getGlobalVisibleRect(it)
        }
        val rootVisibleHeight = rootVisibleRect.height()

        val isPageSmallerThanAvailableHeight =
            pageActualHeight <= pageVisibleHeight && pagerVisibleHeight <= rootVisibleHeight

        if (isPageSmallerThanAvailableHeight) {
            pager.updateLayoutParams {
                 height = pagerVisibleRect.bottom - pageVisibleRect.top

            }
        } else {
            pager.updateLayoutParams {
                height = pageActualHeight
            }
        }
        pager.invalidate()
    }
 }

将以下代码添加到片段或活动中:
    pager.setPageTransformer { page, pos ->
        if (pos == 0.0F) {
            updatePagerHeightForChild(page, pager)
        }
    }

0
我也卡在这个问题上。我正在实现TabLayout和ViewPager2,用于多个带有账户信息的选项卡。这些选项卡必须具有不同的高度,例如:View1 - 300dp,View2 - 200dp,Tab3 - 500dp。高度被锁定在第一个视图的高度内,其他视图则被截断或延伸到(例如)300dp。像这样:

enter image description here

经过两天的搜索,没有什么能帮助我(或者我需要更好地尝试),但我放弃了并使用了NestedScrollView来处理所有我的视图。当然,现在我没有效果,即个人资料的标题随着3个视图中的信息滚动,但至少它现在以某种方式工作了。 希望这篇文章能帮助到某些人!如果您有一些建议,请随时回复!

P.s. 对于我的英语水平不好,我感到很抱歉。


我查看了你的答案,但是 ViewPager2 会将最高的片段应用于其他较短的片段。所以在这种情况下并没有真正帮助。 - Truong Hieu

0

终于,我可以在不使用requestLayoutnotifyDataChanged或上述其他解决方案的情况下解决这个问题了!

这真的很容易和简单!

你只需要在onPause中保存当前高度,然后在onResume中加载保存的高度。

看看这个示例代码:


public class MyTabbedFragment extends Fragment {

   public MyTabbedFragmentViewBinding binding;

   String TAG = "MyTabbedFragment";

   int heightBeforePause;

   // other code

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

      Log.d(TAG, "lifecycle | onResume | before set height | rec view height: " + binding.recycleView.getHeight() + " | height before pause: " + heightBeforePause);

      // load the saved height
      if(heightBeforePause > 0) {
         FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, heightBeforePause);
         binding.recycleView.setLayoutParams(layoutParams);
      }
   }

   @Override
   public void onPause() {
      super.onPause();

      // save the current height
      heightBeforePause = binding.recycleView.getHeight();
      
      Log.d(TAG, "lifecycle | onPause | rec view height: " + binding.recycleView.getHeight());
   }


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

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