使用ViewPager2实现画廊效果,中间item突出,两侧渐小
效果展示:【静默状态和拖动状态】
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)
}
}