使用ViewPager2实现画廊效果,中间item突出,两侧渐小

2,588 阅读2分钟

使用ViewPager2实现画廊效果,中间item突出,两侧渐小

效果展示:【静默状态和拖动状态】

截屏2022-10-06 下午2.11.20.png

截屏2022-10-06 下午2.12.54.png

1、实现方法:

通过动态调整ViewPager2中item的位置和大小实现此功能。布局中添加ViewPager2,设置大小为item大小,父布局设置clipChildren=false。设置好预加载后,为ViewPager2添加PageTransformer,重写transformPage方法,动态设置item大小。

2、PageTransformer

提供两种重写方式,一种为不同item偏移量和缩放比例都不一样,需要针对特定的item设置设定的数据;一种为传入统一偏移量和缩放比例,统一计算处理所有item。第一中情况应该更常见,第二种逻辑更抽象。

a、针对特定position所在区间,特殊设置该item的属性

class ImageTransformer: PageTransformer {

    companion object {
        private const val TAG = "ImageTransformer"
    }

    override fun transformPage(view: View, position: Float) {
        val global = view.findViewById<TextView>(R.id.tv_text).text
        Log.d(TAG, "transformPage: global = $global position = $position")
        view.visibility = View.VISIBLE
        when {
            position < -2 && position > -3 -> { //左三转左二
                val p = 3 + position
                view.translationX = 1000f - (300f * p)
                view.scaleX = 0.3f + 0.2f * p
                view.scaleY = 0.3f + 0.2f * p
                view.translationZ = 0f
                view.alpha = p
            }
            position < -1 && position >= -2 -> { //左二转左一
                val p = 2 + position // 0 -> 1
                view.translationX = 700f - (400f * p)
                view.scaleX = 0.5f + (0.2f* p)
                view.scaleY = 0.5f + (0.2f * p)
                view.translationZ = 0f + (1f * p)
            }
            position < 0 && position >= -1f -> { //左一转中间
                val p = 1 + position
                view.translationX = 300f - (300f * p)
                view.scaleX = 0.7f + (0.3f * p)
                view.scaleY = 0.7f + (0.3f * p)
                view.translationZ = 1f + (8f * p)
            }
            position >= 0f && position < 1f -> { //右一转中间
                view.translationX = 0 + position * -300
                view.scaleX = 1f - 0.3f * position
                view.scaleY = 1f - 0.3f * position
                view.translationZ = 9 - (8 * position)
            }
            position >= 1f && position < 2f -> { // 右二转右一
                val p = position - 1
                view.translationX = -300 + (p * -400)
                view.scaleX = 0.7f - 0.2f * p
                view.scaleY = 0.7f - 0.2f * p
                view.translationZ = 1 - 1f * p
            }
            position >= 2f && position < 3 -> {  //右3转右2
                val p = position - 2
                view.translationX = -700f + (p * -300f)
                view.scaleX = 0.5f - 0.2f * p
                view.scaleY = 0.5f - 0.2f * p
                view.translationZ = 0f
                view.alpha = 1 - p
            }
            else -> { //左3和右3以外隐藏显示
                view.visibility = View.GONE
            }
        }
    }
}

b、统一按比例设置属性

class Transformer(
    /**
     * 缩放比例
     * 相比于前一item的缩放比例
     */
    private val scale: Float = DEFAULT_SCALE,
    /**
     * item显示总数
     */
    private val count: Int = DEFAULT_COUNT,
    /**
     * 偏移量 要被上层item遮盖的区域v
     */
    private val deviation: Int = DEFAULT_DEVIATION,
): PageTransformer {

    companion object {
        private const val TAG = "Transformer"

        const val DEFAULT_SCALE = 0.3f
        const val DEFAULT_COUNT = 5
        const val DEFAULT_DEVIATION = 300
    }


    override fun transformPage(page: View, position: Float) {
        val global = page.findViewById<TextView>(R.id.tv_text).text
        Log.d(TAG, "transformPage: global = $global position = $position")
        Log.d(TAG, "transformPage: x = ${page.translationX}")
        val absPosition = position.absoluteValue
        if (absPosition.toInt() >= count / 2 + 1) {
            page.visibility = View.GONE
            return
        } else {
            page.visibility = View.VISIBLE
        }
        //设置偏移量 拖动过程中动态设置偏移量
        val tran = deviation * position
        Log.d(TAG, "transformPage: tran = $tran")
        page.translationX = -1 * tran
        page.translationZ = count - position.absoluteValue
        //设置缩放比例
        // 拖动过程中动态设置缩放比例在大和小item中顺滑变化
        var s = 1f
        for (i in 1..absPosition.toInt()) {
            s *= (1 - scale)
        }
        val p = absPosition.toInt()
        val tranS = s - (s * scale) * (absPosition - p)
        Log.d(TAG, "transformPage: tranS = $tranS")
        page.scaleX = tranS
        page.scaleY = tranS
        //设置透明度  针对划入显示区域的item设置透明度从0-1
        if (p == count / 2) {
            page.alpha = 1- (absPosition - p)
        }
    }
}

3、布局文件和相关类

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:clipChildren="false"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <ImageView
        android:id="@+id/iv_pic"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:src="@drawable/profile_picture"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        />

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:text="3"
        android:textSize="62dp"
        android:textStyle="bold"
        android:background="#64CC9D"
        android:textColor="#FF0000"
        app:layout_constraintTop_toTopOf="@id/iv_pic"
        app:layout_constraintStart_toStartOf="@id/iv_pic"
        app:layout_constraintEnd_toEndOf="@id/iv_pic"
        app:layout_constraintBottom_toBottomOf="@id/iv_pic"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
class ImageAdapter(val context: Context): RecyclerView.Adapter<ImageAdapter.ImageHolder>() {


    class ImageHolder(val bind: ItemViewPagerBinding): RecyclerView.ViewHolder(bind.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageHolder {
        return ImageHolder(
            ItemViewPagerBinding.inflate(LayoutInflater.from(context), parent, false)
        )
    }

    override fun onBindViewHolder(holder: ImageHolder, position: Int) {
        val p = position % 10
        holder.bind.tvText.text = p.toString()
    }

    override fun getItemCount(): Int {
        return Int.MAX_VALUE
    }

}
class MainActivity : AppCompatActivity() {


    companion object {
        private const val TAG = "MainActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val bind = LayoutViewPagerBinding.inflate(layoutInflater)
        val adapter = ImageAdapter(this)
        bind.pager.adapter = adapter
        bind.pager.setPageTransformer(ImageTransformer())
//        bind.pager.setPageTransformer(Transformer())
        bind.pager.offscreenPageLimit = 3
        setContentView(bind.root)
    }
}