1

我有一个充满多行 TextViews 的 listView。每个 TextView 都有不同数量的文本。按下按钮后,用户将被带到另一个 Activity,他们可以在其中更改字体和字体大小。在重新进入 Fragment 时,如果这些设置已更改,则列表视图将重置,并且 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}")
            }
        }
    }
}

我试过的:

我尝试使用文本和 layoutParams 设置 TextView 并按照此处的说明读取高度(在渲染到布局之前获取文本视图的高度),但结果是相同的。测量的高度比我滚动列表后要小得多。

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

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

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

这报告了正确的高度。所以我的猜测是 OnGlobalLayoutListener 被提前调用。有任何解决这个问题的方法吗?

4

1 回答 1

0

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

因此,在更改设置并重新加载 Fragment 后:

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 毫秒是愚蠢的,只是一个估计值,但它确实有效。它实际上在我的手机上以 100 毫秒的值工作,但我想确保跨设备成功的机会更大。

于 2020-11-13T22:43:15.110 回答