一、前言

1024 一起专注游戏是在屏幕上画上 N x N 个方格(如 4×4 共 16 个),格子内任意填写上从1开始顺序生成的数字(如 1 ~ 16 共 16 个数字)。游戏时,要求玩家用手指按从小到大(如 1 ~ 16 )的顺序依次指出其位置,按完所有数字后,显示所用的时间(秒)。所用时间越短,注意力水平越高。能够培养注意力集中、分配、控制能力;拓展视幅;加快视频;提高视觉的稳定性、辨别人、定向搜索能力。此游戏为最简单,最有效也是最科学的注意力训练方法。寻找目标数字时,注意力是需要极度集中的,把这短暂的高强度的集中精力过程反复练习,大脑的集中注意力功能就会不断的加固,提高。注意水平越来越高。

同时,1024 一起专注游戏使用了鸿蒙分布式协同技术,在训练小孩子专注力和耐力时,大人也可以一起陪伴训练,只要两台鸿蒙系统手机或一台手机一台平板,大人,小孩就可以同时一起在玩一个游戏,比如大人在其中一台手机上按了一部份小数字,然后点击分布式协同图标,拉起另一台手机的 1024 一起专注游戏,小孩可以接着按大人没有按完的数字,最终显示出所用的时间。

二、 实现效果

  • 开发工具环境下视频:https://www.bilibili.com/video/BV1B34y1m7M5?spm_id_from=333.999.0.0
  • 手机 + 手机环境下视频:https://www.bilibili.com/video/BV1kh411b7QM?spm_id_from=333.999.0.0
  • 手机 + 平板环境下视频:https://www.bilibili.com/video/BV1ov411M7sq?spm_id_from=333.999.0.0

三、创建工程

在这当作你已经安装好最新版本 DevEco-Studio 开发工具, 点击 File -> New -> New Project… 弹出 Create HarmonyOS Project 窗口, 这里我选择空白 Java 模板创建, 上一个视频播放实例是用 JS 写的界面,这个游戏界面就用 Java 来写,还是 JS 写界面快,调试也快些。

四、主界面开发

在展示源代码之前,先介绍一下使用到了 JAVA 哪些组件: DirectionalLayout, TableLayout, DependentLayout, Button, Image, Text, ListContainer, CommonDialog,通过查看 Java UI 参考文档,就可以做出你喜欢的应用了。

先介绍公共类 Java 代码,有了这些公共类,以后做类似功能的应用,可以直接复制公共类文件可以使用:

LogUtil 日志打印类:

public class LogUtil {
    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD000F00, "1024Game");
    private static final String LOG_FORMAT = "%{public}s: %{public}s";


    private LogUtil() {


    }
    public static void debug(String className, String msg) {
        HiLog.debug(LABEL_LOG, LOG_FORMAT, className, msg);
    }
    public static void info(String className, String msg) {
        HiLog.info(LABEL_LOG, LOG_FORMAT, className, msg);
    }
    public static void info(Class<?> classType, final String format, Object... args) {
        String buffMsg = String.format(Locale.ROOT, format, args);
        HiLog.info(LABEL_LOG, LOG_FORMAT, classType == null ? "null" : classType.getSimpleName(), buffMsg);
    }
    public static void error(String tag, String msg) {
        HiLog.error(LABEL_LOG, LOG_FORMAT, tag, msg);
    }
}

SelectDeviceDialog设备选择对话框:

public class SelectDeviceDialog {
    private static final int DIALOG_WIDTH = 840;
    private static final int DIALOG_HEIGHT = 900;
    private CommonDialog commonDialog;


    public SelectDeviceDialog(Context context, List<DeviceInfo> devices, SelectResultListener listener) {
        initView(context, devices, listener);
    }
    private void initView(Context context, List<DeviceInfo> devices, SelectResultListener listener) {
        // 创建一个公共对话框
        commonDialog = new CommonDialog(context);
        // 设置对齐方式居中
        commonDialog.setAlignment(LayoutAlignment.CENTER);
        // 设置对话框尺寸
        commonDialog.setSize(DIALOG_WIDTH, DIALOG_HEIGHT);
        // 设置对话框自动关闭
        commonDialog.setAutoClosable(true);
        // 加载XML布局文件
        Component dialogLayout =
                LayoutScatter.getInstance(context).parse(ResourceTable.Layout_dialog_select_device, null, false);
        // 设置对话框内容
        commonDialog.setContentCustomComponent(dialogLayout);
        // 查找到列表容器
        if (dialogLayout.findComponentById(ResourceTable.Id_list_devices) instanceof ListContainer) {
            // 获取列表容器对象
            ListContainer devicesListContainer =
                    (ListContainer) dialogLayout.findComponentById(ResourceTable.Id_list_devices);
            // 设备列表适配器
            DevicesListAdapter devicesListAdapter = new DevicesListAdapter(devices, context);
            // 设置设备列表容器项提供者
            devicesListContainer.setItemProvider(devicesListAdapter);
            // 设置设备列表项单击事件
            devicesListContainer.setItemClickedListener((listContainer, component, position, id) -> {
                // 回调选择的设备信息
                listener.callBack(devices.get(position));
                // 关闭对话框
                commonDialog.hide();
            });
        }
        dialogLayout.findComponentById(ResourceTable.Id_cancel).setClickedListener(component -> {
            // 关闭对话框
            commonDialog.hide();
        });
    }
    // 显示对话框
    public void show() {
        commonDialog.show();
    }
    /**
     * 内部接口, 选择设备后回调事件
     */
    public interface SelectResultListener {
        void callBack(DeviceInfo deviceInfo);
    }
}

DevicesListAdapter设备列表适配器:

public class DevicesListAdapter extends BaseItemProvider {
    // 开始下标从0开始
    private static final int SUBSTRING_START = 0;
    // 结束下标为4
    private static final int SUBSTRING_END = 4;
    // 设备信息列表
    private List<DeviceInfo> deviceInfoList;
    // 当前上下文
    private Context context;


    // 带参构造方法
    public DevicesListAdapter(List<DeviceInfo> deviceInfoList, Context context) {
        this.deviceInfoList = deviceInfoList;
        this.context = context;
    }
    @Override
    public int getCount() {
        return deviceInfoList == null ? 0 : deviceInfoList.size();
    }
    @Override
    public Object getItem(int i) {
        return Optional.of(deviceInfoList.get(i));
    }
    @Override
    public long getItemId(int i) {
        return i;
    }
    @Override
    public Component getComponent(int i, Component component, ComponentContainer componentContainer) {
        // 定义设备视图内部类
        ViewHolder viewHolder = null;
        // 定义组件
        Component mComponent = component;
        // 组件为空时
        if (mComponent == null) {
            // 查找设备列表项布局XML
            mComponent = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_item_device_list, null, false);
            // 初始化设备视图类
            viewHolder = new ViewHolder();
            // 判断组件布局里是否包含设备名称文本组件
            if (mComponent.findComponentById(ResourceTable.Id_device_name) instanceof Text) {
                // 获取设备列表项布局XML的设备名称文件组件,并赋值给内部类设备视图设备名称属性缓存
                viewHolder.devicesName = (Text) mComponent.findComponentById(ResourceTable.Id_device_name);
            }
            // 判断组件布局里是否包含设备Id文本组件
            if (mComponent.findComponentById(ResourceTable.Id_device_id) instanceof Text) {
                // 获取设备列表项布局XML的设备Id文件组件,并赋值给内部类设备视图设备Id属性缓存
                viewHolder.devicesId = (Text) mComponent.findComponentById(ResourceTable.Id_device_id);
            }
            mComponent.setTag(viewHolder);
        } else {
            // 如果组件不为空, 并且标签包含内部类设备视图
            if (mComponent.getTag() instanceof ViewHolder) {
                // 从组件标签获取出设备视图
                viewHolder = (ViewHolder) mComponent.getTag();
            }
        }
        // 设备视图不为空时
        if (viewHolder != null) {
            // 设置设备名称内容
            viewHolder.devicesName.setText(deviceInfoList.get(i).getDeviceName());
            String deviceId = deviceInfoList.get(i).getDeviceId();
            deviceId = deviceId.substring(SUBSTRING_START, SUBSTRING_END) + "******"
                    + deviceId.substring(deviceId.length() - SUBSTRING_END);
            // 设置设备名称Id
            viewHolder.devicesId.setText(deviceId);
        }
        return mComponent;
    }
    /**
     * 内部类, 设备视图
     */
    private static class ViewHolder {
        // 设备名称
        private Text devicesName;
        // 设备Id
        private Text devicesId;
    }
}

MainAbilitySlice主界面功能讲解 :

主界面主要功能就是用表格布局生成 3×3, 4×4, 5×5, 6×6, 7×7, 8×8, 9×9 七个按钮,点击后跳转游戏界面,初始化相应的数字按钮,用到了 Slice 之间跳转传参数, 源码都有详细注释,有兴趣小伙伴可以到 gitee 查看源码。

PlayAbilitySlice游戏界面功能讲解:

游戏界面主要功能也是用表格布局生成相应主界面传过来的参数按钮,数字显示顺序随机, 分布式协同拉起 GameServiceAbility 游戏服务,并且在点击每个数字按钮时,通过订阅 Event,把当前点到哪个数字,相关变量都接收到,然后更新相应的数据, 源码都有详细注释,有兴趣小伙伴可以到 gitee 查看源码。

GameServiceAbility游戏服务讲解:

游戏服务主要功能是如果请求是 Ability 的,接收到参数后,再流转到其它界面传参;如果是其它请求,接收到参数后,通过公共事件发布出去,让订阅了此事件的 Ability 更新数据,源码都有详细注释,有兴趣小伙伴可以到 gitee 查看源码。

讲解到此了,不要忘记了 config.json 文件的权限配置哦,在 module 下添加

"reqPermissions": [
      {
        "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO"
      },
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC"
      },
      {
        "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE"
      },
      {
        "name": "ohos.permission.READ_USER_STORAGE"
      },
      {
        "name": "ohos.permission.WRITE_USER_STORAGE"
      },
      {
        "name": "ohos.permission.GET_BUNDLE_INFO"
      }
    ]

同时,在游戏界面入口也是要提供动态授权:

private static void grantPermission(Context context) {
   LogUtil.info(TAG, "grantPermission");
   if (context.verifySelfPermission(DISTRIBUTED_DATASYNC) != IBundleManager.PERMISSION_GRANTED) {
       if (context.canRequestPermission(DISTRIBUTED_DATASYNC)) {
           context.requestPermissionsFromUser(new String[] {DISTRIBUTED_DATASYNC}, PERMISSION_CODE);
       }
   }
}

五、总结

有兴趣的小伙伴可以下载源码查看, 项目代码基本都有注释了,游戏规则很简单,就是在界面按顺序点击数字,时间越短,说明注意力越集中。 源码同步到 gitee 码云。

源码在这: https://gitee.com/army16/qin-hong-jun-board