Flutter for OpenHarmony 实战:双控制系统实现(按钮+键盘)
一、前言
为了提供良好的用户体验,我们实现了双控制系统:屏幕触控按钮和键盘控制。触控按钮方便移动端操作,键盘控制适合PC端开发调试。本文将详细讲解两种控制方式的实现。

二、屏幕按钮控制

2.1 按钮布局设计
采用经典的D-Pad布局(十字方向键):
dart
if (!isGameOver)
Container(
padding: const EdgeInsets.all(20),
child: Column(
children: [
// 上按钮(单独一行)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildControlButton(Icons.arrow_upward, () {
_changeDirection(Direction.up);
}),
],
),
const SizedBox(height: 10),
// 左、暂停、右按钮(一行三个)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildControlButton(Icons.arrow_back, () {
_changeDirection(Direction.left);
}),
const SizedBox(width: 20),
_buildControlButton(
isPaused ? Icons.play_arrow : Icons.pause,
() {
setState(() {
isPaused = !isPaused;
});
},
),
const SizedBox(width: 20),
_buildControlButton(Icons.arrow_forward, () {
_changeDirection(Direction.right);
}),
],
),
const SizedBox(height: 10),
// 下按钮(单独一行)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildControlButton(Icons.arrow_downward, () {
_changeDirection(Direction.down);
}),
],
),
],
),
),
布局特点:
- 上按钮:居中独占一行
- 中间行:左、暂停、右三个按钮
- 下按钮:居中独占一行
- 形成十字键布局
2.2 圆形按钮样式
dart
Widget _buildControlButton(IconData icon, VoidCallback onPressed) {
return Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(12),
),
child: IconButton(
icon: Icon(icon, color: Colors.white, size: 30),
onPressed: onPressed,
),
);
}
样式特点:
- 尺寸:60×60像素
- 颜色:绿色背景、白色图标
- 圆角:12像素
- 图标大小:30像素
2.3 点击事件处理
dart
_buildControlButton(Icons.arrow_upward, () {
_changeDirection(Direction.up);
})
事件流程:
- 用户点击按钮
- 触发onPressed回调
- 调用_changeDirection()
- 更新nextDirection
- 下次_update()时应用新方向
三、KeyboardListener键盘监听
3.1 KeyboardListener组件使用
dart
KeyboardListener(
focusNode: _focusNode,
onKeyEvent: _handleKeyEvent,
child: Scaffold(
// 游戏内容
),
)
组件说明:
- focusNode:焦点控制器
- onKeyEvent:键盘事件回调
- child:子组件(游戏界面)
3.2 KeyEvent事件类型
Flutter中KeyEvent有两种类型:
dart
void _handleKeyEvent(KeyEvent event) {
if (event is KeyDownEvent) { // 按键按下
// 处理按键
}
if (event is KeyUpEvent) { // 按键抬起
// 一般不处理
}
}
事件类型:
- KeyDownEvent:按键按下时触发
- KeyUpEvent:按键抬起时触发
- 我们只处理KeyDownEvent
3.3 FocusNode焦点管理
dart
late final FocusNode _focusNode;
@override
void initState() {
super.initState();
_focusNode = FocusNode();
_focusNode.requestFocus(); // 请求焦点
}
@override
void dispose() {
_focusNode.dispose(); // 释放焦点
super.dispose();
}
FocusNode作用:
- 管理组件焦点状态
- requestFocus():获取焦点
- 有焦点才能接收键盘事件
重要提示:
- 必须调用requestFocus()
- 否则键盘事件无法触发
四、多键位兼容实现
4.1 WASD键位映射
dart
case LogicalKeyboardKey.keyW:
_changeDirection(Direction.up);
return;
case LogicalKeyboardKey.keyS:
_changeDirection(Direction.down);
return;
case LogicalKeyboardKey.keyA:
_changeDirection(Direction.left);
return;
case LogicalKeyboardKey.keyD:
_changeDirection(Direction.right);
return;

WASD布局:
W
A S D
- W:上
- S:下
- A:左
- D:右
4.2 方向键映射
dart
case LogicalKeyboardKey.arrowUp:
_changeDirection(Direction.up);
return;
case LogicalKeyboardKey.arrowDown:
_changeDirection(Direction.down);
return;
case LogicalKeyboardKey.arrowLeft:
_changeDirection(Direction.left);
return;
case LogicalKeyboardKey.arrowRight:
_changeDirection(Direction.right);
return;
方向键布局:
↑
← ↓ →

4.3 LogicalKeyboardKey使用
Flutter的LogicalKeyboardKey枚举提供跨平台的按键映射:
dart
import 'package:flutter/services.dart';
switch (event.logicalKey) {
case LogicalKeyboardKey.keyW:
case LogicalKeyboardKey.arrowUp:
_changeDirection(Direction.up);
return;
// ...
}
优势:
- 跨平台统一API
- 自动处理不同键盘布局
- 支持物理键盘和软键盘
五、辅助功能实现

5.1 空格暂停/继续
dart
case LogicalKeyboardKey.space:
setState(() {
isPaused = !isPaused;
});
return;
功能说明:
- 按下空格键
- 切换暂停状态
- true→false或false→true
- 定时器在_update()中检查isPaused
5.2 R键重新开始
dart
case LogicalKeyboardKey.keyR:
if (isGameOver) {
_initGame();
}
return;
功能说明:
- 游戏结束时才有效
- 按R键重新开始
- 调用_initGame()重置状态
5.3 焦点自动获取
dart
@override
void initState() {
super.initState();
_focusNode = FocusNode();
_focusNode.requestFocus(); // 自动获取焦点
_initGame();
}
为什么需要自动获取焦点?
- 应用启动后,焦点可能不在游戏组件
- 没有焦点就无法接收键盘事件
- requestFocus()确保游戏能接收输入
六、完整代码实现
dart
class _GameHomePageState extends State<GameHomePage> {
late final FocusNode _focusNode;
@override
void initState() {
super.initState();
_focusNode = FocusNode();
_focusNode.requestFocus();
_initGame();
}
void _changeDirection(Direction newDirection) {
if ((direction == Direction.up && newDirection != Direction.down) ||
(direction == Direction.down && newDirection != Direction.up) ||
(direction == Direction.left && newDirection != Direction.right) ||
(direction == Direction.right && newDirection != Direction.left)) {
nextDirection = newDirection;
}
}
void _handleKeyEvent(KeyEvent event) {
if (event is KeyDownEvent) {
switch (event.logicalKey) {
case LogicalKeyboardKey.keyW:
case LogicalKeyboardKey.arrowUp:
_changeDirection(Direction.up);
return;
case LogicalKeyboardKey.keyS:
case LogicalKeyboardKey.arrowDown:
_changeDirection(Direction.down);
return;
case LogicalKeyboardKey.keyA:
case LogicalKeyboardKey.arrowLeft:
_changeDirection(Direction.left);
return;
case LogicalKeyboardKey.keyD:
case LogicalKeyboardKey.arrowRight:
_changeDirection(Direction.right);
return;
case LogicalKeyboardKey.space:
setState(() {
isPaused = !isPaused;
});
return;
case LogicalKeyboardKey.keyR:
if (isGameOver) {
_initGame();
}
return;
}
}
}
@override
void dispose() {
gameTimer?.cancel();
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return KeyboardListener(
focusNode: _focusNode,
onKeyEvent: _handleKeyEvent,
child: Scaffold(
body: Column(
children: [
// 游戏画面
Expanded(
child: CustomPaint(
painter: GamePainter(...),
),
),
// 控制按钮
if (!isGameOver)
Container(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildControlButton(Icons.arrow_upward, () {
_changeDirection(Direction.up);
}),
],
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildControlButton(Icons.arrow_back, () {
_changeDirection(Direction.left);
}),
const SizedBox(width: 20),
_buildControlButton(
isPaused ? Icons.play_arrow : Icons.pause,
() {
setState(() {
isPaused = !isPaused;
});
},
),
const SizedBox(width: 20),
_buildControlButton(Icons.arrow_forward, () {
_changeDirection(Direction.right);
}),
],
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildControlButton(Icons.arrow_downward, () {
_changeDirection(Direction.down);
}),
],
),
const SizedBox(height: 10),
Text(
'或使用 WASD / 方向键控制',
style: TextStyle(
fontSize: 14,
color: Colors.grey[400],
),
),
],
),
),
],
),
),
);
}
Widget _buildControlButton(IconData icon, VoidCallback onPressed) {
return Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(12),
),
child: IconButton(
icon: Icon(icon, color: Colors.white, size: 30),
onPressed: onPressed,
),
);
}
}
七、功能演示
操作演示:
- 启动游戏,蛇自动向右移动
- 点击上按钮或按W键:蛇向上移动
- 点击暂停按钮或按空格键:游戏暂停
- 游戏结束后,按R键:重新开始
双系统优势:
- 触控按钮:移动端友好
- 键盘控制:PC端开发高效
- 可同时使用,互不冲突
八、总结
本文讲解了双控制系统实现:
- 触控按钮:十字布局,绿色圆角样式
- KeyboardListener:监听键盘事件
- WASD+方向键:多种键位兼容
- 辅助功能:空格暂停、R键重开
关键要点:
- FocusNode必须requestFocus()
- KeyEvent分为KeyDown和KeyUp
- 多键位兼容提升用户体验
下篇预告:《Flutter for OpenHarmony 实战:开发调试中的三个典型Bug》
社区支持
欢迎加入开源 OpenHarmony 跨平台社区,获取更多技术支持和资源:
- 社区论坛 :开源 OpenHarmony 跨平台开发者社区
- 技术交流:参与社区讨论,分享开发经验
如果本文对您有帮助,欢迎点赞、收藏和评论。您的支持是我持续创作的动力!