Compose for Desktop中JList项内ComposePanel持续重绘问题排查
问题
在Compose/Swing互操作项目中,给SwingPanel添加了JList,JList使用渲染器渲染ComposePanel,但启动后JList里的ComposePanel内容持续重绘,控制台不断输出"recompose"。
简化后的核心代码如下:
主Compose代码
val fakeData = mutableListOf<String>().apply { repeat(5) { add("test $it") } } @Composable @Preview fun App() { MaterialTheme { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Column( modifier = Modifier.padding(top = 80.dp, bottom = 20.dp) ) { Text("Recompose Test") Spacer(modifier = Modifier.height(20.dp)) SwingPanel( background = Color.Red, modifier = Modifier.size(500.dp), factory = { JPanel().apply { layout = BoxLayout(this, BoxLayout.Y_AXIS) alignmentY = Component.TOP_ALIGNMENT val composeJList = JList(fakeData.toTypedArray()).apply { cellRenderer = ComposeRenderer() } val scrollPane = JScrollPane(composeJList) add(scrollPane) isVisible = true } } ) } } } } fun main() = application { Window(onCloseRequest = ::exitApplication) { App() } }
ListCellRenderer代码
class ComposeRenderer : JPanel(), ListCellRenderer<String> { private val composePanel = ComposePanel() init { layout = BoxLayout(this, BoxLayout.Y_AXIS) add(composePanel) } override fun getListCellRendererComponent( list: JList<out String>, text: String, index: Int, isSelected: Boolean, cellHasFocus: Boolean ): Component { print("recompose") composePanel.setContent { Text(text) } return this } }
原因分析
这是代码逻辑问题,不是Bug。核心问题在于getListCellRendererComponent的实现:
- 每次Swing需要渲染列表项时,都会调用该方法
- 方法内每次都执行
composePanel.setContent更新Compose内容,而Compose内容更新会触发Swing组件的重绘 - 重绘操作又会再次触发
getListCellRendererComponent调用,形成无限循环,导致控制台持续输出"recompose"
解决方案
通过缓存当前渲染状态,只在内容或状态发生变化时才更新ComposePanel的内容,打破循环。
修改后的ComposeRenderer代码:
class ComposeRenderer : JPanel(), ListCellRenderer<String> { private val composePanel = ComposePanel() private var currentText: String? = null private var currentIsSelected: Boolean = false private var currentHasFocus: Boolean = false init { layout = BoxLayout(this, BoxLayout.Y_AXIS) add(composePanel) } override fun getListCellRendererComponent( list: JList<out String>, text: String, index: Int, isSelected: Boolean, cellHasFocus: Boolean ): Component { // 仅当渲染状态变化时更新Compose内容 if (text != currentText || isSelected != currentIsSelected || cellHasFocus != currentHasFocus) { currentText = text currentIsSelected = isSelected currentHasFocus = cellHasFocus print("recompose") composePanel.setContent { Text( text, color = if (isSelected) Color.White else Color.Black, modifier = Modifier.background(if (isSelected) Color.Blue else Color.Transparent) ) } } // 同步Swing原生选中样式(可选,保证和原生JList视觉一致) background = if (isSelected) list.selectionBackground else list.background foreground = if (isSelected) list.selectionForeground else list.foreground return this } }
另外需要注意:JList的渲染器是复用实例,一个渲染器会被用来绘制多个列表项,因此必须确保每次调用时正确更新状态,同时避免无意义的Compose内容更新。
内容的提问来源于stack exchange,提问作者Eynnzerr
相关产品推荐
相关产品推荐

