之前遇到一个需求,设计师希望的图片轮播切换作用如下图所示。

(图片有点大,加载略微等一小会)

最开端我的想法是选用ViewPager2的PageTransformer来完结,但是略微实践了一下便发现,单独只用PageTransformer来完结不太行,因为PageTransformer本质上相似是对view的特点动画操作,如tranalationX,Y,Z以及等等其他特点。

不过,先一步一步来完结吧。

堆叠作用

首先,PageTransformer能够完结堆叠滑动作用的,完结逻辑也很简略,本质上便是抵消滑动的位移,其原理的示意图如下:

ViewPager2另辟蹊径实现图片遮罩切割页面切换效果

详细完结代码如下:

详细page和position参数代表的含义能够参阅androidx.viewpager2.widget.ViewPager2.PageTransformer#transformPage源码注释,这儿便不再打开。

viewpager.setPageTransformer { page, position ->
    if (position < 0f) {
        page.translationX = 0f
        page.translationZ = 0f
    } else {
        page.translationX = page.width * -position
        page.translationZ = -position
    }
}

利用translationX来抵消后一个Item的滑动,同时利用translationZ来操控显现的层级来保证堆叠作用。 这儿能够再拓展一下另外一种操控view层级的方法,ViewGroup#getChildDrawingOrder方法,简略来说便是操控子View的制作次序。 在RecyclerView中提供了,setChildDrawingOrderCallback,能够直接自定义制作次序。

recyclerView.setChildDrawingOrderCallback { childCount, i -> }

所以上面的translationZ能够去掉,改成以下方法:

viewpager.setPageTransformer { page, position ->
    if (position < 0f) {
        page.translationX = 0f
    } else {
        page.translationX = page.width * -position
    }
}
// 错开制作次序,让当前的层级在后一个之上
viewpager.recyclerView.setChildDrawingOrderCallback { childCount, i -> childCount - i - 1 }

viewpager.recyclerView是用了kotlin的扩展特点,弥补说明一下。

private val ViewPager2.recyclerView: RecyclerView
    get() = this[0] as RecyclerView

好,完结ViewPager2的堆叠滑动仅仅第一步,下一步是图片的遮罩切开的作用。

遮罩切开的作用

这儿我尝试了很多方法,终究放弃仅靠PageTransformer来完结的思路,其实这个作用有点相似于阅览里的卷曲翻页作用(仿真作用)。

ViewPager2另辟蹊径实现图片遮罩切割页面切换效果

其完结原理是依托一个掩盖在上层的View,经过对View自定义处理制作流程来完结的。

所以这儿咱们也能够选用相似的计划,但是不用去完结杂乱的卷曲作用,所以咱们需求完结的只要两点

  1. 用一个OverlayView来掩盖ViewPager2
  2. OverlayView展现的是当前Item(至于展现的内容按照需求而定,也能够是item的View.drawToBitmap())
  3. 对OverlayView中的Bitmap展现区域进行限制

这儿我不打算直接对ViewPager2的布局进行修正,需求介绍一个不常用的内容:ViewOverlay,它提供了一种在不改变视图层次结构的情况下添加、移除或修正视图的能力,这样咱们就不用改变ViewPager2自身地点的布局结构了。

先来简略完结对图片的裁剪吧,其实代码很简略,便是根据滚动的offset来进行clip裁剪。

class PageOverlayView constructor(context: Context) : View(context) {
    var overlay: Bitmap? = null
        set(value) {
            field?.recycle()
            field = value
        }
    var currentPosition: Int = -1
    var currentPositionOffsetPx: Int = 0
        set(value) {
            field = value
            invalidate()
        }
    override fun onDraw(canvas: Canvas?) {
        val overlay = overlay ?: return super.onDraw(canvas)
        canvas?.withClip(left, top, width - currentPositionOffsetPx, bottom) {
            drawBitmap(overlay, 0f, 0f, null)
        }
    }
}

注册ViewPager2的registerOnPageChangeCallback,不过这儿需求首要调用以下OverlayView的layout,因为ViewOverlay是不会帮你进行layout的,所以layout需求自己来调用。

viewpager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
    override fun onPageSelected(position: Int) {
        super.onPageSelected(position)
        pageOverlayView.layout(
            viewpager.left,
            viewpager.top,
            viewpager.right,
            viewpager.bottom
        )
    }
    override fun onPageScrolled(
        position: Int,
        positionOffset: Float,
        positionOffsetPixels: Int
    ) {
        super.onPageScrolled(position, positionOffset, positionOffsetPixels)
        if (position != pageOverlayView.currentPosition) {
            pageOverlayView.overlay =
                viewpager.recyclerView.findViewHolderForAdapterPosition(position)?.itemView?.drawToBitmap()
        }
        pageOverlayView.currentPosition = position
        pageOverlayView.currentPositionOffsetPx = positionOffsetPixels
    }
})
// 最后添加到ViewPager2的overlay中
viewpager.overlay.add(pageOverlayView)

这样,咱们就十分简略的完结了需求的作用,当然这仅仅很简略的完结,详细的细节未完善,仅仅介绍一个可行的计划。

完整Demo

Github: Lowae/PagerOverlay (github.com)