ViewPager 自適應(yīng)高度 & requestLayout()

需求

需要根據(jù)每一個 Fragment 內(nèi)容自適應(yīng)高度

最初實現(xiàn)

確實可以根據(jù) 第一個Fragment 高度,自適應(yīng)。
但是也僅限于第一個。
切換頁面,所有的頁面高度都和 第一個Fragment 高度一樣。

//      自適應(yīng)高度(根據(jù)內(nèi)容區(qū)大小)
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var index = currentItem

        logError(currentItem.toString(),"currentItem")

        var height = 0
        var v = (adapter!!.instantiateItem(this, index) as Fragment).view
        if (v != null) {
            v.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
            height = v.measuredHeight
        }

        var heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)

        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

導(dǎo)致有的頁面顯示不全,有的頁面空白區(qū)域太大

分析原因

打印 log 發(fā)現(xiàn),在少于4個頁面的時候,切換頁面 onMeasure 不會在調(diào)用,
而 currentItem 返回值只有 0 ,也就是第一個頁面

解決

幾乎下意識的就想到,在 onPageScrolled方法中 調(diào)用 onMeasure 方法,但是不可行。

而后又下意識的在 onPageScrolled方法中 寫上了 invalidate() ,重繪界面!

當然沒有效果!invalidate() 是用來重繪界面的,也就是調(diào)用的 draw過程

需要在measure過程進行處理,也就是重新測量View自身大小和布局位置,
也就是 requestLayout()

    override fun onPageScrolled(position: Int, offset: Float, offsetPixels: Int) {
        super.onPageScrolled(position, offset, offsetPixels)
//        重新測量布局
        requestLayout()
    }

問題解決??!

附代碼

/**
 * <pre>
 *     author : jake
 *     time   : 2018/09/18
 *     function   :  控制是否可以左右滑動的viewpager  &  自適應(yīng)高度
 *     version: 1.0
 * </pre>
 */
class ViewPagerAutoHeight : ViewPagerSlide {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)


//      自適應(yīng)高度(根據(jù)內(nèi)容區(qū)大小)
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var index = currentItem

        logError(currentItem.toString(),"currentItem")

        var height = 0
        var v = (adapter!!.instantiateItem(this, index) as Fragment).view
        if (v != null) {
            v.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
            height = v.measuredHeight
        }
        var heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)

        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

    override fun onPageScrolled(position: Int, offset: Float, offsetPixels: Int) {
        super.onPageScrolled(position, offset, offsetPixels)
//        重新測量布局
        requestLayout()
    }
}

特殊情況

然而需求總是多變的,直接總結(jié)需求:
當ViewPage里的內(nèi)容區(qū)未撐到屏幕底部時,使其撐到屏幕底部;
當超出屏幕時,自適應(yīng)高度。

分析

因為ViewPage頭部的內(nèi)容區(qū)不是固定高度。
那么解決方案就只能在ViewPager的屬性上下手了。
只要判斷 ViewPager 的 bottom 與 屏幕寬度 比較大小就可以了(我是橫屏顯示,所以需要與屏幕寬度做對比,正常情況下需要與屏幕高度做對比)

當 bottom 小于 屏幕寬度,就賦值bottom為屏幕寬度,或者間接設(shè)置 ViewPager 的 height

操作

這種想法還是太天真了,因為 onMeasure 會被調(diào)用很多很多很多次!
而且值很不穩(wěn)定,切換頁面你就會看到內(nèi)容區(qū)跳來跳去,很有節(jié)奏!

部分Log值

上述log值,后4位分別是:屏幕寬度,bottom,height,top。

靈感

我忽然想到,既然值不穩(wěn)定,那么我來讓它穩(wěn)定!
用集合存儲所有height值,取最大的一個,這樣 取到的 height 會隨著 onMeasure 的調(diào)用,逐步增大或不變。

 //      自適應(yīng)高度(根據(jù)內(nèi)容區(qū)大?。?    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var index = currentItem
        var height = 0

        var v = (adapter!!.instantiateItem(this, index) as Fragment).view
        if (v != null) {
            v.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
            height = v.measuredHeight

            /**
             *  由于特殊情況,需要實現(xiàn) viewpager 從開始位置 至 底部占滿,超出屏幕才自適應(yīng)高度
             *
             *  因為子任務(wù)提交按鈕的位置,必須撐滿屏幕才行,否則就要放到外層類中,進行消息傳遞,更麻煩
             *
             *  通用情況,可以去掉
             */

            maxList.add(height)
            height = maxList.max()!!

            if (height < appWidth - top) {
                height = appWidth - top
                maxList.add(height)
            }
            // ------------------------- 結(jié)束 --------------------------------------
        }

        var heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)

        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

我們能看到的界面也正向我們期待的那樣,雖然會看到撐開布局的過程(逐步撐開,時間很短,可以接受),但是不會再忽大忽小的跳躍了。

因為已經(jīng)設(shè)置在切換界面時,會重新測量自身大小和布局位置,requestLayout()
所以在切換頁面的時候,清除集合數(shù)據(jù)

    override fun onPageScrolled(position: Int, offset: Float, offsetPixels: Int) {
        super.onPageScrolled(position, offset, offsetPixels)
//        重新測量布局
        maxList.clear()
        requestLayout()
    }

附代碼

/**
 * <pre>
 *     author : jake
 *     time   : 2018/09/18
 *     function   :  特殊需求 & 自適應(yīng)高度
 *     version: 1.0
 * </pre>
 */
class ViewPagerAutoHeight : ViewPagerSlide {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    private var maxList = ArrayList<Int>()

    //      自適應(yīng)高度(根據(jù)內(nèi)容區(qū)大?。?    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var index = currentItem
        var height = 0

        var v = (adapter!!.instantiateItem(this, index) as Fragment).view
        if (v != null) {
            v.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
            height = v.measuredHeight

            /**
             *  由于特殊情況,需要實現(xiàn) viewpager 從開始位置 至 底部占滿,超出屏幕才自適應(yīng)高度
             *
             *  因為子任務(wù)提交按鈕的位置,必須撐滿屏幕才行,否則就要放到外層類中,進行消息傳遞,更麻煩
             *
             *  通用情況,可以去掉
             */

            maxList.add(height)
            height = maxList.max()!!

            if (height < appWidth - top) {
                height = appWidth - top
                maxList.add(height)
            }
            // ------------------------- 結(jié)束 --------------------------------------
        }

        var heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)

        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

    override fun onPageScrolled(position: Int, offset: Float, offsetPixels: Int) {
        super.onPageScrolled(position, offset, offsetPixels)
//        重新測量布局
        maxList.clear()
        requestLayout()
    }

}

奇葩的bug,頁面閃退后再次進入當前頁面,高度會拉長(2018.11.14)

已解決,見《頁面閃退后自適應(yīng)高度的ViewPager高度會拉長 & Resources.getSystem().displayMetrics.widthPixels 不是一個固定值》


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

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

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