之前遇到一个需求,设计师希望的图片轮播切换作用如下图所示。
(图片有点大,加载略微等一小会)
最开端我的想法是选用ViewPager2的PageTransformer来完结,但是略微实践了一下便发现,单独只用PageTransformer来完结不太行,因为PageTransformer本质上相似是对view的特点动画操作,如tranalationX,Y,Z以及等等其他特点。
不过,先一步一步来完结吧。
堆叠作用
首先,PageTransformer能够完结堆叠滑动作用的,完结逻辑也很简略,本质上便是抵消滑动的位移,其原理的示意图如下:
详细完结代码如下:
详细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来完结的思路,其实这个作用有点相似于阅览里的卷曲翻页作用(仿真作用)。
其完结原理是依托一个掩盖在上层的View,经过对View自定义处理制作流程来完结的。
所以这儿咱们也能够选用相似的计划,但是不用去完结杂乱的卷曲作用,所以咱们需求完结的只要两点
- 用一个OverlayView来掩盖ViewPager2
- OverlayView展现的是当前Item(至于展现的内容按照需求而定,也能够是item的View.drawToBitmap())
- 对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)