如何在不阻塞的情况下向 GridLayout 添加多个视图 UI?

How to add many views to GridLayout without blocking UI?

我想在具有数千 (>1.000) 个网格单元格的片段中显示网格布局。为了节省代码行,我想在创建片段时以编程方式添加网格单元。网格单元不需要做的只是每个单独显示某种颜色。

问题是,无论何时创建片段,UI 都会被阻塞几秒钟,因为必须首先设置网格布局。 我尝试使用 AsyncLayoutInflater 但这并没有真正解决我的问题,因为 xml 布局本身非常小并且在不阻塞 UI 的情况下膨胀。在 xml 布局膨胀后创建并向网格布局添加数千个视图是阻止 UI.

的原因

所以我的问题是,如何在后台将所有这些网格单元格添加到我的网格布局中而不阻塞 UI?

我的代码:

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.grid_fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setupGrid()
    }

    private fun setupGrid() {
        // Row and column count is currently set to 60
        for (yPos in 0 until gridLayout.rowCount) {
            for (xPos in 0 until gridLayout.columnCount) {
                val gridCell = ImageView(activity)
                val params = GridLayout.LayoutParams(GridLayout.spec(yPos, 1f), GridLayout.spec(xPos, 1f))
                gridCell.layoutParams = params
                gridLayout.addView(gridCell)
            }
        }
    }

我的 xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <GridLayout
        android:id="@+id/gridLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="240dp"
        android:layout_marginBottom="240dp"
        android:columnCount="60"
        android:rowCount="60"
        android:orientation="horizontal" />

</RelativeLayout>

屏幕截图应该是什么样子:

非常感谢!

好的,正如 Android 文档所说,永远不要在 UI 线程之外的任何视图上调用任何方法或构造函数,因为它不是线程安全的。它可能会编译并且实际上 运行 但使用它是不安全的。

我想出了一个 solution/workaround,它实际上只会延迟 UI 块,用户更有可能没有注意到它。这可能只适用于我的特定场景,因为我仅在建立网络连接后才用数千个视图填充我的网格。填充视图仍然会阻止 UI 但用户可能不会注意到它。下面描述了如何减少阻塞 UI 时间的小调整。

关键在于使用 OnLayoutChangedListener。将所有视图添加到 gridLayout 后,我​​调用 gridLayout.addOnChangeListener 并实施侦听器来处理网格单元格的布局参数。

代码如下:

fun configureGridLayout(gridHeight: Int, gridWidth: Int) {
        println("Setting grid dimensions to: ${gridHeight}x${gridWidth}")
        runOnUiThread {
            gridLayout.rowCount = gridHeight
            gridLayout.columnCount = gridWidth
            for (g in 0 until gridHeight * gridWidth) {
                val gridCell = View(context!!)
                gridLayout.addView(gridCell)
            }
            gridLayout.addOnLayoutChangeListener(LayoutChangeListener(gridLayout, this))
        }
    }



 // This callback is fired when fragment was completely rendered in order to reduce UI blocking time
class LayoutChangeListener(private val gridLayout: GridLayout, private val gridLayoutConfiguredListener: GridLayoutConfiguredListener) : View.OnLayoutChangeListener {

    override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
        v?.removeOnLayoutChangeListener(this)
        val gridRowCount = gridLayout.rowCount
        val gridColumnCount = gridLayout.columnCount
        val gridHeight = gridLayout.height
        val gridWidth = gridLayout.width
        val margin = 1
        val h = gridHeight / gridRowCount
        val w = gridWidth / gridColumnCount

        for (yPos in 0 until gridRowCount) {
            for (xPos in 0 until gridColumnCount) {
                val params = GridLayout.LayoutParams()
                params.height = h - 2 * margin
                params.width = w - 2 * margin
                params.setMargins(margin, margin, margin, margin)
                // Use post to get rid of "requestView() was called twice" errors
                gridLayout.getChildAt(yPos * gridColumnCount + xPos).post {
                    gridLayout.getChildAt(yPos * gridColumnCount + xPos).layoutParams = params
                }
            }
        }
        gridLayoutConfiguredListener.onGridLayoutConfigured()
    }
}