如何在不阻塞的情况下向 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()
}
}
我想在具有数千 (>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()
}
}