ViewPager2实现循环画廊
前言
由于想要更加好看的效果所以在网上学习了ViewPager2实现循环画廊的代码,在这分享一下学习心得。
一、ViewPager2
ViewPager与ViewPager2
ViewPager2是一个非常优秀的框架,它继承自RecyclerView,因此拥有良好的性能的同时可以实现垂直和水平两种方向的滑动,而ViewPager只能水平滑动;另外,ViewPager2模式实现了懒加载,默认不进行预加载,而ViewPager会进行预加载。需要注意的是,ViewPager 和 TabLayout 的代码关联用的是 TabLayout 的方法 setupWithViewPager();而 ViewPager2 是通过 TabLayoutMediator 类来做关联。
二、实现循环画廊
1.布局文件
随便设了一个绿色框,然后在下面设了一个数字为每个item作区分:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/img"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@color/teal_200" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="1"
android:textSize="@dimen/sp_20"
android:textColor="@color/black"/>
</LinearLayout>
非常简单的activity。
需要注意的是需要加上android:clipChildren=“false”,并且ViewPager2的大小需要小于父组件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:clipChildren="false"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/vp"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_400"
android:layout_margin="@dimen/dp_50"
android:clipChildren="false" />
</LinearLayout>
2.适配器
这里的循环其实就是要实现第一个item左滑跳到最后一个item,最后一个item右滑要跳到第一个item,这很容易实现,但由于第一个item前面没有item、最后一个item后面也没有item,所以是空白的,效果上并不好看,因此应该在第一个item前面加上与最后一个item相同的item、最后一个item后面加上与第一个item相同的item,这样滑动的时候肉眼看上去就是循环的效果了。
class GalleryAdapter(val context: Context) : RecyclerView.Adapter<GalleryAdapter.ViewHolder>() {
private val index = listOf<String>(
"3", "1", "2", "3", "1" // 关键在于,第一个需要等于倒数第二个,倒数第一个需要等于第二个
)
override fun getItemCount(): Int {
return index.size
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tv = itemView.findViewById<TextView>(R.id.tv)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tv.text = index[position]
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_gallery, parent, false))
}
}
3.页面切换动画
default:左右page的缩放倍率
default_max:位于中间的page的缩放倍率
default_alpha:左右page的透明度
class GalleryTransform(val viewpae2: ViewPager2) : ViewPager2.PageTransformer {
private val default = 19 / 20f
private val default_max = 21 / 20f
private val TEN = 10
private val default_alpha = 1 / 2f
override fun transformPage(page: View, position: Float) {
// MarginPageTransformer的源码
val offset: Float = 40 * position
if (viewpae2.getOrientation() == ViewPager2.ORIENTATION_HORIZONTAL) {
page.translationX = if (isRtl()) -offset else offset
} else {
page.translationY = offset
}
// 左滑:左page-1->-2,中page0->-1,右page1->0
// 右滑:左page-1->0,中page0->1,右page1->2
when {
position <= -1 -> {
page.scaleX = default
page.scaleY = default
page.alpha = default_alpha
Log.e("position", "<-1:$position")
}
position <= 0 -> {
page.scaleX = default_max + position / TEN
page.scaleY = default_max + position / TEN
page.alpha = 1 + position * default_alpha
Log.e("position", "<0:$position")
}
position <= 1 -> {
page.scaleX = default_max - position / TEN
page.scaleY = default_max - position / TEN
page.alpha = 1 - position * default_alpha
Log.e("position", "<1:$position")
}
else -> {
page.scaleX = default
page.scaleY = default
page.alpha = default_alpha
Log.e("position", ">1:$position")
}
}
}
/**
* 是否是从右往左滑
*/
private fun isRtl(): Boolean {
return viewpae2.layoutDirection == View.LAYOUT_DIRECTION_RTL
}
}
4.Activity
class GalleryActivity : FragmentActivity() {
private lateinit var viewPage: ViewPager2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_gallery)
viewPage = findViewById(R.id.vp)
val adapter = GalleryAdapter(this)
viewPage.adapter = adapter
viewPage.clipChildren = false;
viewPage.offscreenPageLimit = 3
viewPage.currentItem = 1 // 注意初始化时需要跳到第二个item
viewPage.setPageTransformer(GalleryTransform(viewPage))
//viewPage.setPageTransformer(ZoomOutPageTransformer())
viewPage.registerOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
if (state == ViewPager2.SCROLL_STATE_IDLE) {
if (viewPage.currentItem == 0) {
// 滑动到第一页的时候直接跳到倒数第二页,因为两页内容完全一致,跳的过程不会被人眼捕捉到
viewPage.setCurrentItem(adapter.itemCount - 2, false)
}
if (viewPage.currentItem == adapter.itemCount - 1) {
// 同理滑动到倒数第一页的时候直接跳到第二页
viewPage.setCurrentItem(1, false)
}
}
}
})
}
}
其中ZoomOutPageTransformer是来自开源库,不想要自定义切换动画的也可以用这个代替:
implementation 'io.github.youth5201314:banner:2.2.2'