需求
需要根據(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值,后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 不是一個固定值》
