需求
在項(xiàng)目中,我們常常遇到需要?jiǎng)討B(tài)調(diào)整 ViewPager 的高度,以適應(yīng)其內(nèi)容大小的需求拴竹。默認(rèn)情況下唐础,ViewPager 的高度是固定的箱歧,無法根據(jù)每個(gè)頁面的內(nèi)容高度進(jìn)行調(diào)整矾飞。這會(huì)導(dǎo)致在內(nèi)容高度不一致時(shí),出現(xiàn)不必要的空白區(qū)域或者內(nèi)容被裁剪的情況呀邢。為了解決這個(gè)問題洒沦,我們設(shè)計(jì)了一個(gè) AutoHeightViewPager,能夠根據(jù)當(dāng)前顯示頁面的內(nèi)容高度動(dòng)態(tài)調(diào)整自身的高度价淌,保證內(nèi)容完整且沒有多余的空白申眼。
效果
去哪兒效果:
仿的效果:
實(shí)現(xiàn)思路
1. 動(dòng)態(tài)高度調(diào)整
首先,我們需要一個(gè)自定義的 ViewPager 類 AutoHeightViewPager蝉衣,這個(gè)類可以根據(jù)當(dāng)前頁面的內(nèi)容高度來動(dòng)態(tài)調(diào)整自身的高度括尸。通過重寫 onMeasure 方法,可以在滑動(dòng)過程中動(dòng)態(tài)計(jì)算頁面的高度并調(diào)整布局买乃。
2. 監(jiān)聽頁面滑動(dòng)事件
為了平滑過渡姻氨,我們需要監(jiān)聽頁面的滑動(dòng)過程,并計(jì)算滑動(dòng)比例剪验,將當(dāng)前頁面的高度與下一個(gè)頁面的高度按比例過渡肴焊,實(shí)現(xiàn)平滑過渡效果。
3. 自定義 Adapter
自定義的 PagerAdapter 必須實(shí)現(xiàn)一個(gè)接口 AutoHeightPager功戚,用于獲取指定位置頁面的 View娶眷,這樣我們可以測量頁面內(nèi)容的高度。
實(shí)現(xiàn)代碼
1. AutoHeightViewPager
package com.yxlh.androidxy.demo.ui.viewpager.vp
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
interface AutoHeightPager {
fun getView(position: Int): View?
}
class AutoHeightViewPager @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ViewPager(context, attrs) {
private var lastWidthMeasureSpec: Int = 0
private var currentHeight: Int = 0
private var lastPosition: Int = 0
private var isScrolling: Boolean = false
init {
addOnPageChangeListener(object : SimpleOnPageChangeListener() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
if (positionOffset == 0f) {
isScrolling = false
requestLayout()
return
}
val srcPosition = if (position >= lastPosition) position else position + 1
val destPosition = if (position >= lastPosition) position + 1 else position
val srcHeight = getViewHeight(srcPosition)
val destHeight = getViewHeight(destPosition)
currentHeight = (srcHeight + (destHeight - srcHeight) *
if (position >= lastPosition) positionOffset else 1 - positionOffset).toInt()
isScrolling = true
requestLayout()
}
override fun onPageScrollStateChanged(state: Int) {
if (state == SCROLL_STATE_IDLE) {
lastPosition = currentItem
}
}
})
}
override fun setAdapter(adapter: PagerAdapter?) {
require(adapter == null || adapter is AutoHeightPager) { "PagerAdapter must implement AutoHeightPager." }
super.setAdapter(adapter)
}
private fun getViewHeight(position: Int): Int {
val adapter = adapter as? AutoHeightPager ?: return 0
return run {
val view = adapter.getView(position) ?: return 0
view.measure(
lastWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
)
view.measuredHeight
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
lastWidthMeasureSpec = widthMeasureSpec
var heightSpec = heightMeasureSpec
if (isScrolling) {
heightSpec = MeasureSpec.makeMeasureSpec(currentHeight, MeasureSpec.EXACTLY)
} else {
getViewHeight(currentItem).takeIf { it > 0 }?.let {
heightSpec = MeasureSpec.makeMeasureSpec(it, MeasureSpec.EXACTLY)
}
}
super.onMeasure(widthMeasureSpec, heightSpec)
}
}
2. AutoHeightPagerAdapter
package com.yxlh.androidxy.demo.ui.viewpager
import android.view.View
import android.view.ViewGroup
import androidx.viewpager.widget.PagerAdapter
import com.yxlh.androidxy.demo.ui.viewpager.vp.AutoHeightPager
class AutoHeightPagerAdapter : PagerAdapter(), AutoHeightPager {
private val viewList = mutableListOf<View>()
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val view = viewList[position]
val parent = view.parent as? ViewGroup
parent?.removeView(view)
container.addView(view)
return view
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as View)
}
fun setViews(views: List<View>) {
viewList.clear()
viewList.addAll(views)
notifyDataSetChanged()
}
override fun getView(position: Int): View? {
return viewList.getOrNull(position)
}
override fun getCount(): Int {
return viewList.size
}
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view == `object`
}
}
3. Activity 代碼
package com.yxlh.androidxy.demo.ui.viewpager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.yxlh.androidxy.R
import com.yxlh.androidxy.demo.ui.viewpager.vp.AutoHeightViewPager
class VpActivity : AppCompatActivity() {
private var mAutoHeightVp: AutoHeightViewPager? = null
private var mAdapter: AutoHeightPagerAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_vp)
val viewList: MutableList<View> = ArrayList()
viewList.add(LayoutInflater.from(this).inflate(R.layout.view_demo_1, null))
viewList.add(LayoutInflater.from(this).inflate(R.layout.view_demo_2, null))
mAutoHeightVp = findViewById(R.id.viewpager)
mAutoHeightVp?.setAdapter(AutoHeightPagerAdapter().also { mAdapter = it })
mAdapter?.setViews(viewList)
mAutoHeightVp?.setCurrentItem(1)
}
}
4. 布局 XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.yxlh.androidxy.demo.ui.viewpager.vp.AutoHeightViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitXY"
android:src="@drawable/vp_content" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
總結(jié)
通過自定義 ViewPager 的動(dòng)態(tài)高度適配功能啸臀,我們可以解決內(nèi)容高度不一致導(dǎo)致的布局問題届宠。這種方案可以適應(yīng)不同頁面內(nèi)容的高度變化,實(shí)現(xiàn)平滑的過渡效果乘粒,非常適用于動(dòng)態(tài)內(nèi)容展示的場景豌注。
github:github.com/yixiaolunhui/AndroidXY