Android ListView 中的 TextView 在手动滚动之前无法测量正确的高度

Android TextView inside ListView does not measure the correct height until manually scrolling

我有一个充满多行 TextView 的 listView。每个 TextView 都有不同数量的文本。按下一个按钮后,用户将被带到另一个 Activity,在那里他们可以更改字体和字体大小。重新进入 Fragment 后,如果这些设置已更改,则会重置 listView 并更改 TextView 的尺寸。

我需要知道更改这些设置后视图中第一个 TextView 的测量高度。由于某种原因,测量后的测量高度最初是不同的。一旦我手动滚动列表,就会记录真实的身高测量值。

日志输出:

测量后:电视高度=2036

测量后:电视高度=2036

滚动后:电视高度 = 7950

最小代码:

class FragmentRead : Fragment() {
    private var firstVisiblePos = 0
    lateinit var adapterRead: AdapterRead
    lateinit var lvTextList: ListView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        lvTextList = view.findViewById(R.id.read_listview)
        setListView(lvTextList)


        lvTextList.setOnScrollListener(object : AbsListView.OnScrollListener {
            var offset = 0
            override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
                if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
                    offset = if(lvTextList.getChildAt(0) == null) 0 else lvTextList.getChildAt(0).top - lvTextList.paddingTop
                    println("After scroll: tv height = ${lvTextList[0].height}")
                }
            }

            override fun onScroll(view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
                firstVisiblePos = firstVisibleItem
            }
        })
    }
    /*=======================================================================================================*/

    fun setListView(lv: ListView) {
        adapterRead = AdapterRead(Data.getTextList(), context!!)
        lv.apply {this.adapter = adapterRead}
    }
    /*=======================================================================================================*/

    inline fun <T : View> T.afterMeasured(crossinline f: T.() -> Unit) {
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                if(measuredWidth > 0 && measuredHeight > 0) {
                    println("After measured: tv height = ${lvTextList[0].height}")
                    viewTreeObserver.removeOnGlobalLayoutListener(this)
                    f()
                }
            }
        })
    }
    /*=======================================================================================================*/
    override fun onStart() {
    if(Settings.settingsChanged) {
        setListView(lvTextList)
        lvTextList.afterMeasured {
                println("After measured: tv height = ${lvTextList[0].height}")
            }
        }
    }
}

我尝试过的:

我已尝试使用文本和布局参数设置 TextView 并按照此处说明读取高度 (Getting height of text view before rendering to layout),但结果相同。测得的高度比我滚动列表后要小很多

我还尝试使用 lvTextList.scrollBy(0,1) 以编程方式滚动列表,以触发滚动侦听器或在读取正确高度时触发的任何其他内容。

编辑:回到片段后我延迟了:

Handler().postDelayed({
println("tv height after delay = ${lvScriptureList[0].height}")}, 1000)

这会报告正确的高度。所以我的猜测是 OnGlobalLayoutListener 被提前调用了。有什么办法可以解决这个问题?

这是我的解决方案。我需要知道 TextView 高度的原因是因为在用户更改设置(例如字体、字体大小、行间距)后,TextView 的大小会发生变化。为了 return 到 TextView 之前所在的相同位置,我需要知道新测量的 TextView 的高度。然后我可以根据之前的位置去到同一个点(或者很近),然后根据新的高度重新计算。

因此在更改设置并重新加载片段后:

override fun onStart(){
    if(Settings.settingsChanged) {
        setListView(lvTextList)
        lvTextList.afterMeasured {
            lvTextList.post { lvTextList.setSelectionFromTop(readPos, 0) }
            Handler().postDelayed({
                val newOffset = getNewOffset() // Recalculates the new offset based on the last offset and the new TextView height
                lvTextList.post { lvTextList.setSelectionFromTop(readPos, newOffset) }
            }, 500)
        }
    }
}

出于某种原因,在安排延迟之前我必须先滚动到某个位置,所以我只是滚动到 TextView 的开头。

500 毫秒很愚蠢,只是一个估计值,但它有效。它实际上在我的 phone 上以 100ms 的值工作,但我想确保跨设备获得更好的成功机会。