Flutter for OpenHarmony Web开发助手App实战:快捷键参考

快捷键能大幅提升开发效率。今天我们实现一个常用快捷键参考页面。

需求拆一下(做出来像"项目里的一个页面")

  • 按工具分类:VS Code、Chrome DevTools、Git 等。
  • 支持快速检索:输入关键词过滤(按键/描述都能搜)。
  • 交互要顺手:点一下按键组合直接复制,省得手抄。
  • 样式适配:Web / PC 屏幕宽度变化大,页面间距和字号最好做响应式。

下面我按项目里常见的拆法写:先定义数据结构,再做列表单元,最后拼成页面。

1) 数据结构:别用 Map<String, dynamic> 硬扛

dart 复制代码
class ShortcutEntry {
  final String keys;
  final String desc;

  const ShortcutEntry({
    required this.keys,
    required this.desc,
  });
}

const Map<String, List<ShortcutEntry>> kShortcuts = {
  'VS Code': [
    ShortcutEntry(keys: 'Ctrl + P', desc: '快速打开文件'),
    ShortcutEntry(keys: 'Ctrl + Shift + P', desc: '命令面板'),
    ShortcutEntry(keys: 'Ctrl + /', desc: '切换注释'),
    ShortcutEntry(keys: 'Alt + ↑/↓', desc: '移动行'),
    ShortcutEntry(keys: 'Ctrl + D', desc: '选择下一个匹配项'),
  ],
  'Chrome DevTools': [
    ShortcutEntry(keys: 'F12', desc: '打开开发者工具'),
    ShortcutEntry(keys: 'Ctrl + Shift + C', desc: '选择元素'),
    ShortcutEntry(keys: 'Ctrl + Shift + J', desc: '打开控制台'),
    ShortcutEntry(keys: 'Ctrl + R', desc: '刷新页面'),
  ],
};

说明

  • 类型明确 ShortcutEntry 这个小模型能让代码更稳,后面做搜索、排序、埋点都方便。
  • 数据可替换 这里先用 const 做内置数据;后续如果要从接口下发,把 kShortcuts 换成仓库层(Repository)返回的数据结构就行。

2) 列表单元:展示 + 一键复制

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class ShortcutTile extends StatelessWidget {
  final ShortcutEntry entry;

  const ShortcutTile({
    super.key,
    required this.entry,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.only(bottom: 8.h),
      child: ListTile(
        onTap: () async {
          await Clipboard.setData(ClipboardData(text: entry.keys));
          if (!context.mounted) return;
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('已复制:${entry.keys}')),
          );
        },
        leading: Container(
          padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
          decoration: BoxDecoration(
            color: Colors.grey.shade200,
            borderRadius: BorderRadius.circular(4.r),
          ),
          child: Text(
            entry.keys,
            style: TextStyle(
              fontFamily: 'monospace',
              fontSize: 12.sp,
              fontWeight: FontWeight.w700,
            ),
          ),
        ),
        title: Text(entry.desc),
        trailing: Icon(Icons.copy, size: 18.sp, color: Colors.grey.shade600),
      ),
    );
  }
}

说明

  • 点击复制Clipboard.setData 是 Web/桌面常用的交互,配合 SnackBar 给到反馈,用户会觉得"这个页面能用"。
  • mounted 判断 await 之后补一个 context.mounted,避免页面刚好被销毁时弹 SnackBar 报错,这个在真实项目里挺常见。
  • 视觉层次 左侧按键用等宽字体 + 浅灰底,扫一眼就能区分"按键组合"和"说明文字"。

3) 分组块:标题 + 列表

dart 复制代码
class ShortcutSection extends StatelessWidget {
  final String title;
  final List<ShortcutEntry> items;

  const ShortcutSection({
    super.key,
    required this.title,
    required this.items,
  });

  @override
  Widget build(BuildContext context) {
    if (items.isEmpty) return const SizedBox.shrink();

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: EdgeInsets.symmetric(vertical: 12.h),
          child: Text(
            title,
            style: TextStyle(
              fontSize: 18.sp,
              fontWeight: FontWeight.w800,
              color: Colors.blue,
            ),
          ),
        ),
        for (final entry in items) ShortcutTile(entry: entry),
        SizedBox(height: 12.h),
      ],
    );
  }
}

说明

  • 空分组直接隐藏 搜索过滤后可能出现某些工具没有匹配项,items.isEmpty 时返回空控件,列表会更干净。
  • for 循环代替 map 在 UI 代码里我更偏向用 for,断点调试更直观,阅读成本也低一些。

4) 页面本体:搜索过滤 + 渲染

dart 复制代码
class ShortcutsPage extends StatefulWidget {
  const ShortcutsPage({super.key});

  @override
  State<ShortcutsPage> createState() => _ShortcutsPageState();
}

class _ShortcutsPageState extends State<ShortcutsPage> {
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  Map<String, List<ShortcutEntry>> _filter(String q) {
    final query = q.trim().toLowerCase();
    if (query.isEmpty) return kShortcuts;

    return {
      for (final e in kShortcuts.entries)
        e.key: e.value
            .where((it) =>
                it.keys.toLowerCase().contains(query) ||
                it.desc.toLowerCase().contains(query))
            .toList(),
    };
  }

  @override
  Widget build(BuildContext context) {
    final filtered = _filter(_controller.text);

    return Scaffold(
      appBar: AppBar(title: const Text('快捷键参考')),
      body: ListView(
        padding: EdgeInsets.all(16.w),
        children: [
          TextField(
            controller: _controller,
            decoration: InputDecoration(
              hintText: '搜索:比如 Ctrl、F12、控制台...',
              prefixIcon: const Icon(Icons.search),
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(10.r),
              ),
              isDense: true,
            ),
            onChanged: (_) => setState(() {}),
          ),
          SizedBox(height: 12.h),
          for (final entry in filtered.entries)
            ShortcutSection(title: entry.key, items: entry.value),
        ],
      ),
    );
  }
}

说明

  • 过滤逻辑放在函数里 _filter 单独抽出来,后面要加"只看收藏""按热度排序"时不至于把 build 搞得一团乱。
  • 过滤策略 同时对 keys / desc 过滤;并且用 trim + toLowerCase,能覆盖大多数输入习惯。
  • 不要一次性 setState 太重 这里直接 onChanged => setState 是最简单的写法;如果数据变多(比如几百条),再加个 debounce 就够用了。

5) 接入到项目:ScreenUtil 初始化(常见遗漏点)

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(375, 812),
      minTextAdapt: true,
      builder: (_, __) {
        return MaterialApp(
          home: const ShortcutsPage(),
        );
      },
    );
  }
}

说明

  • 先初始化再用 .w/.h/.sp flutter_screenutil 最容易踩坑的就是:没 ScreenUtilInit 就开始用 16.w,结果某些端上尺寸会不对。
  • designSize 怎么选 这里用常见的 iPhone X 尺寸只是示例;你项目里如果有设计稿基准,按设计稿来就行。

一点小经验(写在最后)

  • 快捷键会变 这类内容维护成本不低,最好把数据结构留好口子,后面能换成配置文件或后台下发。
  • 复制反馈要轻 SnackBar 不要太重,也别弹 Dialog,不然用户会觉得"打断操作"。
  • Web 上更需要搜索 屏幕能放很多内容,但用户更倾向于"搜一下马上找到",检索框属于必需品。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
星星在线22 分钟前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒1 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x2 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
京东云开发者2 小时前
京东市民服务又“上新”!这次是黑龙江“龙易办”
前端
袋鱼不重3 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
Fireworks3 小时前
深入vue3源码解读 -- 1、响应式的基础概念
前端
程序员黑豆3 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程
hunterandroid3 小时前
文件存储:内部存储与外部存储
前端
NorBugs4 小时前
飞机大战 Low 版 (Made in AI)
前端
angerdream4 小时前
Android手把手编写儿童手机远程监控App之agentweb如何实现全屏
前端