Android ViewPager - 在左右两侧显示页面预览

149

我正在使用安卓的ViewPager。我的需求是在左右两边都显示页面的预览。我已经发现可以使用负数的pageMargin来实现右侧预览的效果。

setPageMargin(-100);

有没有办法也显示左侧的预览?我正在寻找类似于相册小部件的东西。


好问题!我最近检查了源代码,如果你不想修改源代码,我觉得没有更好的方式了。setPageMargin(-100)能正常工作吗? - Macarse
GalleryView会更好地完成这项工作,除非你正在使用带有片段的ViewPger。 - Ali Alnoaimi
我能够使用画廊视图来完成这个。与任何方式中的ViewPager相同。不知道为什么画廊被弃用了。 - WillingLearner
这个问题在这里的帖子中已经被广泛讨论过了。 - Ben Pearson
我正在使用带有片段的Vierpager。那么我可以使用相同的方法吗? - asliyanage
如果您想使用 ViewPager2 实现,请查看此答案 https://dev59.com/olMH5IYBdhLWcg3w1TuK#58056129 - AskNilesh
17个回答

219

若要显示左右页面预览,请设置以下两个值:

  1. viewpager.setClipToPadding(false);
  2. viewpager.setPadding(left,0,right,0);

如果需要在视图页之间添加间距,则需添加:

viewpager.setPageMargin(int);

2
是的,我同意你的看法,Jiju Induchoodan。更多信息请参考http://blog.neteril.org/blog/2013/10/14/android-tip-viewpager-with-protruding-children/ 和 https://dev59.com/kmYr5IYBdhLWcg3wJ3CU - Sripathi
很酷,这很简单 :) 在视图翻页器切换页面时,是否有可能调整右侧和左侧预览的大小?例如 - 如果片段失去焦点 - 它的比例将为~0.8,但当我们将其拖到中心时 - 比例将增加到1,并且中心视图的上一个片段在离开屏幕时比例将改变为0.8? - iamthevoid
这个能用!但当我尝试缩放中心布局时,它会缩放到顶部和底部。由于填充,左侧和右侧不可见。请参见问题 https://stackoverflow.com/questions/52710076/android-pinch-to-zoom-layout-inside-a-viewpager-with-padding-left-and-right - Anooj Krishnan G

193

使用新的ViewPager2解决方案

现在,您应该考虑使用ViewPager2,它“替换了ViewPager,并解决了大部分前身的痛点”:

  • 基于RecyclerView
  • RTL(从右到左)布局支持
  • 垂直方向支持
  • 可靠的Fragment支持(包括处理底层Fragment集合的更改)
  • 数据集更改动画(包括DiffUtil支持)

结果

enter image description here

代码

在Activity/Fragment中设置ViewPager2:

// MyRecyclerViewAdapter is an standard RecyclerView.Adapter :)
viewPager2.adapter = MyRecyclerViewAdapter() 

// You need to retain one page on each side so that the next and previous items are visible
viewPager2.offscreenPageLimit = 1

// Add a PageTransformer that translates the next and previous items horizontally
// towards the center of the screen, which makes them visible
val nextItemVisiblePx = resources.getDimension(R.dimen.viewpager_next_item_visible)
val currentItemHorizontalMarginPx = resources.getDimension(R.dimen.viewpager_current_item_horizontal_margin)
val pageTranslationX = nextItemVisiblePx + currentItemHorizontalMarginPx
val pageTransformer = ViewPager2.PageTransformer { page: View, position: Float ->
    page.translationX = -pageTranslationX * position
    // Next line scales the item's height. You can remove it if you don't want this effect
    page.scaleY = 1 - (0.25f * abs(position))
    // If you want a fading effect uncomment the next line:
    // page.alpha = 0.25f + (1 - abs(position))
}
viewPager2.setPageTransformer(pageTransformer)

// The ItemDecoration gives the current (centered) item horizontal margin so that
// it doesn't occupy the whole screen width. Without it the items overlap
val itemDecoration = HorizontalMarginItemDecoration(
    context,
    R.dimen.viewpager_current_item_horizontal_margin
)
viewPager2.addItemDecoration(itemDecoration)

添加 HorizontalMarginItemDecoration,它是一个简单的 ItemDecoration
/**
 * Adds margin to the left and right sides of the RecyclerView item.
 * Adapted from https://dev59.com/KGAf5IYBdhLWcg3wdCl1#27664023
 * @param horizontalMarginInDp the margin resource, in dp.
 */
class HorizontalMarginItemDecoration(context: Context, @DimenRes horizontalMarginInDp: Int) :
    RecyclerView.ItemDecoration() {

    private val horizontalMarginInPx: Int =
        context.resources.getDimension(horizontalMarginInDp).toInt()

    override fun getItemOffsets(
        outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State
    ) {
        outRect.right = horizontalMarginInPx
        outRect.left = horizontalMarginInPx
    }

}

添加控制前/后项目可见度以及当前项目水平边距的尺寸:

<dimen name="viewpager_next_item_visible">26dp</dimen>
<dimen name="viewpager_current_item_horizontal_margin">42dp</dimen>

在此输入图片描述

最后将 ViewPager2 添加到您的布局中:

<androidx.viewpager2.widget.ViewPager2
    android:id="@+id/viewPager"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

一个重要的事情:一个 ViewPager2 的项必须有 layout_height="match_parent"(否则会抛出 IllegalStateException 异常),所以你应该这样做:

<androidx.cardview.widget.CardView 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" <-- this!
    app:cardCornerRadius="8dp"
    app:cardUseCompatPadding="true">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="280dp">

        <!-- ... -->

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.cardview.widget.CardView>

PageTransformer示例

Google在ViewPager2上添加了一个指南,其中包含两个PageTransformer实现,可以用作灵感来源:https://developer.android.com/training/animation/screen-slide-2

关于新的ViewPager2


1
我需要稍微移动视图页面才能实现这个效果。 - Muhammad Saqib
1
没事了,我忘记加这一行代码 setOffscreenPageLimit(1) :) - Muhammad Saqib
1
这是我找到的最佳解决方案。它完美地工作,但是是的,请不要忘记将 ViewPager2 的项目宽度和高度设置为 MATCH_PARENT。 - khushbu
1
@akubi 很抱歉,我无法提供代码,因为它属于一家私人公司。但请注意,您在这里拥有所需的所有代码。 - Albert Vila Calvo
对我来说可行,是个好的解决方案。谢谢。 - user3219477
显示剩余5条评论

84

更新:ViewPager 2.0

请使用以下内容

viewPager2.setPageTransformer(new MarginPageTransformer(1500));

另请参见,Android ViewPager2 setPageMargin unresolved

旧回答

@JijuInduchoodan的回答是完美且有效的。但是,由于我对Android相对较新,在理解和正确设置它方面花了一些时间。因此,我发布了这个答案供将来参考,并帮助其他与我处境相同的人。

if (viewPager == null) 
        {
            
            // Initializing view pager
            viewPager = (ViewPager) findViewById(R.id.vpLookBook);
            
            // Disable clip to padding
            viewPager.setClipToPadding(false);
            // set padding manually, the more you set the padding the more you see of prev & next page
            viewPager.setPadding(40, 0, 40, 0);
            // sets a margin b/w individual pages to ensure that there is a gap b/w them
            viewPager.setPageMargin(20);
        }

在适配器中不需要设置ViewPager页面的任何宽度。没有额外的代码需要在ViewPager中查看前一页和后一页。然而,如果您想在每个页面的顶部和底部添加空白间隔,您可以将以下代码设置为ViewPager子页面的父布局。

android:paddingTop="20dp"
android:paddingBottom="20dp"

ViewPager的最终外观

这将是ViewPager的最终外观。


我在ViewPager2中找不到viewPager.setPageMargin(20); - Mahmoud Mabrok
1
@MahmoudMabrok 请查看这个问题 https://dev59.com/PVMI5IYBdhLWcg3wfbfN - Rohan Kandwal

29

在2017年,可以通过使用带有PagerSnapHelperRecyclerView(添加在v7支持库的25.1.0版本中)轻松实现此类行为:

输入图像描述

不久前,我需要这样的viewpager-like功能,并准备了一个小型库:

MetalRecyclerPagerView - 您可以在那里找到所有代码和示例。

主要由单个类文件组成:MetalRecyclerViewPager.java(以及两个xml:attrs.xmlids.xml)。

希望它能帮助某些人并节省一些时间 :)


离题了,因为有时只需要一个 pager,但是还是很好知道!谢谢分享! - sud007
太棒了。我已经有了大量的RecyclerView助手代码。只需添加一个简单的new PagerSnapHelper().attachToRecyclerView(recyclerView),我就可以实现页面捕捉了。 - Steve

16
你可以在XML文件中完成这个操作,只需使用以下代码:
 android:clipToPadding="false"
 android:paddingLeft="XX"
 android:paddingRight="XX"

例如:

<androidx.viewpager.widget.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:paddingLeft="30dp"
        android:paddingRight="30dp" />
注意:如果您需要在页面之间添加间距,请将内边距/外边距设置为子片段。

3
我给你的投票点了一个踩,抱歉兄弟。这次它起作用了!我又点了一个赞。 - shikhar bansal

13

Kotlin + ViewPager2

enter image description here

我晚了一步,但是上面的答案对我没用。所以我来帮助其他仍在寻找答案的人。

  1. 您的XML:

     <androidx.viewpager2.widget.ViewPager2
     android:id="@+id/newsHomeViewPager"
     android:layout_width="match_parent"
     android:layout_height="wrap_content" />
  • 您的item布局根视图应该像这样:

         <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:paddingStart="@dimen/dp_10"
     xmlns:tools="http://schemas.android.com/tools"
     android:orientation="vertical" />
    
  • 您的 Activity/Fragment:

  • 在设置您的 ViewPager 适配器之前,请添加此代码。

            newsHomeViewPager.apply {
            clipToPadding = false   // allow full width shown with padding
            clipChildren = false    // allow left/right item is not clipped
            offscreenPageLimit = 2  // make sure left/right item is rendered
        }
    
        //increase this offset to show more of left/right
        val offsetPx =
            resources.getDimension(R.dimen.dp_30).toInt().dpToPx(resources.displayMetrics)
        newsHomeViewPager.setPadding(0, 0, offsetPx, 0)
    
        //increase this offset to increase distance between 2 items
        val pageMarginPx =
            resources.getDimension(R.dimen.dp_5).toInt().dpToPx(resources.displayMetrics)
        val marginTransformer = MarginPageTransformer(pageMarginPx)
        newsHomeViewPager.setPageTransformer(marginTransformer)
    
    1. 将dp转为像素的函数:

      fun Int.dpToPx(displayMetrics: DisplayMetrics): Int = (this * displayMetrics.density).toInt()

    通用注释:

    1. 我只需要显示下一页,所以在setPadding方法中,左侧保持0,右侧保持offsetPx。如果您想要显示右侧的项目,请在setPadding方法中也将offsetPx添加到右侧。

    2. 我使用了维度文件中的dp,您可以使用自己的。

    谢谢,愉快编码。


    最后一项有不必要的填充。如何摆脱它? - Adeel Ahmad

    9
    如果有人仍在寻找解决方案,我已经定制了ViewPage以实现它而不使用负边距。在此处找到一个示例项目:https://github.com/44kksharma/Android-ViewPager-Carousel-UI。它应该适用于大多数情况,但您仍然可以使用mPager.setPageMargin(像素间距);来定义页面间距。


    5

    Kotlin扩展ViewPager2的解决方案


    1. Single Line Implementation in your Activity or Fragment.

      binding.viewpager2.setPreviewBothSide(R.dimen._20sdp,R.dimen._35sdp)
      
    2. Add Below Code in HorizontalMarginItemDecoration.kt file in your Project

      import android.content.Context
      import android.graphics.Rect
      import android.view.View
      import androidx.annotation.DimenRes
      import androidx.recyclerview.widget.RecyclerView
      import androidx.viewpager2.widget.ViewPager2
      
      class HorizontalMarginItemDecoration(context: Context, @DimenRes horizontalMarginInDp: Int) :
          RecyclerView.ItemDecoration() {
          private val horizontalMarginInPx: Int =
              context.resources.getDimension(horizontalMarginInDp).toInt()
      
          override fun getItemOffsets(
              outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State
          ) {
              outRect.right = horizontalMarginInPx
              outRect.left = horizontalMarginInPx
          }
      }
      
      fun ViewPager2.setPreviewBothSide(@DimenRes nextItemVisibleSize: Int,@DimenRes currentItemHorizontalMargin: Int) {
          this.offscreenPageLimit = 1
          val nextItemVisiblePx = resources.getDimension(nextItemVisibleSize)
          val currentItemHorizontalMarginPx = resources.getDimension(currentItemHorizontalMargin)
          val pageTranslationX = nextItemVisiblePx + currentItemHorizontalMarginPx
          val pageTransformer = ViewPager2.PageTransformer { page: View, position: Float ->
              page.translationX = -pageTranslationX * position
              page.scaleY = 1 - (0.25f * kotlin.math.abs(position))
          }
          this.setPageTransformer(pageTransformer)
      
          val itemDecoration = HorizontalMarginItemDecoration(
              context,
              currentItemHorizontalMargin
          )
          this.addItemDecoration(itemDecoration)
      }
      
    3. And Yes Here you got this Amazing Output

      enter image description here

    这是对Albert Vila Calvo的答案进行优化后的版本。


    垂直ViewPager怎么样?我正在使用垂直ViewPager并希望预览上下项目。 - krupa parekh
    @krupaparekh,你可以尝试修改上面的答案(我给你一个提示),如果提到宽度,你可以用高度替换它,左右替换为上下,并在上面的答案中修改x为y,y为x。 - Nikunj Paradva

    4

    对于那些在不同屏幕上使用此功能有困难的人,

    mPager.setClipToPadding(false);
    DisplayMetrics displayMetrics = new DisplayMetrics();
    self.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    int width = displayMetrics.widthPixels;
    int paddingToSet = width/4; //set this ratio according to how much of the next and previos screen you want to show.
    mPager.setPadding(paddingToSet,0,paddingToSet,0);
    
    

    2
    感谢本文的作者。这是我找到的最佳解决方案。 只需在您的 Helpers 类中创建一个函数,例如:
    fun ViewPager2.showHorizontalPreview(offsetDpLeft : Int, offsetDpRight : Int, marginBtwItems : Int){
            this.apply {
                clipToPadding = false   // allow full width shown with padding
                clipChildren = false    // allow left/right item is not clipped
                offscreenPageLimit = 2  // make sure left/right item is rendered
            }
    
            // increase this offset to show more of left/right
            val offsetPxLeft = offsetDpLeft.toPx()
            val offsetPxRight = offsetDpRight.toPx()
            this.setPadding(offsetPxLeft, 0, offsetPxRight, 0)
    
            // increase this offset to increase distance between 2 items
            val pageMarginPx = marginBtwItems.toPx()
            val marginTransformer = MarginPageTransformer(pageMarginPx)
            this.setPageTransformer(marginTransformer)
        }
    

    然后像这样调用:

    mViewPager.showHorizontalPreview(40,40,5)
    

    在您的Activity/Fragment中。

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