Flutter for OpenHarmony 实战:魔方应用UI设计与交互优化


欢迎加入开源鸿蒙跨平台社区: 开源鸿蒙跨平台开发者社区

前言

在这里插入图片描述

魔方应用的用户界面设计直接影响用户的使用体验。本文将详细介绍魔方应用的UI设计原则、交互优化策略、视觉反馈系统、主题切换功能以及响应式布局设计。

一、UI设计原则

在这里插入图片描述

1.1 布局设计


Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('魔方应用'),
      actions: [
        IconButton(icon: const Icon(Icons.refresh), onPressed: shuffleCube),
        IconButton(icon: const Icon(Icons.history), onPressed: showHistory),
      ],
    ),
    body: Column(
      children: [
        Expanded(
          child: Center(
            child: CustomPaint(
              size: const Size(400, 400),
              painter: RubiksCubePainter(cube: cube),
            ),
          ),
        ),
        _buildControlPanel(),
      ],
    ),
  );
}

使用垂直布局,上部是魔方展示区,下部是控制面板。

1.2 控制面板

Widget _buildControlPanel() {
  return Container(
    padding: const EdgeInsets.all(16),
    child: Column(
      children: [
        Text('移动次数: $moveCount'),
        const SizedBox(height: 16),
        _buildFaceSelector(),
        const SizedBox(height: 16),
        _buildRotationButtons(),
      ],
    ),
  );
}

控制面板包含移动计数、面选择器和旋转按钮。

1.3 面选择器

Widget _buildFaceSelector() {
  return Wrap(
    spacing: 8,
    runSpacing: 8,
    children: List.generate(6, (index) {
      return ChoiceChip(
        label: Text(getFaceName(index)),
        selected: selectedFace == index,
        onSelected: (selected) {
          setState(() {
            selectedFace = selected ? index : null;
          });
        },
        selectedColor: getFaceColor(index),
        labelStyle: TextStyle(
          color: selectedFace == index ? Colors.white : Colors.black,
        ),
      );
    }),
  );
}

使用ChoiceChip实现面选择,选中时显示对应颜色。

二、交互优化

2.1 手势识别

GestureDetector(
  onPanStart: _handlePanStart,
  onPanUpdate: _handlePanUpdate,
  onPanEnd: _handlePanEnd,
  onTapUp: _handleTap,
  child: CustomPaint(...),
)

支持多种手势:点击选择、滑动旋转。

2.2 旋转手势

Offset? startAngle;
double cumulativeAngle = 0;

void _handlePanStart(DragStartDetails details) {
  if (selectedFace == null) return;
  final center = getFaceCenter(selectedFace!);
  startAngle = details.localPosition - center;
  cumulativeAngle = 0;
}

void _handlePanUpdate(DragUpdateDetails details) {
  if (selectedFace == null || startAngle == null) return;
  final center = getFaceCenter(selectedFace!);
  final current = details.localPosition - center;

  final angle = (current.direction - startAngle!.direction);
  cumulativeAngle += angle;

  if (cumulativeAngle.abs() >= pi / 2) {
    if (cumulativeAngle > 0) {
      rotateFaceClockwise(selectedFace!);
    } else {
      rotateFaceCounterClockwise(selectedFace!);
    }
    cumulativeAngle = 0;
  }
}

滑动45度触发旋转,提供直观的交互体验。

2.3 双击重置

void _handleDoubleTap() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('重置魔方'),
      content: const Text('确定要重置魔方吗?'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () {
            initCube();
            Navigator.pop(context);
          },
          child: const Text('确定'),
        ),
      ],
    ),
  );
}

双击触发重置对话框,防止误操作。

三、视觉反馈系统

3.1 选中高亮

void paint(Canvas canvas, Size size) {
  // 绘制魔方
  for (int face = 0; face < 6; face++) {
    _drawFace(canvas, face, getFaceOffset(face), cellSize);

    // 绘制选中边框
    if (face == selectedFace) {
      final offset = getFaceOffset(face);
      final highlightPaint = Paint()
        ..color = Colors.yellow
        ..strokeWidth = 4
        ..style = PaintingStyle.stroke;

      canvas.drawRect(
        Rect.fromLTWH(offset.dx, offset.dy, cellSize * 3, cellSize * 3),
        highlightPaint,
      );
    }
  }
}

选中的面显示黄色高亮边框。

3.2 旋转动画

double rotationAngle = 0;
bool isAnimating = false;

void animateRotation(int face, bool clockwise) {
  isAnimating = true;
  final targetAngle = clockwise ? pi / 2 : -pi / 2;

  AnimationController(duration: const Duration(milliseconds: 300), vsync: this)
    ..addListener(() {
      setState(() {
        rotationAngle = targetAngle * this.value;
      });
    }
    ..addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        isAnimating = false;
        // 实际旋转数据
        if (clockwise) {
          rotateFaceClockwise(face);
        } else {
          rotateFaceCounterClockwise(face);
        }
      }
    })
    ..forward();
}

使用AnimationController实现旋转动画。

3.3 成功提示

void showSuccessDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('恭喜!'),
      content: Text('你成功还原了魔方!\n移动次数: $moveCount'),
      actions: [
        TextButton(
          onPressed: () {
            Navigator.pop(context);
            shuffleCube();
          },
          child: const Text('再来一次'),
        ),
      ],
    ),
  );
}

完成时显示祝贺对话框。

四、主题系统

4.1 主题定义

final lightTheme = ThemeData(
  brightness: Brightness.light,
  primaryColor: Colors.blue,
  scaffoldBackgroundColor: Colors.grey.shade100,
);

final darkTheme = ThemeData(
  brightness: Brightness.dark,
  primaryColor: Colors.blue.shade700,
  scaffoldBackgroundColor: Colors.grey.shade900,
);

定义明暗两种主题。

4.2 主题切换

bool isDarkMode = false;

void toggleTheme() {
  setState(() {
    isDarkMode = !isDarkMode;
  });
}


Widget build(BuildContext context) {
  return MaterialApp(
    theme: isDarkMode ? darkTheme : lightTheme,
    home: RubiksCubeHome(),
  );
}

提供主题切换功能。

五、响应式布局

5.1 屏幕适配

double getCellSize(BuildContext context) {
  final screenWidth = MediaQuery.of(context).size.width;
  final screenHeight = MediaQuery.of(context).size.height;
  final minDimension = min(screenWidth, screenHeight);

  return minDimension / 10;
}

根据屏幕尺寸动态调整单元格大小。

5.2 方向适配

Widget build(BuildContext context) {
  final orientation = MediaQuery.of(context).orientation;

  if (orientation == Orientation.portrait) {
    return _buildPortraitLayout();
  } else {
    return _buildLandscapeLayout();
  }
}

根据屏幕方向调整布局。

六、辅助功能

6.1 语音提示

void announceMove(int face, bool clockwise) {
  // 使用TTS朗读移动
}

为视障用户提供语音提示。

6.2 颜色盲模式

bool colorBlindMode = false;

Color getAccessibleColor(int face) {
  if (!colorBlindMode) {
    return getFaceColor(face);
  }
  // 使用纹理或符号区分
  return getColorBlindColor(face);
}

为色盲用户提供替代颜色方案。

总结

本文详细介绍了魔方应用的UI设计和交互优化。从布局设计到交互优化,从视觉反馈到主题系统,每个技术点都直接影响应用的用户体验。通过这些技术的综合应用,实现了美观易用且功能完整的魔方应用。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐