M8Test Help

附带效应

Jetpack Compose 的附带效应(Side Effects)是指在可组合函数(Composable Function)的执行过程中,与 UI 组合本身之外的世界进行的任何交互。可组合函数应该是一个纯函数,即对于相同的输入,总是产生相同的输出,并且没有其他可观察到的影响。然而,在实际应用中,我们经常需要执行一些非纯操作,例如:

  • 更新外部状态(如 ViewModel 中的数据)。

  • 启动一个协程来执行网络请求或数据库操作。

  • 注册或注销生命周期观察者。

  • 显示 Toast 或 Snackbar。

这些与 UI 组合过程“并列”发生的非纯操作就被称为附带效应。Compose 提供了一系列 API 来管理这些附带效应,确保它们在正确的时机执行,并且能够与 Composable 的生命周期保持同步,避免内存泄漏或其他不一致的问题。 主要的附带效应 API 包括 SideEffect、LaunchedEffect 和 DisposableEffect。

SideEffect

SideEffect 用于安排一个 effect 在当前的组合成功完成并应用更改后运行。它适用于那些不需要在 Composable 离开组合时进行清理的简单附带效应。

  • 用途: 当 Composable 成功重组并应用更改后,你需要执行一个操作,且该操作不涉及任何需要清理的资源。例如,记录分析事件、更新外部的非 Compose 状态,或者修改一个不被 Compose 观察的对象。

  • 特性:

    • effect 会在 每次 成功的重组后运行。

    • 不提供清理机制。

    • 始终在组合的 apply 调度器上运行,并且与 Composable 树的更改、 RememberObserver 回调等操作不会并发执行。

    • 总是会在 RememberObserver 事件回调之后运行。

  • 何时使用: 当你只需要在每次重组后“做点什么”,而不需要担心资源的分配和释放时。

import com.m8test.script.GlobalVariables.* fun side1Run() { // 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView.create { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 Column { // 设置 Column 中 content 插槽的内容 setContent { // 1. 创建一个可变状态,用于保存计数器的值 val count = mutableStateOf(0) // 添加一个文件组件到 Column (垂直布局)中 Text { // 2. 在组件的某个属性中使用该状态的值 setText(count.getValue().toString()) // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) trackSingleState(count) // 每当 count 发生变化并导致重组时,SideEffect 都会在重组完成后执行 SideEffect { // 这里是配置的代码,只会执行一次 _console.log("config side effect: ", count.getValue()) setEffect { // 这个代码会在每次 Text 重组时执行 _console.log("side effect count: ", count.getValue()) } } } // 添加一个文本按钮到 Column (垂直布局)中 TextButton { // 设置文本按钮的content插槽内容 setContent { // 在插槽中添加一个文本 Text { setText("点击我") } } // 设置按钮点击事件 setOnClick { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) } } } } } // 启动一个Activity用于显示脚本界面 _activity.start() } //-m8test-remove side1Run();
// 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create { slot -> // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column { column -> // 设置 Column 中 content 插槽的内容 column.setContent { columnSlot -> // 1. 创建一个可变状态,用于保存计数器的值 def count = slot.mutableStateOf(0) // 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text { text -> // 2. 在组件的某个属性中使用该状态的值 text.setText(count.getValue().toString()) // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count) // 每当 count 发生变化并导致重组时,SideEffect 都会在重组完成后执行 text.SideEffect { effect -> // 这里是配置的代码,只会执行一次 $console.log("config side effect: ", count.getValue()) effect.setEffect { // 这个代码会在每次 Text 重组时执行 $console.log("side effect count: ", count.getValue()) } } } // 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton { button -> // 设置文本按钮的content插槽内容 button.setContent { buttonSlot -> // 在插槽中添加一个文本 buttonSlot.Text { text -> text.setText("点击我") } } // 设置按钮点击事件 button.setOnClick { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) } } } } } // 启动一个Activity用于显示脚本界面 $activity.start()
// 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create(slot => { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column(column => { // 设置 Column 中 content 插槽的内容 column.setContent(columnSlot => { // 1. 创建一个可变状态,用于保存计数器的值 const count = slot.mutableStateOf(0); // 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text(text => { // 2. 在组件的某个属性中使用该状态的值 text.setText(count.getValue().toString()); // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count); // 每当 count 发生变化并导致重组时,SideEffect 都会在重组完成后执行 text.SideEffect(effect => { // 这里是配置的代码,只会执行一次 $console.log("config side effect: ", count.getValue()); effect.setEffect(() => { // 这个代码会在每次 Text 重组时执行 $console.log("side effect count: ", count.getValue()); }); }); }); // 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton(button => { // 设置文本按钮的content插槽内容 button.setContent(buttonSlot => { // 在插槽中添加一个文本 buttonSlot.Text(text => { text.setText("点击我"); }); }); // 设置按钮点击事件 button.setOnClick(() => { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1); }); }); }); }); }); // 启动一个Activity用于显示脚本界面 $activity.start();
-- 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView:create(function(slot) -- 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 -- 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot:Column(function(column) -- 设置 Column 中 content 插槽的内容 column:setContent(function(columnSlot) -- 1. 创建一个可变状态,用于保存计数器的值 local count = slot:mutableStateOf(0) -- 添加一个文件组件到 Column (垂直布局)中 columnSlot:Text(function(text) -- 2. 在组件的某个属性中使用该状态的值 text:setText(tostring(count:getValue())) -- 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text:trackSingleState(count) -- 每当 count 发生变化并导致重组时,SideEffect 都会在重组完成后执行 text:SideEffect(function(effect) -- 这里是配置的代码,只会执行一次 _console:log("config side effect: ", count:getValue()) effect:setEffect(function() -- 这个代码会在每次 Text 重组时执行 _console:log("side effect count: ", count:getValue()) end) end) end) -- 添加一个文本按钮到 Column (垂直布局)中 columnSlot:TextButton(function(button) -- 设置文本按钮的content插槽内容 button:setContent(function(buttonSlot) -- 在插槽中添加一个文本 buttonSlot:Text(function(text) text:setText("点击我") end) end) -- 设置按钮点击事件 button:setOnClick(function() -- 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count:setValue(count:getValue() + 1) end) end) end) end) end) -- 启动一个Activity用于显示脚本界面 _activity:start()
<?php /** @var m8test_java\com\m8test\script\core\api\ui\compose\ComposeView $composeView */ global $composeView; /** @var m8test_java\com\m8test\script\core\api\ui\Activity $activity */ global $activity; /** @var m8test_java\com\m8test\script\core\api\console\Console $console */ global $console; // 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView->create(function ($slot) use ($console) { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 $slot->Column(function ($column) use ($slot, $console) { // 设置 Column 中 content 插槽的内容 $column->setContent(function ($columnSlot) use ($slot, $console) { // 1. 创建一个可变状态,用于保存计数器的值 $count = $slot->mutableStateOf(0); // 添加一个文件组件到 Column (垂直布局)中 $columnSlot->Text(function ($text) use ($count, $console) { // 2. 在组件的某个属性中使用该状态的值 $text->setText((string)$count->getValue()); // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) $text->trackSingleState($count); // 每当 count 发生变化并导致重组时,SideEffect 都会在重组完成后执行 $text->SideEffect(function ($effect) use ($count, $console) { // 这里是配置的代码,只会执行一次 $console->log(javaString("config side effect: "), $count->getValue()); $effect->setEffect(function () use ($count, $console) { // 这个代码会在每次 Text 重组时执行 $console->log(javaString("side effect count: "), $count->getValue()); }); }); }); // 添加一个文本按钮到 Column (垂直布局)中 $columnSlot->TextButton(function ($button) use ($count) { // 设置文本按钮的content插槽内容 $button->setContent(function ($buttonSlot) { // 在插槽中添加一个文本 $buttonSlot->Text(function ($text) { $text->setText(javaString("点击我")); }); }); // 设置按钮点击事件 $button->setOnClick(function () use ($count) { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState $count->setValue($count->getValue() + 1); }); }); }); }); }); // 启动一个Activity用于显示脚本界面 $activity->start();
# 导入全局变量,$console -> _console # 导入全局变量,$activity -> _activity from m8test_java.com.m8test.script.GlobalVariables import _activity # 导入全局变量,$composeView -> _composeView from m8test_java.com.m8test.script.GlobalVariables import _composeView from m8test_java.com.m8test.script.GlobalVariables import _console # 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView.create(lambda slot: # 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 # 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column(lambda column: # 设置 Column 中 content 插槽的内容 # 使用元组和海象表达式来在lambda中执行多个语句 column.setContent(lambda columnSlot: ( # 1. 创建一个可变状态,用于保存计数器的值 (count := slot.mutableStateOf(0)), # 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text(lambda text: ( # 2. 在组件的某个属性中使用该状态的值 text.setText(str(count.getValue())), # 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count), # 每当 count 发生变化并导致重组时,SideEffect 都会在重组完成后执行 text.SideEffect(lambda effect: ( # 这里是配置的代码,只会执行一次 _console.log("config side effect: ", count.getValue()), effect.setEffect(lambda: # 这个代码会在每次 Text 重组时执行 _console.log("side effect count: ", count.getValue()) ) )) )), # 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton(lambda button: ( # 设置文本按钮的content插槽内容 button.setContent(lambda buttonSlot: # 在插槽中添加一个文本 buttonSlot.Text(lambda text: text.setText("点击我") ) ), # 设置按钮点击事件 button.setOnClick(lambda: # 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) ) )) )) ) ) # 启动一个Activity用于显示脚本界面 _activity.start()
# encoding: utf-8 # 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create do |slot| # 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 # 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column do |column| # 设置 Column 中 content 插槽的内容 column.setContent do |columnSlot| # 1. 创建一个可变状态,用于保存计数器的值 count = slot.mutableStateOf(0) # 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text do |text| # 2. 在组件的某个属性中使用该状态的值 text.setText(count.getValue().to_s) # 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count) # 每当 count 发生变化并导致重组时,SideEffect 都会在重组完成后执行 text.SideEffect do |effect| # 这里是配置的代码,只会执行一次 $console.log("config side effect: ", count.getValue()) effect.setEffect do # 这个代码会在每次 Text 重组时执行 $console.log("side effect count: ", count.getValue()) end end end # 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton do |button| # 设置文本按钮的content插槽内容 button.setContent do |buttonSlot| # 在插槽中添加一个文本 buttonSlot.Text do |text| text.setText("点击我") end end # 设置按钮点击事件 button.setOnClick do # 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) end end end end end # 启动一个Activity用于显示脚本界面 $activity.start()

DisposableEffect

DisposableEffect 用于管理那些需要初始化并在 Composable 离开组合或其 key 发生变化时进行清理的附带效应。它保证了每一次 effect 都会有一个对应的 onDispose 调用。

  • 用途: 订阅外部数据源(如观察者模式、Android 的 BroadcastReceiver),启动/停止服务,注册/注销回调,或任何涉及资源分配和释放的操作。

  • 特性:

    • 接受一个或多个 key 参数。当这些 key 中的任何一个发生变化时,Compose 会首先执行前一个效应的清理逻辑 (onDispose) ,然后重新执行新的 effect 块。

    • 当 Composable 离开组合时,会执行 onDispose 块。

    • effect 块必须以 onDispose 子句作为其最终语句。

    • effectonDispose 都会在组合的 apply 调度器上运行,并且不会与其他相关操作并发执行。

  • 何时使用: 当你需要与 Composable 外部的生命周期敏感资源交互时,例如订阅一个流、注册一个监听器等。

import com.m8test.script.GlobalVariables.* fun side1Run() { // 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView.create { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 Column { // 设置 Column 中 content 插槽的内容 setContent { // 1. 创建一个可变状态,用于保存计数器的值 val count = mutableStateOf(0) // 添加一个文件组件到 Column (垂直布局)中 Text { // 2. 在组件的某个属性中使用该状态的值 setText(count.getValue().toString()) // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) trackSingleState(count) DisposableEffect { // 当 count 的值改变时会重新执行附带效应 setKeys(_iterables.mutableListOf(count) as List<com.m8test.script.core.api.ui.compose.state.MutableState<Any?>>) // 这个日志只会打印一次 _console.log("config disposable effect count: ", count.getValue()) setEffect { // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合或者 count 值改变时执行 _console.log("disposable effect enter or count value change: ", count.getValue()) setOnDispose { // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合或者 count 值改变时执行 _console.log("disposable effect leave or count value change: ", count.getValue()) } } } DisposableEffect { // 这个日志只会打印一次 _console.log("config disposable effect without keys") setEffect { // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合时执行 _console.log("disposable effect enter without keys") setOnDispose { // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合时执行 _console.log("disposable effect leave without keys") } } } } // 添加一个文本按钮到 Column (垂直布局)中 TextButton { // 设置文本按钮的content插槽内容 setContent { // 在插槽中添加一个文本 Text { setText("点击我") } } // 设置按钮点击事件 setOnClick { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) } } } } } // 启动一个Activity用于显示脚本界面 _activity.start() } //-m8test-remove side1Run();
// 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create { slot -> // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column { column -> // 设置 Column 中 content 插槽的内容 column.setContent { columnSlot -> // 1. 创建一个可变状态,用于保存计数器的值 def count = slot.mutableStateOf(0) // 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text { text -> // 2. 在组件的某个属性中使用该状态的值 text.setText(count.getValue().toString()) // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count) text.DisposableEffect { effect -> // 当 count 的值改变时会重新执行附带效应 effect.setKeys($iterables.mutableListOf(count)) // 这个日志只会打印一次 $console.log("config disposable effect count: ", count.getValue()) effect.setEffect { // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合或者 count 值改变时执行 $console.log("disposable effect enter or count value change: ", count.getValue()) it.setOnDispose { // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合或者 count 值改变时执行 $console.log("disposable effect leave or count value change: ", count.getValue()) } } } text.DisposableEffect { effect -> // 这个日志只会打印一次 $console.log("config disposable effect without keys") effect.setEffect { // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合时执行 $console.log("disposable effect enter without keys") it.setOnDispose { // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合时执行 $console.log("disposable effect leave without keys") } } } } // 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton { button -> // 设置文本按钮的content插槽内容 button.setContent { buttonSlot -> // 在插槽中添加一个文本 buttonSlot.Text { text -> text.setText("点击我") } } // 设置按钮点击事件 button.setOnClick { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) } } } } } // 启动一个Activity用于显示脚本界面 $activity.start()
// 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create(slot => { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column(column => { // 设置 Column 中 content 插槽的内容 column.setContent(columnSlot => { // 1. 创建一个可变状态,用于保存计数器的值 const count = slot.mutableStateOf(0); // 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text(text => { // 2. 在组件的某个属性中使用该状态的值 text.setText(count.getValue().toString()); // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count); text.DisposableEffect(effect => { // 当 count 的值改变时会重新执行附带效应 effect.setKeys([count]); // 这个日志只会打印一次 $console.log("config disposable effect count: ", count.getValue()); effect.setEffect(it => { // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合或者 count 值改变时执行 $console.log("disposable effect enter or count value change: ", count.getValue()); it.setOnDispose(() => { // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合或者 count 值改变时执行 $console.log("disposable effect leave or count value change: ", count.getValue()); }); }); }); text.DisposableEffect(effect => { // 这个日志只会打印一次 $console.log("config disposable effect without keys"); effect.setEffect(it => { // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合时执行 $console.log("disposable effect enter without keys"); it.setOnDispose(() => { // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合时执行 $console.log("disposable effect leave without keys"); }); }); }); }); // 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton(button => { // 设置文本按钮的content插槽内容 button.setContent(buttonSlot => { // 在插槽中添加一个文本 buttonSlot.Text(text => { text.setText("点击我"); }); }); // 设置按钮点击事件 button.setOnClick(() => { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1); }); }); }); }); }); // 启动一个Activity用于显示脚本界面 $activity.start();
-- 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView:create(function(slot) -- 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 -- 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot:Column(function(column) -- 设置 Column 中 content 插槽的内容 column:setContent(function(columnSlot) -- 1. 创建一个可变状态,用于保存计数器的值 local count = slot:mutableStateOf(0) -- 添加一个文件组件到 Column (垂直布局)中 columnSlot:Text(function(text) -- 2. 在组件的某个属性中使用该状态的值 text:setText(tostring(count:getValue())) -- 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text:trackSingleState(count) text:DisposableEffect(function(effect) -- 当 count 的值改变时会重新执行附带效应 effect:setKeys(_iterables:mutableListOf(count)) -- 这个日志只会打印一次 _console:log("config disposable effect count: ", count:getValue()) effect:setEffect(function(it) -- 这里才是附带效应的代码,下面这个代码会在 Text 进入组合或者 count 值改变时执行 _console:log("disposable effect enter or count value change: ", count:getValue()) it:setOnDispose(function() -- 这里才是附带效应的代码,下面这个代码会在 Text 退出组合或者 count 值改变时执行 _console:log("disposable effect leave or count value change: ", count:getValue()) end) end) end) text:DisposableEffect(function(effect) -- 这个日志只会打印一次 _console:log("config disposable effect without keys") effect:setEffect(function(it) -- 这里才是附带效应的代码,下面这个代码会在 Text 进入组合时执行 _console:log("disposable effect enter without keys") it:setOnDispose(function() -- 这里才是附带效应的代码,下面这个代码会在 Text 退出组合时执行 _console:log("disposable effect leave without keys") end) end) end) end) -- 添加一个文本按钮到 Column (垂直布局)中 columnSlot:TextButton(function(button) -- 设置文本按钮的content插槽内容 button:setContent(function(buttonSlot) -- 在插槽中添加一个文本 buttonSlot:Text(function(text) text:setText("点击我") end) end) -- 设置按钮点击事件 button:setOnClick(function() -- 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count:setValue(count:getValue() + 1) end) end) end) end) end) -- 启动一个Activity用于显示脚本界面 _activity:start()
<?php /** @var m8test_java\com\m8test\script\core\api\ui\compose\ComposeView $composeView */ global $composeView; /** @var m8test_java\com\m8test\script\core\api\ui\Activity $activity */ global $activity; /** @var m8test_java\com\m8test\script\core\api\console\Console $console */ global $console; /** @var m8test_java\com\m8test\script\core\api\collections\Iterables $iterables */ global $iterables; // 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView->create(function ($slot) { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 $slot->Column(function ($column) use ($slot) { // <-- 关键修正点在这里 // 设置 Column 中 content 插槽的内容 $column->setContent(function ($columnSlot) use ($slot) { // 1. 创建一个可变状态,用于保存计数器的值 $count = $slot->mutableStateOf(0); // 添加一个文件组件到 Column (垂直布局)中 $columnSlot->Text(function ($text) use ($count) { // 2. 在组件的某个属性中使用该状态的值 $text->setText((string)$count->getValue()); // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) $text->trackSingleState($count); $text->DisposableEffect(function ($effect) use ($count) { global $iterables, $console; // 当 count 的值改变时会重新执行附带效应 $effect->setKeys($iterables->mutableListOf($count)); // 这个日志只会打印一次 $console->log(javaString("config disposable effect count: "), $count->getValue()); $effect->setEffect(function ($it) use ($count) { global $console; // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合或者 count 值改变时执行 $console->log(javaString("disposable effect enter or count value change: "), $count->getValue()); $it->setOnDispose(function () use ($count) { global $console; // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合或者 count 值改变时执行 $console->log(javaString("disposable effect leave or count value change: "), $count->getValue()); }); }); }); $text->DisposableEffect(function ($effect) { global $console; // 这个日志只会打印一次 $console->log(javaString("config disposable effect without keys")); $effect->setEffect(function ($it) { global $console; // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合时执行 $console->log(javaString("disposable effect enter without keys")); $it->setOnDispose(function () { global $console; // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合时执行 $console->log(javaString("disposable effect leave without keys")); }); }); }); }); // 添加一个文本按钮到 Column (垂直布局)中 $columnSlot->TextButton(function ($button) use ($count) { // 设置文本按钮的content插槽内容 $button->setContent(function ($buttonSlot) { // 在插槽中添加一个文本 $buttonSlot->Text(function ($text) { $text->setText(javaString("点击我")); }); }); // 设置按钮点击事件 $button->setOnClick(function () use ($count) { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState $count->setValue($count->getValue() + 1); }); }); }); }); }); // 启动一个Activity用于显示脚本界面 $activity->start();
# 导入全局变量,$console -> _console from m8test_java.com.m8test.script.GlobalVariables import _console # 导入全局变量,$composeView -> _composeView from m8test_java.com.m8test.script.GlobalVariables import _composeView # 导入全局变量,$activity -> _activity from m8test_java.com.m8test.script.GlobalVariables import _activity # 导入全局变量,$iterables -> _iterables from m8test_java.com.m8test.script.GlobalVariables import _iterables # 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView.create(lambda slot: # 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 # 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column(lambda column: # 设置 Column 中 content 插槽的内容 # 使用元组和海象表达式来在lambda中执行多个语句 column.setContent(lambda columnSlot: ( # 1. 创建一个可变状态,用于保存计数器的值 (count := slot.mutableStateOf(0)), # 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text(lambda text: ( # 2. 在组件的某个属性中使用该状态的值 text.setText(str(count.getValue())), # 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count), text.DisposableEffect(lambda effect: ( # 当 count 的值改变时会重新执行附带效应 effect.setKeys(_iterables.mutableListOf(count)), # 这个日志只会打印一次 _console.log("config disposable effect count: ", count.getValue()), effect.setEffect(lambda disposable_scope: ( # 这里才是附带效应的代码,下面这个代码会在 Text 进入组合或者 count 值改变时执行 _console.log("disposable effect enter or count value change: ", count.getValue()), disposable_scope.setOnDispose(lambda: # 这里才是附带效应的代码,下面这个代码会在 Text 退出组合或者 count 值改变时执行 _console.log("disposable effect leave or count value change: ", count.getValue()) ) )) )), text.DisposableEffect(lambda effect: ( # 这个日志只会打印一次 _console.log("config disposable effect without keys"), effect.setEffect(lambda disposable_scope: ( # 这里才是附带效应的代码,下面这个代码会在 Text 进入组合时执行 _console.log("disposable effect enter without keys"), disposable_scope.setOnDispose(lambda: # 这里才是附带效应的代码,下面这个代码会在 Text 退出组合时执行 _console.log("disposable effect leave without keys") ) )) )) )), # 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton(lambda button: ( # 设置文本按钮的content插槽内容 button.setContent(lambda buttonSlot: # 在插槽中添加一个文本 buttonSlot.Text(lambda text: text.setText("点击我") ) ), # 设置按钮点击事件 button.setOnClick(lambda: # 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) ) )) )) ) ) # 启动一个Activity用于显示脚本界面 _activity.start()
# encoding: utf-8 # 如果源代码中包含中文,那么就需要添加上面的编码语句 # 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create do |slot| # 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 # 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column do |column| # 设置 Column 中 content 插槽的内容 column.setContent do |columnSlot| # 1. 创建一个可变状态,用于保存计数器的值 count = slot.mutableStateOf(0) # 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text do |text| # 2. 在组件的某个属性中使用该状态的值 text.setText(count.getValue().to_s) # 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count) text.DisposableEffect do |effect| # 当 count 的值改变时会重新执行附带效应 effect.setKeys($iterables.mutableListOf(count)) # 这个日志只会打印一次 $console.log("config disposable effect count: ", count.getValue()) # 在 Groovy 中 implicit 'it' 指代闭包参数,在 Ruby 中需要显式声明 |it| effect.setEffect do |it| # 这里才是附带效应的代码,下面这个代码会在 Text 进入组合或者 count 值改变时执行 $console.log("disposable effect enter or count value change: ", count.getValue()) it.setOnDispose do # 这里才是附带效应的代码,下面这个代码会在 Text 退出组合或者 count 值改变时执行 $console.log("disposable effect leave or count value change: ", count.getValue()) end end end text.DisposableEffect do |effect| # 这个日志只会打印一次 $console.log("config disposable effect without keys") effect.setEffect do |it| # 这里才是附带效应的代码,下面这个代码会在 Text 进入组合时执行 $console.log("disposable effect enter without keys") it.setOnDispose do # 这里才是附带效应的代码,下面这个代码会在 Text 退出组合时执行 $console.log("disposable effect leave without keys") end end end end # 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton do |button| # 设置文本按钮的content插槽内容 button.setContent do |buttonSlot| # 在插槽中添加一个文本 buttonSlot.Text do |text| text.setText("点击我") end end # 设置按钮点击事件 button.setOnClick do # 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) end end end end end # 启动一个Activity用于显示脚本界面 $activity.start()
import com.m8test.script.GlobalVariables.* fun side1Run() { // 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView.create { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 Column { // 设置 Column 中 content 插槽的内容 setContent { // 1. 创建一个可变状态,用于显示/隐藏文本 val showTextComponent = mutableStateOf(true) // 添加一个文件组件到 Column (垂直布局)中 Text { // 2. 在组件的某个属性中使用该状态的值 setVisible(showTextComponent.getValue()) setText("我是文本") // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) trackSingleState(showTextComponent) DisposableEffect { // 这个日志只会打印一次 _console.log("config disposable effect without keys") setEffect { // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合(显示)时执行 _console.log("disposable effect enter without keys") setOnDispose { // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合(隐藏)时执行 _console.log("disposable effect leave without keys") } } } } // 添加一个文本按钮到 Column (垂直布局)中 TextButton { // 设置文本按钮的content插槽内容 setContent { // 在插槽中添加一个文本 Text { setText("点击我") } } // 设置按钮点击事件 setOnClick { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState showTextComponent.setValue(!showTextComponent.getValue()) } } } } } // 启动一个Activity用于显示脚本界面 _activity.start() } //-m8test-remove side1Run();
// 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create { slot -> // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column { column -> // 设置 Column 中 content 插槽的内容 column.setContent { columnSlot -> // 1. 创建一个可变状态,用于显示/隐藏文本 def showTextComponent = slot.mutableStateOf(true) // 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text { text -> // 2. 在组件的某个属性中使用该状态的值 text.setVisible(showTextComponent.getValue()) text.setText("我是文本") // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(showTextComponent) text.DisposableEffect { effect -> // 这个日志只会打印一次 $console.log("config disposable effect without keys") effect.setEffect { // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合(显示)时执行 $console.log("disposable effect enter without keys") it.setOnDispose { // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合(隐藏)时执行 $console.log("disposable effect leave without keys") } } } } // 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton { button -> // 设置文本按钮的content插槽内容 button.setContent { buttonSlot -> // 在插槽中添加一个文本 buttonSlot.Text { text -> text.setText("点击我") } } // 设置按钮点击事件 button.setOnClick { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState showTextComponent.setValue(!showTextComponent.getValue()) } } } } } // 启动一个Activity用于显示脚本界面 $activity.start()
// 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create(slot => { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column(column => { // 设置 Column 中 content 插槽的内容 column.setContent(columnSlot => { // 1. 创建一个可变状态,用于显示/隐藏文本 const showTextComponent = slot.mutableStateOf(true); // 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text(text => { // 2. 在组件的某个属性中使用该状态的值 text.setVisible(showTextComponent.getValue() == true); text.setText("我是文本"); // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(showTextComponent); text.DisposableEffect(effect => { // 这个日志只会打印一次 $console.log("config disposable effect without keys"); effect.setEffect(it => { // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合(显示)时执行 $console.log("disposable effect enter without keys"); it.setOnDispose(() => { // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合(隐藏)时执行 $console.log("disposable effect leave without keys"); }); }); }); }); // 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton(button => { // 设置文本按钮的content插槽内容 button.setContent(buttonSlot => { // 在插槽中添加一个文本 buttonSlot.Text(text => { text.setText("点击我"); }); }); // 设置按钮点击事件 button.setOnClick(() => { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState if (showTextComponent.getValue() == true) { showTextComponent.setValue(false); } else { showTextComponent.setValue(true); } }); }); }); }); }); // 启动一个Activity用于显示脚本界面 $activity.start();
-- 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView:create(function(slot) -- 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 -- 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot:Column(function(column) -- 设置 Column 中 content 插槽的内容 column:setContent(function(columnSlot) -- 1. 创建一个可变状态,用于显示/隐藏文本 local showTextComponent = slot:mutableStateOf(true) -- 添加一个文件组件到 Column (垂直布局)中 columnSlot:Text(function(text) -- 2. 在组件的某个属性中使用该状态的值 text:setVisible(showTextComponent:getValue()) text:setText("我是文本") -- 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) text:trackSingleState(showTextComponent) text:DisposableEffect(function(effect) -- 这个日志只会打印一次 _console:log("config disposable effect without keys") effect:setEffect(function(it) -- 这里才是附带效应的代码,下面这个代码会在 Text 进入组合(显示)时执行 _console:log("disposable effect enter without keys") it:setOnDispose(function() -- 这里才是附带效应的代码,下面这个代码会在 Text 退出组合(隐藏)时执行 _console:log("disposable effect leave without keys") end) end) end) end) -- 添加一个文本按钮到 Column (垂直布局)中 columnSlot:TextButton(function(button) -- 设置文本按钮的content插槽内容 button:setContent(function(buttonSlot) -- 在插槽中添加一个文本 buttonSlot:Text(function(text) text:setText("点击我") end) end) -- 设置按钮点击事件 button:setOnClick(function() -- 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState showTextComponent:setValue(not showTextComponent:getValue()) end) end) end) end) end) -- 启动一个Activity用于显示脚本界面 _activity:start()
<?php /** @var m8test_java\com\m8test\script\core\api\ui\compose\ComposeView $composeView */ global $composeView; /** @var m8test_java\com\m8test\script\core\api\ui\Activity $activity */ global $activity; /** @var m8test_java\com\m8test\script\core\api\console\Console $console */ global $console; // 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView->create(function ($slot) { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 $slot->Column(function ($column) use ($slot) { // 设置 Column 中 content 插槽的内容 $column->setContent(function ($columnSlot) use ($slot) { // 1. 创建一个可变状态,用于显示/隐藏文本 $showTextComponent = $slot->mutableStateOf(true); // 添加一个文件组件到 Column (垂直布局)中 $columnSlot->Text(function ($text) use ($showTextComponent) { // 2. 在组件的某个属性中使用该状态的值 $text->setVisible($showTextComponent->getValue()); $text->setText(javaString("我是文本")); // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) $text->trackSingleState($showTextComponent); $text->DisposableEffect(function ($effect) { global $console; // 这个日志只会打印一次 $console->log(javaString("config disposable effect without keys")); $effect->setEffect(function ($it) { global $console; // 这里才是附带效应的代码,下面这个代码会在 Text 进入组合(显示)时执行 $console->log(javaString("disposable effect enter without keys")); $it->setOnDispose(function () { global $console; // 这里才是附带效应的代码,下面这个代码会在 Text 退出组合(隐藏)时执行 $console->log(javaString("disposable effect leave without keys")); }); }); }); }); // 添加一个文本按钮到 Column (垂直布局)中 $columnSlot->TextButton(function ($button) use ($showTextComponent) { // 设置文本按钮的content插槽内容 $button->setContent(function ($buttonSlot) { // 在插槽中添加一个文本 $buttonSlot->Text(function ($text) { $text->setText(javaString("点击我")); }); }); // 设置按钮点击事件 $button->setOnClick(function () use ($showTextComponent) { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState $showTextComponent->setValue(!$showTextComponent->getValue()); }); }); }); }); }); // 启动一个Activity用于显示脚本界面 $activity->start();
# 导入全局变量,$console -> _console # 导入全局变量,$activity -> _activity from m8test_java.com.m8test.script.GlobalVariables import _activity # 导入全局变量,$composeView -> _composeView from m8test_java.com.m8test.script.GlobalVariables import _composeView from m8test_java.com.m8test.script.GlobalVariables import _console # 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView.create(lambda slot: # 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 # 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column(lambda column: # 设置 Column 中 content 插槽的内容 # 使用元组和海象表达式来在lambda中执行多个语句 column.setContent(lambda columnSlot: ( # 1. 创建一个可变状态,用于显示/隐藏文本 (showTextComponent := slot.mutableStateOf(True)), # 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text(lambda text: ( # 2. 在组件的某个属性中使用该状态的值 text.setVisible(showTextComponent.getValue()), text.setText("我是文本"), # 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(showTextComponent), text.DisposableEffect(lambda effect: ( # 这个日志只会打印一次 _console.log("config disposable effect without keys"), effect.setEffect(lambda it: ( # 这里才是附带效应的代码,下面这个代码会在 Text 进入组合(显示)时执行 _console.log("disposable effect enter without keys"), it.setOnDispose(lambda: # 这里才是附带效应的代码,下面这个代码会在 Text 退出组合(隐藏)时执行 _console.log("disposable effect leave without keys") ) )) )) )), # 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton(lambda button: ( # 设置文本按钮的content插槽内容 button.setContent(lambda buttonSlot: # 在插槽中添加一个文本 buttonSlot.Text(lambda text: text.setText("点击我") ) ), # 设置按钮点击事件 button.setOnClick(lambda: # 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState showTextComponent.setValue(not showTextComponent.getValue()) ) )) )) ) ) # 启动一个Activity用于显示脚本界面 _activity.start()
# encoding: utf-8 # 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create do |slot| # 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 # 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column do |column| # 设置 Column 中 content 插槽的内容 column.setContent do |columnSlot| # 1. 创建一个可变状态,用于显示/隐藏文本 showTextComponent = slot.mutableStateOf(true) # 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text do |text| # 2. 在组件的某个属性中使用该状态的值 text.setVisible(showTextComponent.getValue()) text.setText("我是文本") # 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(showTextComponent) text.DisposableEffect do |effect| # 这个日志只会打印一次 $console.log("config disposable effect without keys") effect.setEffect do |it| # 这里才是附带效应的代码,下面这个代码会在 Text 进入组合(显示)时执行 $console.log("disposable effect enter without keys") it.setOnDispose do # 这里才是附带效应的代码,下面这个代码会在 Text 退出组合(隐藏)时执行 $console.log("disposable effect leave without keys") end end end end # 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton do |button| # 设置文本按钮的content插槽内容 button.setContent do |buttonSlot| # 在插槽中添加一个文本 buttonSlot.Text do |text| text.setText("点击我") end end # 设置按钮点击事件 button.setOnClick do # 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState showTextComponent.setValue(!showTextComponent.getValue()) end end end end end # 启动一个Activity用于显示脚本界面 $activity.start()

LaunchedEffect

LaunchedEffect 用于在 Composable 进入组合时启动一个协程,并在 Composable 离开组合或其 key 发生变化时取消该协程。它适用于需要执行异步操作(挂起函数)的场景。

  • 用途: 执行网络请求、数据库操作、计时器、动画等异步操作,这些操作的生命周期与 Composable 的生命周期相关联。

  • 特性:

    • 接受一个或多个 key 参数。当这些 key 中的任何一个发生变化时,当前的协程会被取消 (Job.cancel ),并重新启动一个新的协程来执行 block

    • 当 Composable 离开组合时,协程会被自动取消。

    • block 是一个 suspend 函数,在一个与组合绑定的 CoroutineScope 中执行。

  • 何时使用: 当你需要执行异步操作,并且这些操作应该在 Composable 存在时运行,并在 Composable 消失时停止时。

  • 重要提示: 不应该使用 LaunchedEffect 来响应 UI 事件(例如按钮点击),并通过将回调数据存储在 MutableState 中并作为 key 传递来触发重新启动。对于响应事件,应该使用 rememberCoroutineScope 来获取一个 CoroutineScope ,然后在该作用域内手动启动协程。

import com.m8test.script.GlobalVariables.* fun side1Run() { // 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView.create { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 Column { // 设置 Column 中 content 插槽的内容 setContent { // 1. 创建一个可变状态,用于保存计数器的值 val count = mutableStateOf(0) // 添加一个文件组件到 Column (垂直布局)中 Text { // 2. 在组件的某个属性中使用该状态的值 setText(count.getValue().toString()) // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) trackSingleState(count) LaunchedEffect { // 设置 LaunchedEffect 的 keys,如果keys改变,那么附带效应会重新执行 setKeys(_iterables.mutableListOf(count) as List<com.m8test.script.core.api.ui.compose.state.MutableState<Any?>>) // 这个代码只会执行一次 _console.log("config launched effect count: ", count.getValue()) setEffect { // 这是附带效应的代码,当 Text 进入组合或者每次 count 改变时都会执行 _console.log("launched effect enter or count value change: ", count.getValue()) } } LaunchedEffect { // 这个代码只会执行一次 _console.log("config launched effect without keys") setEffect { // 这是附带效应的代码,当 Text 进入组合时会执行 _console.log("launched effect enter without keys") } } } // 添加一个文本按钮到 Column (垂直布局)中 TextButton { // 设置文本按钮的content插槽内容 setContent { // 在插槽中添加一个文本 Text { setText("点击我") } } // 设置按钮点击事件 setOnClick { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) } } } } } // 启动一个Activity用于显示脚本界面 _activity.start() } //-m8test-remove side1Run();
// 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create { slot -> // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column { column -> // 设置 Column 中 content 插槽的内容 column.setContent { columnSlot -> // 1. 创建一个可变状态,用于保存计数器的值 def count = slot.mutableStateOf(0) // 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text { text -> // 2. 在组件的某个属性中使用该状态的值 text.setText(count.getValue().toString()) // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count) text.LaunchedEffect { effect -> // 设置 LaunchedEffect 的 keys,如果keys改变,那么附带效应会重新执行 effect.setKeys($iterables.mutableListOf(count)) // 这个代码只会执行一次 $console.log("config launched effect count: ", count.getValue()) effect.setEffect { // 这是附带效应的代码,当 Text 进入组合或者每次 count 改变时都会执行 $console.log("launched effect enter or count value change: ", count.getValue()) } } text.LaunchedEffect { effect -> // 这个代码只会执行一次 $console.log("config launched effect without keys") effect.setEffect { // 这是附带效应的代码,当 Text 进入组合时会执行 $console.log("launched effect enter without keys") } } } // 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton { button -> // 设置文本按钮的content插槽内容 button.setContent { buttonSlot -> // 在插槽中添加一个文本 buttonSlot.Text { text -> text.setText("点击我") } } // 设置按钮点击事件 button.setOnClick { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) } } } } } // 启动一个Activity用于显示脚本界面 $activity.start()
// 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create(slot => { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column(column => { // 设置 Column 中 content 插槽的内容 column.setContent(columnSlot => { // 1. 创建一个可变状态,用于保存计数器的值 const count = slot.mutableStateOf(0); // 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text(text => { // 2. 在组件的某个属性中使用该状态的值 text.setText(count.getValue().toString()); // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count); text.LaunchedEffect(effect => { // 设置 LaunchedEffect 的 keys,如果keys改变,那么附带效应会重新执行 effect.setKeys($iterables.mutableListOf(count)); // 这个代码只会执行一次 $console.log("config launched effect count: ", count.getValue()); effect.setEffect(() => { // 这是附带效应的代码,当 Text 进入组合或者每次 count 改变时都会执行 $console.log("launched effect enter or count value change: ", count.getValue()); }); }); text.LaunchedEffect(effect => { // 这个代码只会执行一次 $console.log("config launched effect without keys"); effect.setEffect(() => { // 这是附带效应的代码,当 Text 进入组合时会执行 $console.log("launched effect enter without keys"); }); }); }); // 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton(button => { // 设置文本按钮的content插槽内容 button.setContent(buttonSlot => { // 在插槽中添加一个文本 buttonSlot.Text(text => { text.setText("点击我"); }); }); // 设置按钮点击事件 button.setOnClick(() => { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1); }); }); }); }); }); // 启动一个Activity用于显示脚本界面 $activity.start();
-- 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView:create(function(slot) -- 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 -- 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot:Column(function(column) -- 设置 Column 中 content 插槽的内容 column:setContent(function(columnSlot) -- 1. 创建一个可变状态,用于保存计数器的值 local count = slot:mutableStateOf(0) -- 添加一个文件组件到 Column (垂直布局)中 columnSlot:Text(function(text) -- 2. 在组件的某个属性中使用该状态的值 text:setText(tostring(count:getValue())) -- 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text:trackSingleState(count) text:LaunchedEffect(function(effect) -- 设置 LaunchedEffect 的 keys,如果keys改变,那么附带效应会重新执行 effect:setKeys(_iterables:mutableListOf(count)) -- 这个代码只会执行一次 _console:log("config launched effect count: ", count:getValue()) effect:setEffect(function() -- 这是附带效应的代码,当 Text 进入组合或者每次 count 改变时都会执行 _console:log("launched effect enter or count value change: ", count:getValue()) end) end) text:LaunchedEffect(function(effect) -- 这个代码只会执行一次 _console:log("config launched effect without keys") effect:setEffect(function() -- 这是附带效应的代码,当 Text 进入组合时会执行 _console:log("launched effect enter without keys") end) end) end) -- 添加一个文本按钮到 Column (垂直布局)中 columnSlot:TextButton(function(button) -- 设置文本按钮的content插槽内容 button:setContent(function(buttonSlot) -- 在插槽中添加一个文本 buttonSlot:Text(function(text) text:setText("点击我") end) end) -- 设置按钮点击事件 button:setOnClick(function() -- 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count:setValue(count:getValue() + 1) end) end) end) end) end) -- 启动一个Activity用于显示脚本界面 _activity:start()
<?php /** @var m8test_java\com\m8test\script\core\api\ui\compose\ComposeView $composeView */ global $composeView; /** @var m8test_java\com\m8test\script\core\api\ui\Activity $activity */ global $activity; /** @var m8test_java\com\m8test\script\core\api\console\Console $console */ global $console; /** @var m8test_java\com\m8test\script\core\api\collections\Iterables $iterables */ global $iterables; // 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView->create(function ($slot) { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 $slot->Column(function ($column) use ($slot) { // 设置 Column 中 content 插槽的内容 $column->setContent(function ($columnSlot) use ($slot) { // 1. 创建一个可变状态,用于保存计数器的值 $count = $slot->mutableStateOf(0); // 添加一个文件组件到 Column (垂直布局)中 $columnSlot->Text(function ($text) use ($count) { // 2. 在组件的某个属性中使用该状态的值 $text->setText((string)$count->getValue()); // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) $text->trackSingleState($count); $text->LaunchedEffect(function ($effect) use ($count) { global $iterables, $console; // 设置 LaunchedEffect 的 keys,如果keys改变,那么附带效应会重新执行 $effect->setKeys($iterables->mutableListOf($count)); // 这个代码只会执行一次 $console->log(javaString("config launched effect count: "), $count->getValue()); $effect->setEffect(function () use ($count) { global $console; // 这是附带效应的代码,当 Text 进入组合或者每次 count 改变时都会执行 $console->log(javaString("launched effect enter or count value change: "), $count->getValue()); }); }); $text->LaunchedEffect(function ($effect) { global $console; // 这个代码只会执行一次 $console->log(javaString("config launched effect without keys")); $effect->setEffect(function () { global $console; // 这是附带效应的代码,当 Text 进入组合时会执行 $console->log(javaString("launched effect enter without keys")); }); }); }); // 添加一个文本按钮到 Column (垂直布局)中 $columnSlot->TextButton(function ($button) use ($count) { // 设置文本按钮的content插槽内容 $button->setContent(function ($buttonSlot) { // 在插槽中添加一个文本 $buttonSlot->Text(function ($text) { $text->setText(javaString("点击我")); }); }); // 设置按钮点击事件 $button->setOnClick(function () use ($count) { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState $count->setValue($count->getValue() + 1); }); }); }); }); }); // 启动一个Activity用于显示脚本界面 $activity->start();
# 导入全局变量,$composeView -> _composeView from m8test_java.com.m8test.script.GlobalVariables import _composeView # 导入全局变量,$console -> _console from m8test_java.com.m8test.script.GlobalVariables import _console # 导入全局变量,$iterables -> _iterables from m8test_java.com.m8test.script.GlobalVariables import _iterables # 导入全局变量,$activity -> _activity from m8test_java.com.m8test.script.GlobalVariables import _activity # 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView.create(lambda slot: # 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 # 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column(lambda column: # 设置 Column 中 content 插槽的内容 column.setContent(lambda columnSlot: ( # 1. 创建一个可变状态,用于保存计数器的值 (count := slot.mutableStateOf(0)), # 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text(lambda text: ( # 2. 在组件的某个属性中使用该状态的值 text.setText(str(count.getValue())), # 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count), text.LaunchedEffect(lambda effect: ( # 设置 LaunchedEffect 的 keys,如果keys改变,那么附带效应会重新执行 effect.setKeys(_iterables.mutableListOf(count)), # 这个代码只会执行一次 _console.log("config launched effect count: ", count.getValue()), # --- 修复点 1: 将 lambda: 改为 lambda _: --- effect.setEffect(lambda _: # 这是附带效应的代码,当 Text 进入组合或者每次 count 改变时都会执行 _console.log("launched effect enter or count value change: ", count.getValue()) ) )), text.LaunchedEffect(lambda effect: ( # 这个代码只会执行一次 _console.log("config launched effect without keys"), # --- 修复点 2: 将 lambda: 改为 lambda _: --- effect.setEffect(lambda _: # 这是附带效应的代码,当 Text 进入组合时会执行 _console.log("launched effect enter without keys") ) )) )), # 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton(lambda button: ( # 设置文本按钮的content插槽内容 button.setContent(lambda buttonSlot: # 在插槽中添加一个文本 buttonSlot.Text(lambda text: text.setText("点击我") ) ), # 设置按钮点击事件 button.setOnClick(lambda: # 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) ) )) )) ) ) # 启动一个Activity用于显示脚本界面 _activity.start()
# encoding: utf-8 # 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create do |slot| # 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 # 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column do |column| # 设置 Column 中 content 插槽的内容 column.setContent do |columnSlot| # 1. 创建一个可变状态,用于保存计数器的值 count = slot.mutableStateOf(0) # 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text do |text| # 2. 在组件的某个属性中使用该状态的值 text.setText(count.getValue().to_s) # 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 count 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(count) text.LaunchedEffect do |effect| # 设置 LaunchedEffect 的 keys,如果keys改变,那么附带效应会重新执行 effect.setKeys($iterables.mutableListOf(count)) # 这个代码只会执行一次 $console.log("config launched effect count: ", count.getValue()) effect.setEffect do # 这是附带效应的代码,当 Text 进入组合或者每次 count 改变时都会执行 $console.log("launched effect enter or count value change: ", count.getValue()) end end text.LaunchedEffect do |effect| # 这个代码只会执行一次 $console.log("config launched effect without keys") effect.setEffect do # 这是附带效应的代码,当 Text 进入组合时会执行 $console.log("launched effect enter without keys") end end end # 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton do |button| # 设置文本按钮的content插槽内容 button.setContent do |buttonSlot| # 在插槽中添加一个文本 buttonSlot.Text do |text| text.setText("点击我") end end # 设置按钮点击事件 button.setOnClick do # 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState count.setValue(count.getValue() + 1) end end end end end # 启动一个Activity用于显示脚本界面 $activity.start()
import com.m8test.script.GlobalVariables.* fun side1Run() { // 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView.create { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 Column { // 设置 Column 中 content 插槽的内容 setContent { // 1. 创建一个可变状态,用于显示/隐藏文本 val showTextComponent = mutableStateOf(true) // 添加一个文件组件到 Column (垂直布局)中 Text { // 2. 在组件的某个属性中使用该状态的值 setVisible(showTextComponent.getValue()) setText("我是文本") // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) trackSingleState(showTextComponent) LaunchedEffect { // 这个代码只会执行一次 _console.log("config launched effect without keys") setEffect { // 这是附带效应的代码,当 Text 进入组合(显示)时会执行 _console.log("launched effect enter without keys") } } } // 添加一个文本按钮到 Column (垂直布局)中 TextButton { // 设置文本按钮的content插槽内容 setContent { // 在插槽中添加一个文本 Text { setText("点击我") } } // 设置按钮点击事件 setOnClick { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState showTextComponent.setValue(!showTextComponent.getValue()) } } } } } // 启动一个Activity用于显示脚本界面 _activity.start() } //-m8test-remove side1Run();
// 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create { slot -> // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column { column -> // 设置 Column 中 content 插槽的内容 column.setContent { columnSlot -> // 1. 创建一个可变状态,用于显示/隐藏文本 def showTextComponent = slot.mutableStateOf(true) // 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text { text -> // 2. 在组件的某个属性中使用该状态的值 text.setVisible(showTextComponent.getValue()) text.setText("我是文本") // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(showTextComponent) text.LaunchedEffect { effect -> // 这个代码只会执行一次 $console.log("config launched effect without keys") effect.setEffect { // 这是附带效应的代码,当 Text 进入组合(显示)时会执行 $console.log("launched effect enter without keys") } } } // 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton { button -> // 设置文本按钮的content插槽内容 button.setContent { buttonSlot -> // 在插槽中添加一个文本 buttonSlot.Text { text -> text.setText("点击我") } } // 设置按钮点击事件 button.setOnClick { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState showTextComponent.setValue(!showTextComponent.getValue()) } } } } } // 启动一个Activity用于显示脚本界面 $activity.start()
// 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create(slot => { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column(column => { // 设置 Column 中 content 插槽的内容 column.setContent(columnSlot => { // 1. 创建一个可变状态,用于显示/隐藏文本 const showTextComponent = slot.mutableStateOf(true); // 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text(text => { // 2. 在组件的某个属性中使用该状态的值 text.setVisible(showTextComponent.getValue() == true); text.setText("我是文本"); // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(showTextComponent); text.LaunchedEffect(effect => { // 这个代码只会执行一次 $console.log("config launched effect without keys"); effect.setEffect(() => { // 这是附带效应的代码,当 Text 进入组合(显示)时会执行 $console.log("launched effect enter without keys"); }); }); }); // 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton(button => { // 设置文本按钮的content插槽内容 button.setContent(buttonSlot => { // 在插槽中添加一个文本 buttonSlot.Text(text => { text.setText("点击我"); }); }); // 设置按钮点击事件 button.setOnClick(() => { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState if (showTextComponent.getValue() == true) { showTextComponent.setValue(false); } else { showTextComponent.setValue(true); } }); }); }); }); }); // 启动一个Activity用于显示脚本界面 $activity.start();
-- 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView:create(function(slot) -- 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 -- 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot:Column(function(column) -- 设置 Column 中 content 插槽的内容 column:setContent(function(columnSlot) -- 1. 创建一个可变状态,用于显示/隐藏文本 local showTextComponent = slot:mutableStateOf(true) -- 添加一个文件组件到 Column (垂直布局)中 columnSlot:Text(function(text) -- 2. 在组件的某个属性中使用该状态的值 text:setVisible(showTextComponent:getValue()) text:setText("我是文本") -- 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) text:trackSingleState(showTextComponent) text:LaunchedEffect(function(effect) -- 这个代码只会执行一次 _console:log("config launched effect without keys") effect:setEffect(function() -- 这是附带效应的代码,当 Text 进入组合(显示)时会执行 _console:log("launched effect enter without keys") end) end) end) -- 添加一个文本按钮到 Column (垂直布局)中 columnSlot:TextButton(function(button) -- 设置文本按钮的content插槽内容 button:setContent(function(buttonSlot) -- 在插槽中添加一个文本 buttonSlot:Text(function(text) text:setText("点击我") end) end) -- 设置按钮点击事件 button:setOnClick(function() -- 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState showTextComponent:setValue(not showTextComponent:getValue()) end) end) end) end) end) -- 启动一个Activity用于显示脚本界面 _activity:start()
<?php /** @var m8test_java\com\m8test\script\core\api\ui\compose\ComposeView $composeView */ global $composeView; /** @var m8test_java\com\m8test\script\core\api\ui\Activity $activity */ global $activity; /** @var m8test_java\com\m8test\script\core\api\console\Console $console */ global $console; // 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView->create(function ($slot) { // 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 // 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 $slot->Column(function ($column) use ($slot) { // 设置 Column 中 content 插槽的内容 $column->setContent(function ($columnSlot) use ($slot) { // 1. 创建一个可变状态,用于显示/隐藏文本 $showTextComponent = $slot->mutableStateOf(true); // 添加一个文件组件到 Column (垂直布局)中 $columnSlot->Text(function ($text) use ($showTextComponent) { global $console; // 2. 在组件的某个属性中使用该状态的值 $text->setVisible($showTextComponent->getValue()); $text->setText(javaString("我是文本")); // 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) $text->trackSingleState($showTextComponent); $text->LaunchedEffect(function ($effect) { global $console; // 这个代码只会执行一次 $console->log(javaString("config launched effect without keys")); $effect->setEffect(function () { global $console; // 这是附带效应的代码,当 Text 进入组合(显示)时会执行 $console->log(javaString("launched effect enter without keys")); }); }); }); // 添加一个文本按钮到 Column (垂直布局)中 $columnSlot->TextButton(function ($button) use ($showTextComponent) { // 设置文本按钮的content插槽内容 $button->setContent(function ($buttonSlot) { // 在插槽中添加一个文本 $buttonSlot->Text(function ($text) { $text->setText(javaString("点击我")); }); }); // 设置按钮点击事件 $button->setOnClick(function () use ($showTextComponent) { // 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState $showTextComponent->setValue(!$showTextComponent->getValue()); }); }); }); }); }); // 启动一个Activity用于显示脚本界面 $activity->start();
# 导入全局变量,$console -> _console from m8test_java.com.m8test.script.GlobalVariables import _console # 导入全局变量,$composeView -> _composeView from m8test_java.com.m8test.script.GlobalVariables import _composeView # 导入全局变量,$activity -> _activity from m8test_java.com.m8test.script.GlobalVariables import _activity # 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 _composeView.create(lambda slot: # 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 # 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column(lambda column: # 设置 Column 中 content 插槽的内容 column.setContent(lambda columnSlot: ( # 1. 创建一个可变状态,用于显示/隐藏文本 # 使用海象表达式 (:=) 在lambda中进行赋值 (showTextComponent := slot.mutableStateOf(True)), # 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text(lambda text: ( # 2. 在组件的某个属性中使用该状态的值 text.setVisible(showTextComponent.getValue()), text.setText("我是文本"), # 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(showTextComponent), text.LaunchedEffect(lambda effect: ( # 这个代码只会执行一次 _console.log("config launched effect without keys"), # 修改点: 将 lambda: 改为 lambda _: 以接收框架传入的未使用参数 effect.setEffect(lambda _: # 这是附带效应的代码,当 Text 进入组合(显示)时会执行 _console.log("launched effect enter without keys") ) )) )), # 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton(lambda button: ( # 设置文本按钮的content插槽内容 button.setContent(lambda buttonSlot: # 在插槽中添加一个文本 buttonSlot.Text(lambda text: text.setText("点击我") ) ), # 设置按钮点击事件 button.setOnClick(lambda: # 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState # Groovy中的 ! 在 Python 中是 not showTextComponent.setValue(not showTextComponent.getValue()) ) )) )) ) ) # 启动一个Activity用于显示脚本界面 _activity.start()
# encoding: utf-8 # 创建一个 ComposeView, 可以通过声明式ui创建脚本界面 $composeView.create do |slot| # 插槽(Slot)是一种内容分发机制。它允许你在封装一个通用组件时,在组件内部预留一些“坑位”或“插槽”,然后让使用该组件的父组件来决定这些“坑位”里到底填充什么内容。 # 创建一个Column,Column是一种垂直布局,可以让其中的组件垂直排列 slot.Column do |column| # 设置 Column 中 content 插槽的内容 column.setContent do |columnSlot| # 1. 创建一个可变状态,用于显示/隐藏文本 showTextComponent = slot.mutableStateOf(true) # 添加一个文件组件到 Column (垂直布局)中 columnSlot.Text do |text| # 2. 在组件的某个属性中使用该状态的值 text.setVisible(showTextComponent.getValue()) text.setText("我是文本") # 3. 将状态添加到组件中,这样text组件就会监听该状态的改变,这个是必须的步骤,如果没有此步骤,那么 showTextComponent 的值改变时,composable(text组件)不会重组(界面更新) text.trackSingleState(showTextComponent) text.LaunchedEffect do |effect| # 这个代码只会执行一次 $console.log("config launched effect without keys") effect.setEffect do # 这是附带效应的代码,当 Text 进入组合(显示)时会执行 $console.log("launched effect enter without keys") end end end # 添加一个文本按钮到 Column (垂直布局)中 columnSlot.TextButton do |button| # 设置文本按钮的content插槽内容 button.setContent do |buttonSlot| # 在插槽中添加一个文本 buttonSlot.Text do |text| text.setText("点击我") end end # 设置按钮点击事件 button.setOnClick do # 4. 当按钮被点击时,更新状态的值,这时使用到count的所有组件都会重组(界面更新), 必须要 trackSingleState showTextComponent.setValue(!showTextComponent.getValue()) end end end end end # 启动一个Activity用于显示脚本界面 $activity.start()
09 December 2025