M8Test Help

动作

动作可以是功能性的, 例如跳转到某个界面、 保存编辑器更改到文件中等. 也可以是带界面的, 例如新建文件(需要通过对话框输入文件名)、删除文件(需要用户通过对话框确认删除)等.

动作列表

点击悬浮菜单中动作列表按钮

42

可以看到当前页面可用的所有分组

43

点击某个分组可以看到该分组中可用的动作, 右上角的角标的数字表示该 action 被引用多少次, 例如删除文件 action, 界面中包含多个文件, 每个文件都有引用了删除文件 action, 右上角的角标的数字就会显示有多少个文件

44

如果需要执行某个动作可以直接点击

通过命令执行动作

简单执行动作

action aaa
  • action: 表示执行动作

  • aaa: 表示动作Id

指定动作分组

action aaa --group-id=xxx
  • action: 表示执行动作

  • aaa: 表示动作Id

  • --group-id=xxx: 指定动作分组为xxx

指定动作能力

action aaa --ability=xxx
  • action: 表示执行动作

  • aaa: 表示动作Id

  • --ability=xxx: 指定动作能力为xxx

同时指定动作分组和动作能力

action aaa --ability=xxx --group-id=yyy
  • action: 表示执行动作

  • aaa: 表示动作Id

  • --ability=xxx: 指定动作能力为xxx

  • --group-id=yyy: 指定动作分组为yyy

自定义动作

如果想要自定义动作的话需要通过插件实现, 如果您还不会开发插件可以先查看 插件开发

  1. 编写动作分组继承 AbstractActionGroup, 并实现其中的 getIdgetName 方法

package com.m8test.plugins.common.action import com.hjq.toast.Toaster import com.m8test.action.impl.AbstractActionGroup import com.m8test.action.impl.DefaultFunctionalAction import com.m8test.plugin.api.PluggableApkPlugin /** * Description TODO * * @date 2025/01/02 14:35:21 * @author M8Test, [email protected], https://m8test.com */ class TestActionGroup(plugin: PluggableApkPlugin) : AbstractActionGroup() { init { registerAction(ComposableActions.getWrapContentDialogAction(plugin)) registerAction(ComposableActions.getWrapContentBottomSheetAction(plugin)) registerAction(ComposableActions.getWrapContentFloatyAction(plugin)) registerAction(ComposableActions.getFullscreenDialogAction(plugin)) registerAction(ComposableActions.getFullscreenBottomSheetAction(plugin)) registerAction(ComposableActions.getFullscreenFloatyAction(plugin)) registerAction(ViewActions.getWrapContentDialogAction(plugin)) registerAction(ViewActions.getWrapContentBottomSheetAction(plugin)) registerAction(ViewActions.getWrapContentFloatyAction(plugin)) registerAction(ViewActions.getFullscreenDialogAction(plugin)) registerAction(ViewActions.getFullscreenBottomSheetAction(plugin)) registerAction(ViewActions.getFullscreenFloatyAction(plugin)) registerAction( DefaultFunctionalAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "FunctionalAction", id = "FunctionalAction", onPerform = { Toaster.show("FunctionalAction") }) ) } override fun getId(): String { return "TestActionGroup" } override fun getName(): String { return "TestActionGroup" } }
  1. AbstractComposablePluggableApkPlugin 中的 onInstall 方法注册动作分组, 以及在 onUninstall 方法取消注册动作分组, 并在其中注册需要的动作, 并通过 ActionGroupLazyColumnActionGroupLazyRow 显示动作分组.

package com.m8test.plugins.common.action import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.currentRecomposeScope import androidx.compose.ui.Modifier import com.google.accompanist.imageloading.rememberDrawablePainter import com.m8test.action.impl.ability.DefaultActionAbilityFactory import com.m8test.compose.widget.BackTopBarScaffold import com.m8test.plugin.api.ApkPluginProvider import com.m8test.plugin.impl.AbstractComposablePluggableApkPlugin import com.m8test.util.ActionUtils import com.m8test.util.ActionUtils.ActionGroupLazyColumn import com.m8test.util.ActionUtils.ActionGroupLazyRow /** * Description TODO * * @date 2025/01/01 12:14:31 * @author M8Test, [email protected], https://m8test.com */ class ActionPlugin(apkPluginProvider: ApkPluginProvider) : AbstractComposablePluggableApkPlugin(apkPluginProvider) { private val actionGroup = TestActionGroup(this) override fun onInstall() { super.onInstall() ActionUtils.registerGroup(this, actionGroup) } override fun onUninstall() { super.onUninstall() ActionUtils.unregisterGroup(this, actionGroup) } @Composable override fun Content() { val recomposeScope = currentRecomposeScope val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher BackTopBarScaffold( onBackPress = { onBackPressedDispatcher?.onBackPressed() }, titleText = getResources().getString(R.string.text_settings) ) { paddingValues -> Column( modifier = Modifier .fillMaxSize() .padding(paddingValues) ) { val factory = DefaultActionAbilityFactory( ability = Unit, onInvalidate = { recomposeScope.invalidate() }) ActionGroupLazyRow( actionGroup = actionGroup, factory = { factory }, itemContent = { action, isEnabled, onClick -> IconButton(enabled = isEnabled, onClick = onClick) { Icon( painter = rememberDrawablePainter( drawable = action.getResources() .getDrawable(action.getIconId(), null) ), contentDescription = null ) } } ) ActionGroupLazyColumn( actionGroup = actionGroup, factory = { factory }, itemContent = { action, isEnabled, onClick -> Row( modifier = Modifier .fillMaxWidth() .clickable(enabled = isEnabled, onClick = onClick) ) { Icon( painter = rememberDrawablePainter( drawable = action.getResources() .getDrawable(action.getIconId(), null) ), contentDescription = null ) Text(text = action.getName()) } } ) } } } }
  1. 编写测试用的动作, 下面是 jetpack compose 实现的动作

package com.m8test.plugins.common.action import androidx.compose.material3.Text import com.m8test.action.api.VisualAction import com.m8test.action.impl.DefaultComposableAction import com.m8test.plugin.api.PluggableApkPlugin /** * Description TODO * * @date 2025/01/01 12:26:18 * @author M8Test, [email protected], https://m8test.com */ object ComposableActions { fun getWrapContentDialogAction(plugin: PluggableApkPlugin) = DefaultComposableAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "非全屏对话框(Composable)", id = "ComposableWrapContentDialog", location = VisualAction.Location.WrapContentDialog, content = { ability -> Text(text = "非全屏对话框(Composable)") } ) fun getWrapContentBottomSheetAction(plugin: PluggableApkPlugin) = DefaultComposableAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "非全屏底部弹窗(Composable)", id = "ComposableWrapContentBottomSheet", location = VisualAction.Location.WrapContentBottomSheet, content = { ability -> Text(text = "非全屏底部弹窗(Composable)") }) fun getFullscreenDialogAction(plugin: PluggableApkPlugin) = DefaultComposableAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "全屏对话框(Composable)", id = "ComposableFullscreenDialog", location = VisualAction.Location.FullscreenDialog, content = { ability -> Text(text = "全屏对话框(Composable)") } ) fun getFullscreenBottomSheetAction(plugin: PluggableApkPlugin) = DefaultComposableAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "全屏底部弹窗(Composable)", id = "ComposableFullscreenBottomSheet", location = VisualAction.Location.FullscreenBottomSheet, content = { Text(text = "全屏底部弹窗(Composable)") } ) fun getWrapContentFloatyAction(plugin: PluggableApkPlugin) = DefaultComposableAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "非全屏悬浮窗口(Composable)", id = "ComposableWrapContentFloaty", location = VisualAction.Location.WrapContentFloaty, content = { ability -> Text(text = "非全屏悬浮窗口(Composable)") } ) fun getFullscreenFloatyAction(plugin: PluggableApkPlugin) = DefaultComposableAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "全屏悬浮窗口(Composable)", id = "ComposableFullscreenFloaty", location = VisualAction.Location.FullscreenFloaty, content = { ability -> Text(text = "全屏悬浮窗口(Composable)") } ) }
  1. 继续编写测试用的动作, 下面是 View 实现的动作

package com.m8test.plugins.common.action import android.widget.TextView import com.m8test.action.api.VisualAction import com.m8test.action.impl.DefaultViewAction import com.m8test.plugin.api.PluggableApkPlugin /** * Description TODO * * @date 2025/01/01 12:26:18 * @author M8Test, [email protected], https://m8test.com */ object ViewActions { fun getWrapContentDialogAction(plugin: PluggableApkPlugin) = DefaultViewAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "非全屏对话框(View)", id = "ViewWrapContentDialog", location = VisualAction.Location.WrapContentDialog, viewCreator = { ability -> TextView(ability.getContext()).apply { text = "非全屏对话框(View)" } } ) fun getWrapContentBottomSheetAction(plugin: PluggableApkPlugin) = DefaultViewAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "非全屏底部弹窗(View)", id = "ViewWrapContentBottomSheet", location = VisualAction.Location.WrapContentBottomSheet, viewCreator = { ability -> TextView(ability.getContext()).apply { text = "非全屏底部弹窗(View)" } } ) fun getFullscreenDialogAction(plugin: PluggableApkPlugin) = DefaultViewAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "全屏对话框(View)", id = "ViewFullscreenDialog", location = VisualAction.Location.FullscreenDialog, viewCreator = { ability -> TextView(ability.getContext()).apply { text = "全屏对话框(View)" } } ) fun getFullscreenBottomSheetAction(plugin: PluggableApkPlugin) = DefaultViewAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "全屏底部弹窗(View)", id = "ViewFullscreenBottomSheet", location = VisualAction.Location.FullscreenBottomSheet, viewCreator = { ability -> TextView(ability.getContext()).apply { text = "全屏底部弹窗(View)" } } ) fun getWrapContentFloatyAction(plugin: PluggableApkPlugin) = DefaultViewAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "非全屏悬浮窗口(View)", id = "ViewWrapContentFloaty", location = VisualAction.Location.WrapContentFloaty, viewCreator = { ability -> TextView(ability.getContext()).apply { text = "非全屏悬浮窗口(View)" } } ) fun getFullscreenFloatyAction(plugin: PluggableApkPlugin) = DefaultViewAction( resources = plugin.getResources(), iconId = R.drawable.ic_launcher, name = "全屏悬浮窗口(View)", id = "ViewFullscreenFloaty", location = VisualAction.Location.FullscreenFloaty, viewCreator = { ability -> TextView(ability.getContext()).apply { text = "全屏悬浮窗口(View)" } } ) }
Last modified: 29 April 2025