Android-自適用高度的ViewPager

需求

在項(xiàng)目中,我們常常遇到需要?jiǎng)討B(tài)調(diào)整 ViewPager 的高度,以適應(yīng)其內(nèi)容大小的需求。默認(rèn)情況下,ViewPager 的高度是固定的,無(wú)法根據(jù)每個(gè)頁(yè)面的內(nèi)容高度進(jìn)行調(diào)整。這會(huì)導(dǎo)致在內(nèi)容高度不一致時(shí),出現(xiàn)不必要的空白區(qū)域或者內(nèi)容被裁剪的情況。為了解決這個(gè)問(wèn)題,我們?cè)O(shè)計(jì)了一個(gè) AutoHeightViewPager,能夠根據(jù)當(dāng)前顯示頁(yè)面的內(nèi)容高度動(dòng)態(tài)調(diào)整自身的高度,保證內(nèi)容完整且沒(méi)有多余的空白。

效果

去哪兒效果:


640.gif

仿的效果:


640 (1).gif

實(shí)現(xiàn)思路

1. 動(dòng)態(tài)高度調(diào)整

首先,我們需要一個(gè)自定義的 ViewPager 類 AutoHeightViewPager,這個(gè)類可以根據(jù)當(dāng)前頁(yè)面的內(nèi)容高度來(lái)動(dòng)態(tài)調(diào)整自身的高度。通過(guò)重寫(xiě) onMeasure 方法,可以在滑動(dòng)過(guò)程中動(dòng)態(tài)計(jì)算頁(yè)面的高度并調(diào)整布局。

2. 監(jiān)聽(tīng)頁(yè)面滑動(dòng)事件

為了平滑過(guò)渡,我們需要監(jiān)聽(tīng)頁(yè)面的滑動(dòng)過(guò)程,并計(jì)算滑動(dòng)比例,將當(dāng)前頁(yè)面的高度與下一個(gè)頁(yè)面的高度按比例過(guò)渡,實(shí)現(xiàn)平滑過(guò)渡效果。

3. 自定義 Adapter

自定義的 PagerAdapter 必須實(shí)現(xiàn)一個(gè)接口 AutoHeightPager,用于獲取指定位置頁(yè)面的 View,這樣我們可以測(cè)量頁(yè)面內(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é)

通過(guò)自定義 ViewPager 的動(dòng)態(tài)高度適配功能,我們可以解決內(nèi)容高度不一致導(dǎo)致的布局問(wèn)題。這種方案可以適應(yīng)不同頁(yè)面內(nèi)容的高度變化,實(shí)現(xiàn)平滑的過(guò)渡效果,非常適用于動(dòng)態(tài)內(nèi)容展示的場(chǎng)景。
github:github.com/yixiaolunhui/AndroidXY

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容