android 了解Compose声明性逻辑

6ovsh4lw  于 2022-12-09  发布在  Android
关注(0)|答案(3)|浏览(136)

我刚开始接触Compose和声明式编程,我正在努力理解它。为了学习,在阅读教程和观看课程之后,现在我正在创建我的第一个应用程序。
我正在创建一个使用compose multiplatform的compose桌面应用程序,它可以给予你从电脑中选择一个文件夹,并显示该文件夹中的所有文件。我正在启动一个JFileChooser来选择一个文件夹。当它被选中时,状态变量被改变并且Box被填充以表示该文件夹内的文件名的文本。这些名称是通过使用JFileChooser返回的路径的函数获得的。
这个应用程序有两个奇怪的行为。首先是因为这个屏幕有一个TextField,如果我在里面写东西,充满文本的Box似乎被重新绘制,再次调用搜索文件的函数(这些文件可能会导致应用程序变慢)。
第二个奇怪的行为是,如果我再次打开JFileChooser来更改文件夹,它会正确地重绘Box,并获得该文件夹的文件名,但如果我选择之前选择的同一个文件夹,则不会重绘Box,如果该文件夹中的文件被更改,则会出现问题。
有人能帮助我理解Compose的这些问题吗?我认为这两个问题都与声明性组合逻辑有关,但不明白哪里出了问题。谢谢。
这是显示JFileChooser的按钮

var listRomsState by remember { mutableStateOf(false) }
Button(onClick = {
    folderChosenPath = folderChooser()
    if (folderChosenPath != "")
        listRomsState = true
}) {
    Text(text = "List roms")
}

这是显示JFileChooser的函数

fun folderChooser(): String {
    UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
    val f = JFileChooser()
    f.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY

    val result: Int = f.showSaveDialog(null)
    if (result == JFileChooser.APPROVE_OPTION) {
        return f.selectedFile.path
    } else {
        return ""
    }
}

显示文件选择器的按钮下方是文件名列表:

if (listRomsState) {
    RomsList(File(folderChosenPath))
}

下面是RomsList函数:

@Composable
fun RomsList(folder: File) {
    Box (
        modifier = Modifier.fillMaxSize().border(1.dp, Color.LightGray)
    ) {
        LazyColumn(
            Modifier.fillMaxSize().padding(top = 5.dp, end = 8.dp)
        ){
            var romsList = getRomsFromFolder(folder)
            items(romsList.size) {
                Box (
                    modifier = Modifier.padding(5.dp, 0.dp, 5.dp, 0.dp).fillMaxWidth(),
                    contentAlignment = Alignment.CenterStart
                ) {
                    Row (horizontalArrangement = Arrangement.spacedBy(5.dp)){
                        Text(text = "" + (it+1), modifier = Modifier.weight(0.6f).background(color = Color(0, 0, 0, 20)))
                        Text(text = romsList[it].title, modifier = Modifier.weight(9.4f).background(color = Color(0, 0, 0, 20)))
                    }
                }
                Spacer(modifier = Modifier.height(5.dp))
            }
        }
    }
}

这个函数可以递归地获取文件夹中的所有文件名:

fun getRomsFromFolder(curDir: File? = File(".")): MutableList<Rom> {
    var romsList = mutableListOf<Rom>()

    val filesList = curDir?.listFiles()
    filesList?.let {
        for (f in filesList) {
            if (f.isDirectory) romsList.addAll(getRomsFromFolder(f))
            if (f.isFile) {
                romsList.add(Rom(f.name))
            }
        }
    }

    return romsList
}
crcmnpdw

crcmnpdw1#

我创建了一个非常简单的可组合对象,它与基于我们讨论的组合结构完全相同。
请考虑以下代码:

@Composable
fun MyTvScreen() {

    Log.e("MyComposableSample", "MyTvScreen Recomposed")

    var fileName by remember {
        mutableStateOf("")
    }
    val someFile = File("")

    Column {

        TextField(
            value = fileName,
            onValueChange = {
                fileName = it
            }
        )

        RomsList(file = someFile)
    }
}

@Composable
fun RomsList(file: File) {
    Log.e("MyComposableSample", "RomsList Recomposed $file")
}

当您运行此命令时,以及当您在TextField中键入任何内容时,当您在文本字段中键入内容时,这两个可组合程序都将重新组合并生成此日志输出

E/MyComposableSample: MyTvScreen Recomposed   // initial composition 
E/MyComposableSample: RomsList Recomposed     // initial composition

// succeeding re-compositions when you typed something in the TextField
E/MyComposableSample: MyTvScreen Recomposed
E/MyComposableSample: RomsList Recomposed 
E/MyComposableSample: MyTvScreen Recomposed
E/MyComposableSample: RomsList Recomposed

从这个article中,我运行了一个命令,发现java.io.File不是一个稳定的类型。

restartable fun RomsList( 
    unstable file: File 
)

现在我们知道File不是@Stable类型,并且我们无法控制它的API,我将它 Package 在一个自定义数据类中,如下所示,并修改了调用站点

@Stable
data class FileWrapper(
    val file: File
)

因此,使用FileWrapper修改上面的所有代码。

@Composable
fun MyTvScreen() {

    ...
    val someFile = FileWrapper(File(""))

    Column {

        TextField(
           ...
        )

        RomsList(fileWrapper = someFile)
    }
}

@Composable
fun RomsList(fileWrapper: FileWrapper) {
    Log.e("MyComposableSample", "RomsList Recomposed ${fileWrapper.file}")
}

生成以下日志输出。

E/MyComposableSample: MyTvScreen Recomposed // initial composition
E/MyComposableSample: RomsList Recomposed   // initial composition

// succeeding re-compositions when you typed something in the TextField
E/MyComposableSample: MyTvScreen Recomposed
E/MyComposableSample: MyTvScreen Recomposed
E/MyComposableSample: MyTvScreen Recomposed
E/MyComposableSample: MyTvScreen Recomposed
E/MyComposableSample: MyTvScreen Recomposed

运行gradle命令后,报告如下:RomsList现在是skippable,带有一个稳定的参数,因此当其父级可组合重新组合时,将跳过RomsList

restartable skippable fun RomsList( 
  stable fileWrapper: FileWrapper 
)

对于您的第二个问题,您是否介意尝试将mutableList替换为mutableStateList()?(这将创建SnapshotStateList的示例),这样,对列表的任何更改都将保证对读取它的可组合对象的更新

fun getRomsFromFolder(curDir: File? = File(".")): MutableList<Rom> {
    var romsList = mutableStateListOf<Rom>() // here
...
7lrncoxx

7lrncoxx2#

你需要习惯的重要机制是重组。我不知道它在ComposeMultiplatform中是如何工作的,但在android中重组依赖于状态的变化。当可组合函数包含某种状态时,它会自动监听它的变化,并在突变时进行重组。
在重新组合期间,重新组合的可组合对象的UI元素将使用新值再次绘制,以表示实际状态。
所以,解释你的奇怪行为:
这个应用程序有两个奇怪的行为。首先是因为屏幕上有一个文本字段,如果我在里面写东西,充满文本的框似乎会被重新绘制,再次调用搜索文件的函数(这些文件可能会导致应用程序变慢)。
发生这种情况是因为你改变了状态--文本字段的文本值。所以重组发生了。解决方案是移动所有的逻辑,这确实需要被再次调用以分离可组合的。解释是现在here
第二个奇怪的行为是,如果我再次打开JFileChooser来更改文件夹,它会正确地重画Box,并获得该文件夹的文件名,但如果我选择先前选择的同一个文件夹,则不会重画Box,如果该文件夹中的文件被更改,则会出现问题。
当需要重新组合但没有发生时就是这种情况。发生这种情况是因为可组合的RomsList不包含文件夹状态,因此不会在文件夹更改时自动重新组合。
您可能不应该将folder作为一个简单的参数传递,而应该将其作为一个状态来记忆。

val folderState by remember { mutableStateOf(folder) }

然而,由于文件夹是从另一个函数进入可组合对象的,因此解决方案之一是在调用函数中创建这样的状态,并将该函数标记为@Composable

6jjcrrmo

6jjcrrmo3#

最后我在Steyrix,z.y和其他一些人的帮助下发现了一些东西。
首先,正如这里所提到的,https://developer.android.com/jetpack/compose/mental-model#recomposition可组合函数只有在其参数被更改时才执行其代码,因此这就是问题2的原因。
另外,这两个问题的主要问题是,我执行的逻辑在错误的位置检索文件夹中的所有文件。它是在一个可组合函数中执行的,这是一个问题,因为每次重新组合时都会执行它。因此,在收到文件选择器的结果后,将逻辑移到onclick解决了这两个问题。
而且,现在我明白的事情多了。
谢谢你们!

相关问题