Flutter for OpenHarmony 实战之基础组件:第三篇 Stack 层叠布局详解

简介: 本文详解 Flutter for OpenHarmony 中 Stack 层叠布局:涵盖 Z 轴堆叠原理、Alignment 对齐与 Positioned 精准定位,实战 Badge 角标、新闻 Banner 及 IndexedStack 页面状态保持,助你高效构建跨端复杂 UI。(239字)

Flutter for OpenHarmony 实战之基础组件:第三篇 Stack 层叠布局详解

前言

如果你习惯了 CSS 中的 absolute(绝对定位)或者 Android 中的 FrameLayout,那么 Flutter 中的 Stack 对你来说将非常亲切。

RowColumn 解决了线性排列的问题,而 Stack 则解决了重叠展示的需求。它是构建复杂 UI(如视频播放器界面、带角标的图标、全屏加载动画)不可或缺的工具。

本文你将学到

  • Stack 的对齐原理与层级顺序
  • Positioned 与 Align 的精准定位
  • 构建带角标(Badge)的通用组件
  • 实战:打造精美的新闻 Banner 组件
  • 性能优化:IndexedStack 在鸿蒙开发中的应用

一、Stack 基础概念

1.1 堆叠原理 (Z-Order)

Stack 像一叠扑克牌,子组件按照添加顺序从下往上堆叠:

  1. children 列表中的第一个组件在最底层 (Bottom)。
  2. children 列表中的最后一个组件在最顶层 (Top)。

my_first_app/lib/widgets/basic_stack.dart

import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
   
    return Stack(
      children: [
        // 底层:绿色背景
        Container(width: 300, height: 300, color: Colors.green),

        // 中层:黄色方块
        Container(width: 200, height: 200, color: Colors.yellow),

        // 顶层:红色圆圈
        Container(width: 100, height: 100, color: Colors.red),
      ],
    );
  }
}

my_first_app/lib/main.dart

import 'package:flutter/material.dart';
import 'package:my_first_app/widgets/basic_stack.dart';

/// 应用入口函数
void main() {
  // 运行 Flutter 应用
  runApp(const MyApp());
}

/// 根 Widget - 应用程序的顶层组件
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter for OpenHarmony', // 应用标题
      debugShowCheckedModeBanner: false, // 隐藏调试标签
      theme: ThemeData(
        // 主题配置
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true, // 使用 Material 3 设计
      ),
      home: const HomePage(), // 首页
    );
  }
}

/// 首页 Widget
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 应用栏
      appBar: AppBar(
        title: const Text('我的第一个鸿蒙应用 By 王码码'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      // 页面主体
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BasicStack(),
          ],
        ),
      ),
      // 悬浮按钮
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 显示提示
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Flutter + OpenHarmony = ❤️')),
          );
        },
        child: const Icon(Icons.favorite),
      ),
    );
  }
}

在这里插入图片描述

1.2 对齐方式 (Alignment)

当子组件没有被 Positioned 包裹时,它们是非定位(non-positioned)组件,由 Stack.alignment 属性统一控制位置。

my_first_app/lib/widgets/basic_alignment.dart

import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
   
    return Stack(
      alignment: Alignment.center, // 所有非定位子组件居中
      children: [
        Container(width: 200, height: 200, color: Colors.blue),
        const Text('Center Text', style: TextStyle(color: Colors.white)),
      ],
    );
  }
}

Alignment 常用坐标系

  • (0, 0): 中心点
  • (-1, -1): 左上角
  • (1, 1): 右下角
  • (0, 1): 底部中心

在这里插入图片描述


二、精准定位:Positioned 组件

Positioned 组件只能作为 Stack 的直接子节点使用,用于控制子组件的精确位置和尺寸。

2.1 核心属性

  • left, top, right, bottom: 距离 Stack 边缘的距离。
  • width, height: 强制指定子组件尺寸。

my_first_app/lib/widgets/basic_position.dart

import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
   
    return Stack(
      children: [
        // 底图
        Container(color: Colors.grey[200], height: 200),

        // 左上角距离 10px
        const Positioned(
          left: 10,
          top: 10,
          child: const Icon(Icons.star, color: Colors.orange),
        ),

        // 底部横幅 (left=0, right=0 相当于只有 width=parentWidth)
        Positioned(
          left: 0,
          right: 0,
          bottom: 0,
          height: 40,
          child: Container(
            color: Colors.black54,
            alignment: Alignment.center,
            child: const Text('底部悬浮条', style: TextStyle(color: Colors.white)),
          ),
        ),
      ],
    );
  }
}

在这里插入图片描述

2.2 常见误区

错误用法:在 Stack 外部使用 Positioned

Column(
  children: [
    Positioned(...) // 报错:Positioned 必须是 Stack 的子组件
  ],
)

技巧:Positioned.fill
如果想让子组件填满整个 Stack,可以使用简写:

Positioned.fill(
  child: Image.asset('bg.png', fit: BoxFit.cover),
)

三、实战案例 1:消息角标 (Badge) 组件

这是 APP 中最常见的设计:头像右上角有个红色未读数字。

my_first_app/lib/widgets/avatar_badge.dart

import 'package:flutter/material.dart';

class AvatarWithBadge extends StatelessWidget {
   
  final String imageUrl;
  final int count;

  const AvatarWithBadge({
   
    super.key,
    required this.imageUrl,
    required this.count,
  });

  
  Widget build(BuildContext context) {
   
    return Stack(
      // 允许子组件略微超出 Stack 范围 (clipBehavior 默认是 hardEdge,需要改为 none)
      clipBehavior: Clip.none,
      children: [
        // 1. 头像
        Container(
          width: 60,
          height: 60,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            image: DecorationImage(
              // 根据路径自动选择图片来源:本地资源 or 网络
              image: imageUrl.startsWith('http')
                  ? NetworkImage(imageUrl)
                  : AssetImage(imageUrl) as ImageProvider,
              fit: BoxFit.cover,
            ),
            border: Border.all(color: Colors.white, width: 2),
            boxShadow: const [
              BoxShadow(
                color: Colors.black12,
                blurRadius: 4,
                offset: Offset(0, 2),
              ),
            ],
          ),
        ),

        // 2. 红色角标 (仅当数量 > 0 时显示)
        if (count > 0)
          Positioned(
            right: -4, // 向右偏移,制造破局跟随效果
            top: -4, // 向上偏移
            child: Container(
              padding: const EdgeInsets.all(4),
              constraints: const BoxConstraints(
                minWidth: 20,
                minHeight: 20,
              ),
              decoration: BoxDecoration(
                color: Colors.red,
                shape: BoxShape.circle,
                border: Border.all(color: Colors.white, width: 1.5),
              ),
              child: Center(
                child: Text(
                  count > 99 ? '99+' : count.toString(),
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 10,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
          ),
      ],
    );
  }
}

my_first_app/lib/main.dart

import 'package:flutter/material.dart';
import 'package:my_first_app/widgets/basic_position.dart';
import 'package:my_first_app/widgets/basic_stack.dart';
import 'package:my_first_app/widgets/basic_alignment.dart';
import 'package:my_first_app/widgets/avatar_badge.dart';

/// 应用入口函数
void main() {
   
  // 运行 Flutter 应用
  runApp(const MyApp());
}

/// 根 Widget - 应用程序的顶层组件
class MyApp extends StatelessWidget {
   
  const MyApp({
   super.key});

  
  Widget build(BuildContext context) {
   
    return MaterialApp(
      title: 'Flutter for OpenHarmony', // 应用标题
      debugShowCheckedModeBanner: false, // 隐藏调试标签
      theme: ThemeData(
        // 主题配置
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true, // 使用 Material 3 设计
      ),
      home: const HomePage(), // 首页
    );
  }
}

/// 首页 Widget
class HomePage extends StatelessWidget {
   
  const HomePage({
   super.key});

  
  Widget build(BuildContext context) {
   
    return Scaffold(
      // 应用栏
      appBar: AppBar(
        title: const Text('我的第一个鸿蒙应用 By 王码码'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      // 页面主体
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // BasicStack(),
            // BasicAlignment(),
            // BasicPosition(),
            AvatarWithBadge(
              imageUrl:
                  'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120&h=120&fit=crop', // Unsplash 头像
              count: 5,
            ),
          ],
        ),
      ),
      // 悬浮按钮
      floatingActionButton: FloatingActionButton(
        onPressed: () {
   
          // 显示提示
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Flutter + OpenHarmony = ❤️')),
          );
        },
        child: const Icon(Icons.favorite),
      ),
    );
  }
}

在这里插入图片描述


四、实战案例 2:新闻 Banner 组件

我们需要实现一个典型的 Banner 效果:图片 + 渐变蒙层 + 左下角文字 + 右下角指示器。

my_first_app/lib/widgets/news_banner.dart

import 'package:flutter/material.dart';

class NewsBanner extends StatelessWidget {
   
  final String title;
  final String imageUrl;
  final String tag;

  const NewsBanner({
   
    super.key,
    required this.title,
    required this.imageUrl,
    required this.tag,
  });

  
  Widget build(BuildContext context) {
   
    return Container(
      height: 200,
      margin: const EdgeInsets.all(16),
      // ClipRRect 用于裁剪 Stack 的圆角
      child: ClipRRect(
        borderRadius: BorderRadius.circular(12),
        child: Stack(
          children: [
            // 1. 背景图 (填满)
            Positioned.fill(
              child: Image.network(
                imageUrl,
                fit: BoxFit.cover,
              ),
            ),

            // 2. 渐变蒙层 (提升文字可读性)
            Positioned(
              left: 0,
              right: 0,
              bottom: 0,
              height: 100, // 仅底部有渐变
              child: Container(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    begin: Alignment.topCenter,
                    end: Alignment.bottomCenter,
                    colors: [
                      Colors.transparent,
                      Colors.black.withOpacity(0.8),
                    ],
                  ),
                ),
              ),
            ),

            // 3. 标签 (左上角)
            Positioned(
              left: 12,
              top: 12,
              child: Container(
                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                decoration: BoxDecoration(
                  color: Colors.blue,
                  borderRadius: BorderRadius.circular(4),
                ),
                child: Text(
                  tag,
                  style: const TextStyle(color: Colors.white, fontSize: 10),
                ),
              ),
            ),

            // 4. 标题 (左下角)
            Positioned(
              left: 16,
              bottom: 16,
              right: 60, // 留出空间给指示器等
              child: Text(
                title,
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

my_first_app/lib/main.dart

import 'package:flutter/material.dart';
import 'package:my_first_app/widgets/basic_position.dart';
import 'package:my_first_app/widgets/basic_stack.dart';
import 'package:my_first_app/widgets/basic_alignment.dart';
import 'package:my_first_app/widgets/avatar_badge.dart';
import 'package:my_first_app/widgets/news_banner.dart';

/// 应用入口函数
void main() {
   
  // 运行 Flutter 应用
  runApp(const MyApp());
}

/// 根 Widget - 应用程序的顶层组件
class MyApp extends StatelessWidget {
   
  const MyApp({
   super.key});

  
  Widget build(BuildContext context) {
   
    return MaterialApp(
      title: 'Flutter for OpenHarmony', // 应用标题
      debugShowCheckedModeBanner: false, // 隐藏调试标签
      theme: ThemeData(
        // 主题配置
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true, // 使用 Material 3 设计
      ),
      home: const HomePage(), // 首页
    );
  }
}

/// 首页 Widget
class HomePage extends StatelessWidget {
   
  const HomePage({
   super.key});

  
  Widget build(BuildContext context) {
   
    return Scaffold(
      // 应用栏
      appBar: AppBar(
        title: const Text('我的第一个鸿蒙应用 By 王码码'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      // 页面主体
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // BasicStack(),
            // BasicAlignment(),
            // BasicPosition(),
            // AvatarWithBadge(
            //   imageUrl:
            //       'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120&h=120&fit=crop', // Unsplash 头像
            //   count: 5,
            // ),
            NewsBanner(
              title: 'HarmonyOS NEXT 5.0 正式发布:纯血鸿蒙时代来临',
              imageUrl:
                  'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=600&h=400&fit=crop', // 科技发布会配图
              tag: '鸿蒙',
            ),
          ],
        ),
      ),
      // 悬浮按钮
      floatingActionButton: FloatingActionButton(
        onPressed: () {
   
          // 显示提示
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Flutter + OpenHarmony = ❤️')),
          );
        },
        child: const Icon(Icons.favorite),
      ),
    );
  }
}

在这里插入图片描述

五、鸿蒙开发技巧:IndexedStack

在 OpenHarmony 应用开发中,我们经常需要实现底部导航栏(BottomNavigationBar)的页面切换。

5.1 传统方式的痛点

如果直接使用 body: pages[currentIndex],每次切换页面时,原来的页面会被销毁,新页面会被重建。如果页面包含复杂的网络请求或地图组件,重建成本非常高,且无法保持滚动位置。

5.2 使用 IndexedStack 优化

IndexedStack 是一个特殊的 Stack,它一次性加载所有子组件,但只显示 index 对应的那个。其他组件虽然不可见,但状态依然保持(State Keep Alive)。

在这里插入图片描述

my_first_app/lib/main_page.dart

import 'package:flutter/material.dart';
import 'package:my_first_app/main.dart';
import 'package:my_first_app/category_page.dart';
import 'package:my_first_app/profile_page.dart';

class MainPage extends StatefulWidget {
   
  const MainPage({
   super.key});

  
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
   
  int _currentIndex = 0;

  // 页面列表
  final List<Widget> _pages = const [
    HomePage(),
    CategoryPage(),
    ProfilePage(),
  ];

  
  Widget build(BuildContext context) {
   
    return Scaffold(
      appBar: AppBar(title: const Text('IndexedStack 性能优化')),

      // ✅ 优化:使用 IndexedStack 保持页面状态
      body: IndexedStack(
        index: _currentIndex,
        children: _pages,
      ),

      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
   
          setState(() {
   
            _currentIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.category), label: '分类'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
        ],
      ),
    );
  }
}

my_first_app/lib/category_page.dart

import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
   
    return const Center(
      child: Text('分类页面'),
    );
  }
}

my_first_app/lib/profile_page.dart

import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
   
    return const Center(
      child: Text('我的页面'),
    );
  }
}

my_first_app/lib/main.dart

// ...
import 'package:my_first_app/main_page.dart';

// ...省略
/// 根 Widget - 应用程序的顶层组件
class MyApp extends StatelessWidget {
   
  const MyApp({
   super.key});

  
  Widget build(BuildContext context) {
   
    return MaterialApp(
      title: 'Flutter for OpenHarmony', // 应用标题
      debugShowCheckedModeBanner: false, // 隐藏调试标签
      theme: ThemeData(
        // 主题配置
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true, // 使用 Material 3 设计
      ),
      home: const MainPage(), // 首页
    );
  }
}
// ...省略

⚠️ 性能注意IndexedStack 适合页面数量较少(3-5 个)且需要保持状态的场景。如果页面非常多,建议配合 PageView 或自定义缓存策略。


六、层叠上下文陷阱 (Z-Index 不存在?)

Flutter 中没有 Web 开发中的 z-index 属性。如果你想调整层级,只能通过调整 children 列表的顺序

如何让点击事件穿透?
有时候上层的透明蒙层会挡住下层按钮的点击事件。

  • 使用 IgnorePointer: 忽略自己和子组件的点击事件。
  • 使用 AbsorbPointer: 吸收点击事件,不让其传递给下层。
Stack(
  children: [
    ElevatedButton(onPressed: () {
   }, child: Text('点我')),

    // 这个透明层会挡住按钮点击
    // 解决方法:包裹 IgnorePointer
    IgnorePointer(
      child: Container(color: Colors.transparent),
    ),
  ],
)

七、总结

Stack 赋予了我们在 Z 轴上的布局能力,配合 Positioned 可以实现像素级的精确控制。

核心知识点

  1. 层叠顺序:代码在后的在上面。
  2. 定位控制:使用 Positioned + top/bottom/left/right
  3. 溢出处理:设置 clipBehavior: Clip.none 允许子组件画出界。
  4. 性能优化:状态保持场景优先选 IndexedStack

下一篇预告

我们已经掌握了基本的布局和定位。接下来的文章我们将进入最基础但也最复杂的组件——文本
《Flutter for OpenHarmony 实战之基础组件:第四篇 Text 文本组件全解》
我们将讨论富文本(RichText)、文本溢出处理、自定义字体以及鸿蒙系统下的字体适配。

相关文章
|
20天前
|
人工智能 前端开发 Linux
阿里云/本地部署OpenClaw与人格定制终极指南:186位专家即插即用+千问/Coding Plan配置教程
OpenClaw(社区昵称“小龙虾”)的核心能力,由`workspace`目录下三大配置文件共同定义:`SOUL.md`人格内核、`IDENTITY.md`身份名片、`AGENTS.md`能力边界。这三份文件决定了AI的专业方向、沟通风格、工作流与行为准则。手动编写不仅耗时,且难以达到专业水准。
490 8
|
22天前
|
机器学习/深度学习 人工智能 监控
猪仔行为检测数据集(3700张高清标注)| YOLO实战 智慧养殖与猪只健康监测
本数据集开源3700张高清标注图像,覆盖猪仔9类核心行为(饮水、进食、撞击、跪地、拱蹭、休息、站立、吮乳、踩踏),严格按YOLO格式划分训练/验证/测试集,适配YOLOv5/v8/v11,助力智慧养殖与健康监测。
|
2月前
|
人工智能 API 机器人
OpenClaw 用户部署和使用指南汇总
本文档为OpenClaw(原MoltBot)官方使用指南,涵盖一键部署(阿里云轻量服务器年仅68元)、钉钉/飞书/企微等多平台AI员工搭建、典型场景实践及高频问题FAQ。同步更新产品化修复进展,助力用户高效落地7×24小时主动执行AI助手。
26864 199
|
19天前
|
缓存 JavaScript 前端开发
《苏宁商品详情页前端性能优化实战》
本文揭秘苏宁商品详情页前端性能优化实战,通过网络加载、渲染路径、接口聚合、运行时四大维度系统优化,实现首屏FCP从3.2s→0.9s、LCP从5.1s→1.4s、JS体积下降80%,转化率提升12%。
|
12天前
|
机器学习/深度学习 弹性计算 人工智能
2026年阿里云服务器收费价格表(轻量/ECS/GPU):一年、1个月与小时费用清单
阿里云2026年推出轻量应用服务器、云服务器ECS及GPU服务器三大高性价比套餐,阿里云官方活动:https://t.aliyun.com/U/FzmsXA 覆盖个人建站、企业应用与AI训练等场景。提供包年、月付、按量三种计费模式,价格透明,新老用户同享优惠,支持一键部署与弹性扩展
579 13
|
2天前
|
Android开发 C++ 容器
Flutter for OpenHarmony 实战之基础组件:第二篇 Row 与 Column 弹性布局详解
本文详解 Flutter for OpenHarmony 中核心布局组件 Row 与 Column:厘清主轴/交叉轴概念,对比 Expanded、Flexible 与 Spacer 的适用场景,提供溢出警告(RenderFlex overflowed)的实战解决方案,并结合复杂列表项与鸿蒙折叠屏动态横竖排布局,助你高效构建响应式 UI。(239字)
Flutter for OpenHarmony 实战之基础组件:第二篇 Row 与 Column 弹性布局详解
|
9天前
|
人工智能 供应链 安全
2026 年网络威胁态势与智能防御体系研究 —— 基于 Check Point 威胁情报报告
本文基于Check Point 2026年4月威胁情报,系统剖析AI驱动攻击、供应链入侵、高危零日漏洞及定向威胁新趋势;提出以威胁情报驱动、AI检测、漏洞闭环、零信任与供应链安全为核心的一体化防御体系,并提供可落地的检测代码、配置与响应流程。(239字)
226 13
|
3天前
|
人工智能 JSON 机器人
OpenClaw 飞书机器人一键对接教程|聊天窗口直接发 AI 指令
本文为OpenClaw飞书机器人配置全流程指南:涵盖账号要求、飞书开放平台登录、自建应用创建、机器人能力添加、权限批量导入、事件订阅、应用发布、凭证配置及常见问题排查,助用户快速完成AI智能体接入。
146 4
|
6天前
|
人工智能 安全 测试技术
全栈开发者必备:AI全流程研发实战技巧与案例分享全栈开发者必备
很多开发者对AI编程的印象还停留在写片段、补代码,但真正落地到团队项目、需求评审、架构设计、Code Review全链路时,大多AI都显得“水土不服”。最近深度实践了AI全流程研发模式,结合行业实践与真实项目落地,聊一聊如何把AI从“辅助写代码”变成覆盖需求→设计→开发→审查的工程化研发助力。
171 3
|
22天前
|
Cloud Native Java 应用服务中间件
从单体到云原生——Java架构演进十五年的启示
2008年,一个典型的Java Web应用是这样的:一个WAR包部署在WebLogic或WebSphere应用服务器上,通过JDBC直连Oracle数据库,使用EJB实现业务逻辑。
105 4
下一篇
开通oss服务