在这里插入图片描述
帧率直接影响游戏体验。高帧率能带来更顺滑的画面、也更容易压枪和跟枪。

这篇内容我按“项目里怎么落地”的方式写:我们在助手里做一个「帧率优化」页面,把常见的设置项整理出来,再根据设备档位给一句可执行的推荐方案,最后让用户可以勾选自己已经做过的优化(方便对照)。

为了不让文章看起来像堆代码,下面的代码都会刻意拆成小段,并且每段代码后面紧跟解释

帧率优化建议

class OptimizationTip {
  final String title;
  final String description;
  final String impact;
  final String difficulty;

  const OptimizationTip({
    required this.title,
    required this.description,
    required this.impact,
    required this.difficulty,
  });
}

上面先把“建议”抽成一个小模型类,而不是直接用 Map<String, dynamic>。项目里这种列表会被 UI 重复读取很多次,模型化之后字段更直观,也更不容易写错 key。

另外 OptimizationTipconst 构造,对这种“几乎不变的静态配置”挺合适:滚动的时候少创建对象,心里也踏实。

下面两段代码都是同一个 FrameRateOptimization 类里的片段,我在文档里只摘了关键部分(避免整段贴上来太长)。

class FrameRateOptimization {
  static const optimizationTips = <OptimizationTip>[
    OptimizationTip(
      title: '降低画质',
      description: '将画质设置为低或中等',
      impact: '帧率提升 30-50%',
      difficulty: '简单',
    ),
    OptimizationTip(
      title: '关闭阴影',
      description: '禁用实时阴影渲染',
      impact: '帧率提升 20-30%',
      difficulty: '简单',
    ),
  ];
}

这里我把列表按文档习惯做了摘录,只保留两条示例。真实工程里剩余项我会继续按这个结构补齐。

这类建议基本不变,用 static const 更省心;如果后面要做“按收益排序/按难度筛选”,再把 impactdifficulty 改成结构化字段也不迟。

class FrameRateOptimization {
  static String getDeviceRecommendation(String deviceType) {
    switch (deviceType) {
      case '高端设备':
        return '可以开启所有特效,优先画质;但遇到团战掉帧时,先关阴影。';
      case '中端设备':
        return '建议以“稳定”为主:中等画质 + 关阴影/后处理,帧率更稳。';
      case '低端设备':
        return '先把分辨率和画质拉下来,再考虑视距;能稳 40-60 帧比偶尔 90 帧更实用。';
      default:
        return '根据实际情况调整设置,优先保证帧率稳定。';
    }
  }
}

推荐文案我会写得更“像人话”一点,避免只有空泛的“开/关”,也更贴近玩家真实的调参路径。

  • 一个小经验
    • 很多玩家会盯“最高帧率”,但真正影响手感的是帧时间波动。所以建议里我会强调“稳”。

帧率优化页面

class FrameRateOptimizationPage extends StatefulWidget {
  const FrameRateOptimizationPage({Key? key}) : super(key: key);

  
  State<FrameRateOptimizationPage> createState() => 
    _FrameRateOptimizationPageState();
}

class _FrameRateOptimizationPageState extends State<FrameRateOptimizationPage> {
  String _selectedDevice = '中端设备';
  final Set<int> _appliedTips = {};
}

页面状态我只保留两样:

  • _selectedDevice:当前设备档位(高/中/低)。
  • _appliedTips:用户已“应用”的建议索引集合,用 Set 是为了查询 contains 更直接。
class _FrameRateOptimizationPageState extends State<FrameRateOptimizationPage> {
  void _toggleTip(int index) {
    setState(() {
      if (_appliedTips.contains(index)) {
        _appliedTips.remove(index);
      } else {
        _appliedTips.add(index);
      }
    });
  }
}

把“点一下切换状态”的逻辑抽出来,后面卡片里就不需要塞一坨 setState

  • 为什么要抽方法
    • UI 代码更干净;后期你要加埋点、加 Toast、或者加“已应用数量”的统计,也有落点。

Widget build(BuildContext context) {
  final recommendation = FrameRateOptimization.getDeviceRecommendation(
    _selectedDevice,
  );

  return Scaffold(
    appBar: AppBar(
      title: const Text('帧率优化'),
      backgroundColor: const Color(0xFF2D2D2D),
    ),
    backgroundColor: const Color(0xFF1A1A1A),
    body: SingleChildScrollView(
      padding: EdgeInsets.all(16.w),
      child: Column(
        children: [
          _buildDeviceSelector(),
          _buildRecommendationCard(recommendation),
          _buildOptimizationTips(),
        ],
      ),
    ),
  );
}

这个 build 我还是用 SingleChildScrollView + Column,原因很现实:建议项就几条,别过度设计。

如果你后面把建议扩到二三十条(甚至要做分类),再换 ListView.builderSliverList,避免一次性构建太多 Widget。

Widget _buildDeviceSelector() {
  return Row(
    children: ['高端设备', '中端设备', '低端设备'].map((device) {
      final selected = _selectedDevice == device;

      return Expanded(
        child: GestureDetector(
          onTap: () => setState(() => _selectedDevice = device),
          child: Container(
            decoration: BoxDecoration(
              color: selected ? const Color(0xFFFF6B35) : Colors.white10,
              borderRadius: BorderRadius.circular(6.r),
            ),
            child: Text(device, textAlign: TextAlign.center),
          ),
        ),
      );
    }).toList(),
  );
}

设备选择器我在文档里只留“核心逻辑”:三等分按钮 + 点击切档位。真实项目里外层还会包一层 Card/Padding、文字也会加上 TextStyle,但那部分属于 UI 细节,放在文章里反而显得啰嗦。

Widget _buildRecommendationCard(String recommendation) {
  return Card(
    color: const Color(0xFF2D2D2D),
    child: Container(
      width: double.infinity,
      padding: EdgeInsets.all(16.w),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12.r),
        gradient: const LinearGradient(
          colors: [Color(0xFF4CAF50), Color(0xFF66BB6A)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
      ),
      child: Text(
        recommendation,
        style: TextStyle(
          color: Colors.white,
          fontSize: 13.sp,
          height: 1.6,
        ),
      ),
    ),
  );
}

推荐卡片我故意做得简单:只展示一句“能执行的建议”。

  • 为什么不放太多内容
    • 用户来这里通常是“想快速抄作业”,先让他一眼看到结论更重要。
Widget _buildTipContent(OptimizationTip tip) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(
        tip.title,
        style: TextStyle(
          color: Colors.white,
          fontSize: 14.sp,
          fontWeight: FontWeight.bold,
        ),
      ),
      SizedBox(height: 4.h),
      Text(
        tip.description,
        style: TextStyle(
          color: Colors.white70,
          fontSize: 12.sp,
        ),
      ),
    ],
  );
}

建议卡片里最核心的是标题和描述,我先把这一段抽成 _buildTipContent。后面要把 impactdifficulty 塞进来时,就不会在一坨 widget 树里来回翻。

Widget _buildTipAction(int index) {
  final isApplied = _appliedTips.contains(index);

  return GestureDetector(
    onTap: () => _toggleTip(index),
    child: Container(
      padding: EdgeInsets.all(8.w),
      decoration: BoxDecoration(
        color: isApplied ? const Color(0xFF4CAF50) : Colors.white10,
        borderRadius: BorderRadius.circular(6.r),
      ),
      child: Icon(
        isApplied ? Icons.check : Icons.add,
        color: Colors.white,
        size: 20.sp,
      ),
    ),
  );
}

右侧按钮单独拆出来,逻辑就集中在“是否已应用”这一件事上,代码更好读,也方便你后面加埋点。

  • 交互上
    • 已应用是绿色 check,未应用是 add,用户不需要再看文字就懂。
Widget _buildOptimizationTips() {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      const Text('优化建议'),
      ...List.generate(
        FrameRateOptimization.optimizationTips.length,
        (index) {
          final tip = FrameRateOptimization.optimizationTips[index];
          return Row(
            children: [
              Expanded(child: _buildTipContent(tip)),
              _buildTipAction(index),
            ],
          );
        },
      ),
    ],
  );
}

列表渲染这块我也按文档做了“去装饰化”处理:你看核心就是 List.generate 把数据映射成一行(内容 + 按钮)。真实项目里我会再包 Card、加 margin/padding、加颜色,但那些都属于“样式堆料”。

如果建议项后面涨到 20+ 条,就别犟了,直接 ListView.builder;再复杂一点(搜索/筛选/排序)就把数据和筛选逻辑挪到状态管理里,页面只负责展示。

小结

这页的目标其实很简单:把“该怎么调”讲清楚,并让用户能对照着一步步做。

  • 建议写法

    • 先给一句可执行的推荐方案(降低决策成本)。
    • 再列出细项,允许用户逐条打勾(降低遗忘成本)。
  • 体验上最容易踩的坑

    • 别把“提升幅度”写得太死,实际会受地图/团战/温控影响。
    • 别只追峰值帧率,稳定比数字好看更重要。

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

Logo

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

更多推荐