Flutter 与开源鸿蒙 底部弹窗多项选择实现方案全解析

文章目录

  • [Flutter 与开源鸿蒙 底部弹窗多项选择实现方案全解析](#Flutter 与开源鸿蒙 底部弹窗多项选择实现方案全解析)
    • 一、技术背景与应用场景
      • [1.1 底部弹窗多项选择的应用场景](#1.1 底部弹窗多项选择的应用场景)
      • [1.2 Flutter 与开源鸿蒙技术特性对比](#1.2 Flutter 与开源鸿蒙技术特性对比)
    • [二、Flutter 实现底部弹窗多项选择](#二、Flutter 实现底部弹窗多项选择)
      • [2.1 核心组件与 API 说明](#2.1 核心组件与 API 说明)
      • [2.2 完整代码实现](#2.2 完整代码实现)
        • [步骤 1:定义数据模型](#步骤 1:定义数据模型)
        • [步骤 2:构建多选底部弹窗页面](#步骤 2:构建多选底部弹窗页面)
        • [步骤 3:在主页面调用底部弹窗](#步骤 3:在主页面调用底部弹窗)
      • [2.3 关键优化点](#2.3 关键优化点)
    • 三、开源鸿蒙实现底部弹窗多项选择
      • [3.1 核心组件与装饰器说明](#3.1 核心组件与装饰器说明)
      • [3.2 完整代码实现](#3.2 完整代码实现)
        • [步骤 1:定义数据模型](#步骤 1:定义数据模型)
        • [步骤 2:构建主页面与多选底部弹窗](#步骤 2:构建主页面与多选底部弹窗)
        • [步骤 3:配置权限与运行](#步骤 3:配置权限与运行)
      • [3.3 关键优化点](#3.3 关键优化点)
    • [四、Flutter 与开源鸿蒙实现方案对比](#四、Flutter 与开源鸿蒙实现方案对比)
    • 五、常见问题与解决方案
      • [5.1 多选列表数据量大时的性能问题](#5.1 多选列表数据量大时的性能问题)
      • [5.2 选中状态重置问题](#5.2 选中状态重置问题)
      • [5.3 弹窗高度自适应问题](#5.3 弹窗高度自适应问题)
    • 六、总结与拓展
    • 写在最后

Flutter 与开源鸿蒙 底部弹窗多项选择实现方案全解析

在移动端应用开发中,底部弹窗(Bottom Sheet)是一种常用的交互组件,常用于承载筛选、选择等操作,而多项选择 功能则是底部弹窗的高频使用场景,比如文件批量操作、兴趣标签选择等。本文将分别详细讲解 Flutter开源鸿蒙(OpenHarmony) 中底部弹窗多项选择的实现方案,对比两种技术栈的设计思路与编码差异,帮助开发者快速掌握跨平台开发技能。

一、技术背景与应用场景

1.1 底部弹窗多项选择的应用场景

底部弹窗多项选择在移动端应用中十分常见,典型场景包括:

  • 电商 App 的商品筛选(多选品牌、价格区间)
  • 社交 App 的兴趣标签选择
  • 工具类 App 的文件批量删除、移动操作
  • 资讯 App 的频道订阅管理

这种交互模式的优势在于不占用主页面空间,且操作流程清晰,用户可以快速完成多选操作并确认结果。

1.2 Flutter 与开源鸿蒙技术特性对比

特性 Flutter 开源鸿蒙(OpenHarmony)
架构类型 跨平台 UI 框架(自绘引擎) 全场景分布式操作系统(原生框架)
UI 组件实现方式 基于 Widget 组合 基于 ArkUI 声明式 UI
状态管理 StatefulWidget + Provider/BLoC @State + @Link + 状态管理装饰器
跨平台能力 一套代码运行多端(iOS/Android) 一套代码运行多设备(手机/平板/手表等)

二、Flutter 实现底部弹窗多项选择

Flutter 中实现底部弹窗多项选择的核心是 showModalBottomSheet 方法,结合 ListViewGridView 承载选择项,通过状态管理维护选中状态。

2.1 核心组件与 API 说明

  1. showModalBottomSheet:Flutter 内置的底部弹窗展示方法,支持自定义弹窗高度、背景、圆角等属性。
  2. StatefulWidget:用于维护多选列表的选中状态,因为选中项会随用户操作动态变化。
  3. CheckboxListTile:自带复选框的列表项组件,简化多选列表的 UI 开发,也可自定义复选框样式。

2.2 完整代码实现

步骤 1:定义数据模型

首先定义一个标签数据模型,包含标签名称和选中状态:

dart 复制代码
class TagModel {
  final String name;
  bool isSelected;

  TagModel({required this.name, this.isSelected = false});
}
步骤 2:构建多选底部弹窗页面

创建 MultiSelectBottomSheet 组件,继承 StatefulWidget,通过 List<TagModel> 维护标签列表,实现选中状态切换:

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

class MultiSelectBottomSheet extends StatefulWidget {
  // 传入初始标签列表
  final List<TagModel> tagList;
  // 回调函数:返回选中的标签列表
  final Function(List<TagModel>) onConfirm;

  const MultiSelectBottomSheet({
    super.key,
    required this.tagList,
    required this.onConfirm,
  });

  @override
  State<MultiSelectBottomSheet> createState() => _MultiSelectBottomSheetState();
}

class _MultiSelectBottomSheetState extends State<MultiSelectBottomSheet> {
  late List<TagModel> _selectedTags;

  @override
  void initState() {
    super.initState();
    // 初始化选中列表
    _selectedTags = widget.tagList.where((tag) => tag.isSelected).toList();
  }

  // 切换标签选中状态
  void _toggleTagSelection(TagModel tag) {
    setState(() {
      tag.isSelected = !tag.isSelected;
      if (tag.isSelected) {
        _selectedTags.add(tag);
      } else {
        _selectedTags.remove(tag);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      // 设置弹窗高度
      height: MediaQuery.of(context).size.height * 0.6,
      child: Column(
        children: [
          // 弹窗标题
          const Text(
            "选择兴趣标签",
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 16),
          // 多选列表
          Expanded(
            child: ListView.builder(
              itemCount: widget.tagList.length,
              itemBuilder: (context, index) {
                final tag = widget.tagList[index];
                return CheckboxListTile(
                  title: Text(tag.name),
                  value: tag.isSelected,
                  onChanged: (value) {
                    _toggleTagSelection(tag);
                  },
                  // 自定义复选框颜色
                  checkColor: Colors.white,
                  activeColor: Colors.blue,
                );
              },
            ),
          ),
          // 确认与取消按钮
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              TextButton(
                onPressed: () {
                  Navigator.pop(context);
                },
                child: const Text("取消"),
              ),
              const SizedBox(width: 16),
              ElevatedButton(
                onPressed: () {
                  widget.onConfirm(_selectedTags);
                  Navigator.pop(context);
                },
                child: const Text("确认"),
              ),
            ],
          ),
        ],
      ),
    );
  }
}
步骤 3:在主页面调用底部弹窗

在主页面添加一个按钮,点击后通过 showModalBottomSheet 展示多选弹窗:

dart 复制代码
class FlutterMultiSelectDemo extends StatelessWidget {
  // 模拟标签数据
  final List<TagModel> _tagList = [
    TagModel(name: "Flutter"),
    TagModel(name: "OpenHarmony"),
    TagModel(name: "Android"),
    TagModel(name: "iOS"),
    TagModel(name: "前端"),
    TagModel(name: "后端"),
  ];

  FlutterMultiSelectDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Flutter 多项选择底部弹窗"),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            showModalBottomSheet(
              context: context,
              // 设置圆角
              shape: const RoundedRectangleBorder(
                borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
              ),
              builder: (context) {
                return MultiSelectBottomSheet(
                  tagList: _tagList,
                  onConfirm: (selectedTags) {
                    // 处理选中结果
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text("选中了 ${selectedTags.map((e) => e.name).toList()}"),
                      ),
                    );
                  },
                );
              },
            );
          },
          child: const Text("打开多选弹窗"),
        ),
      ),
    );
  }
}

// 程序入口
void main() {
  runApp(const MaterialApp(
    home: FlutterMultiSelectDemo(),
  ));
}

2.3 关键优化点

  1. 列表复用 :使用 ListView.builder 而非 Column 承载列表,避免大量数据时的性能问题。
  2. 状态同步 :通过 setState 实时更新选中状态,确保 UI 与数据一致。
  3. 样式定制 :通过 shape 属性设置弹窗圆角,提升视觉体验;通过 activeColor 自定义复选框选中颜色。

三、开源鸿蒙实现底部弹窗多项选择

开源鸿蒙采用 ArkUI 声明式 UI 框架,实现底部弹窗多项选择的核心是 BottomSheet 组件,结合 List 组件和状态装饰器@State@Link)维护选中状态。

3.1 核心组件与装饰器说明

  1. BottomSheet:开源鸿蒙内置的底部弹窗组件,支持设置弹窗高度、背景、遮罩层等属性。
  2. @State:用于标记组件内部的状态变量,当变量变化时,UI 会自动刷新。
  3. Checkbox :复选框组件,需与 List 结合使用实现多选列表。
  4. Button:触发弹窗展示和确认操作的核心组件。

3.2 完整代码实现

本文基于开源鸿蒙 API 9(声明式 UI 标准版)进行开发,需确保开发环境已配置完成。

步骤 1:定义数据模型

同样先定义标签数据模型,包含名称和选中状态:

typescript 复制代码
export class TagModel {
  name: string;
  isSelected: boolean;

  constructor(name: string, isSelected: boolean = false) {
    this.name = name;
    this.isSelected = isSelected;
  }
}
步骤 2:构建主页面与多选底部弹窗

创建 OpenHarmonyMultiSelectDemo 页面,通过 @State 标记弹窗显示状态和标签列表,点击按钮后展示 BottomSheet

typescript 复制代码
@Entry
@Component
struct OpenHarmonyMultiSelectDemo {
  // 控制底部弹窗显示/隐藏
  @State isBottomSheetShow: boolean = false;
  // 标签列表数据
  @State tagList: TagModel[] = [
    new TagModel("Flutter"),
    new TagModel("OpenHarmony"),
    new TagModel("Android"),
    new TagModel("iOS"),
    new TagModel("前端"),
    new TagModel("后端"),
  ];
  // 选中的标签列表
  @State selectedTags: TagModel[] = [];

  // 切换标签选中状态
  toggleTagSelection(tag: TagModel) {
    tag.isSelected = !tag.isSelected;
    if (tag.isSelected) {
      this.selectedTags.push(tag);
    } else {
      this.selectedTags = this.selectedTags.filter(item => item.name !== tag.name);
    }
  }

  build() {
    Column() {
      Button("打开多选弹窗")
        .onClick(() => {
          this.isBottomSheetShow = true;
        })
        .margin({ top: 50 })
        .fontSize(18)

      // 底部弹窗
      BottomSheet() {
        Column() {
          // 弹窗标题
          Text("选择兴趣标签")
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
            .margin({ bottom: 20 })

          // 多选列表
          List() {
            ForEach(this.tagList, (tag: TagModel) => {
              ListItem() {
                Row() {
                  Checkbox()
                    .select(tag.isSelected)
                    .onChange((value: boolean) => {
                      this.toggleTagSelection(tag);
                    })
                    .margin({ right: 10 })

                  Text(tag.name)
                    .fontSize(16)
                    .flexGrow(1)
                }
                .padding({ vertical: 15 })
              }
            }, (tag: TagModel) => tag.name)
          }
          .height(300)

          // 确认与取消按钮
          Row() {
            Button("取消")
              .onClick(() => {
                this.isBottomSheetShow = false;
              })
              .margin({ right: 20 })

            Button("确认")
              .onClick(() => {
                // 处理选中结果
                let selectedNames = this.selectedTags.map(item => item.name).join(", ");
                prompt.showToast({ message: `选中了 ${selectedNames}` });
                this.isBottomSheetShow = false;
              })
          }
          .margin({ top: 20 })
        }
        .padding(20)
      }
      .height(450)
      .backgroundColor(Color.White)
      .borderRadius({ topLeft: 20, topRight: 20 })
      .show(this.isBottomSheetShow)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Grey[100])
    .justifyContent(FlexAlign.Center)
  }
}
步骤 3:配置权限与运行

module.json5 中添加弹窗权限(API 9 中 BottomSheet 无需额外权限,若使用 prompt.showToast 需确保已导入相关模块):

json 复制代码
{
  "module": {
    "name": "entry",
    "type": "entry",
    "srcEntry": "./ets/main/ets",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": ["phone", "tablet"],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages"
  }
}

3.3 关键优化点

  1. 数据过滤 :使用 filter 方法更新选中列表,避免重复添加或删除。
  2. 列表性能 :通过 ForEach 的第三个参数(唯一标识)优化列表渲染性能。
  3. 样式优化 :通过 borderRadius 设置弹窗顶部圆角,通过 backgroundColor 区分弹窗与主页面背景。

四、Flutter 与开源鸿蒙实现方案对比

通过以上两种技术栈的实现,我们可以总结出以下核心差异:

对比维度 Flutter 实现方案 开源鸿蒙实现方案
弹窗触发方式 调用 showModalBottomSheet 方法 通过 BottomSheet 组件的 show 属性控制
状态管理 基于 StatefulWidget + setState 基于 @State 装饰器的响应式状态管理
列表渲染 ListView.builder 实现按需加载 List + ForEach 实现列表渲染
事件回调 通过构造函数传入回调函数 直接调用组件内部方法
跨端适配 自动适配 iOS/Android 样式 自动适配鸿蒙生态多设备样式

五、常见问题与解决方案

5.1 多选列表数据量大时的性能问题

  • Flutter :使用 ListView.builder 而非 Column,避免一次性渲染所有列表项;可结合 AutomaticKeepAliveClientMixin 保持列表项状态。
  • 开源鸿蒙 :在 List 组件中设置 cachedCount 属性,控制缓存的列表项数量,减少内存占用。

5.2 选中状态重置问题

场景:关闭弹窗后再次打开,选中状态未重置。

解决方案:在弹窗关闭时,遍历标签列表,将所有 isSelected 设为 false,并清空选中列表。

5.3 弹窗高度自适应问题

  • Flutter :通过 MediaQuery.of(context).size.height 动态计算弹窗高度,或使用 wrap: true 让弹窗高度自适应内容。
  • 开源鸿蒙 :不设置 BottomSheetheight 属性,通过内容自动撑开高度。

六、总结与拓展

本文详细讲解了 Flutter 和开源鸿蒙中底部弹窗多项选择的实现方案,两种技术栈均采用声明式 UI 的设计思想,核心是通过状态管理维护选中状态,结合列表组件承载多选项。

拓展方向

  1. 自定义复选框样式 :Flutter 可通过 Checkbox 结合 Container 实现自定义样式;开源鸿蒙可通过 CheckboxselectedColorunselectedColor 属性定制。
  2. 全选/反选功能:在弹窗顶部添加全选按钮,点击后切换所有标签的选中状态。
  3. 数据持久化 :将选中的标签列表存储到本地(Flutter 使用 shared_preferences,开源鸿蒙使用 Preferences)。

无论是 Flutter 还是开源鸿蒙,掌握底部弹窗多项选择的实现,都能帮助开发者提升移动端应用的交互体验。希望本文能为跨平台开发提供参考,欢迎大家在评论区交流更多优化方案!

写在最后

如果本文对你有帮助,欢迎点赞 + 收藏 + 关注 !后续会持续更新 Flutter 与开源鸿蒙的对比教程,带你玩转跨平台开发。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

相关推荐
笨小孩7872 小时前
Flutter深度解析:从核心原理到实战开发全攻略
flutter
笨小孩7873 小时前
Flutter深度解析:从原理到实战的跨平台开发指南
flutter
飛6793 小时前
Flutter 状态管理深度实战:从零封装轻量级响应式状态管理器,告别 Provider/Bloc 的臃肿与复杂
前端·javascript·flutter
tangweiguo030519873 小时前
Flutter iOS 风格弹框组件封装
flutter
LYFlied3 小时前
浅谈跨端开发:大前端时代的融合之道
前端·flutter·react native·webview·大前端·跨端开发·hybrid
500844 小时前
鸿蒙 Flutter 分布式数据同步:DistributedData 实时协同实战
分布式·flutter·华为·electron·开源·wpf·音视频
song5014 小时前
鸿蒙 Flutter 图像编辑:原生图像处理与滤镜开发
图像处理·人工智能·分布式·flutter·华为·交互
●VON4 小时前
从零构建可扩展 Flutter 应用:v1.0 → v2.0 全代码详解 -《已适配开源鸿蒙》
学习·flutter·开源·openharmony·开源鸿蒙
心随雨下4 小时前
Flutter自适应布局部件(SafeArea 和 MediaQuery)总结
flutter·typescript