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)、文本溢出处理、自定义字体以及鸿蒙系统下的字体适配。

相关文章
|
1月前
|
机器学习/深度学习 弹性计算 人工智能
2026年阿里云服务器收费价格表(轻量/ECS/GPU):一年、1个月与小时费用清单
阿里云2026年推出轻量应用服务器、云服务器ECS及GPU服务器三大高性价比套餐,阿里云官方活动:https://t.aliyun.com/U/FzmsXA 覆盖个人建站、企业应用与AI训练等场景。提供包年、月付、按量三种计费模式,价格透明,新老用户同享优惠,支持一键部署与弹性扩展
1103 13
|
1月前
|
Android开发 C++ 容器
Flutter for OpenHarmony 实战之基础组件:第二篇 Row 与 Column 弹性布局详解
本文详解 Flutter for OpenHarmony 中核心布局组件 Row 与 Column:厘清主轴/交叉轴概念,对比 Expanded、Flexible 与 Spacer 的适用场景,提供溢出警告(RenderFlex overflowed)的实战解决方案,并结合复杂列表项与鸿蒙折叠屏动态横竖排布局,助你高效构建响应式 UI。(239字)
102 0
Flutter for OpenHarmony 实战之基础组件:第二篇 Row 与 Column 弹性布局详解
|
1月前
|
编解码 Dart 前端开发
Flutter for OpenHarmony 实战之基础组件:第一篇 Container 容器组件完全指南
本文是 Flutter for OpenHarmony 开发的首篇基础组件指南,深度解析 Container 容器组件:涵盖尺寸控制、margin/padding 区别、Alignment 对齐、BoxDecoration 装饰(渐变/圆角/阴影),并重点讲解 OpenHarmony 多分辨率适配、刘海屏安全区处理及性能优化技巧,助你打造高质量跨端 UI。(239字)
118 1
Flutter for OpenHarmony 实战之基础组件:第一篇 Container 容器组件完全指南
|
3天前
|
前端开发 数据库 数据安全/隐私保护
搭建互联网医院系统:医疗资质对接与合规建设解析
互联网医院开发难点不在界面,而在资质合规、多系统对接(HIS/EMR/医保/处方平台)与数据安全。需构建可审计的日志体系、智能接口中台及全流程加密机制,实现医疗协同而非简单线上问诊。
|
19天前
|
人工智能 供应链 监控
工业制造难题大揭秘:AI 改造解锁高效新未来
本文介绍JBoltAI为工业制造业打造的四大AI解决方案:智能大宗物料监控、AI报价导航、SOP+VisuCAD快速原型开发,以及质量检测、预测性维护等延伸应用,助力企业降本增效、加速创新。(239字)
68 5
|
27天前
|
缓存 运维 NoSQL
WooCommerce订单管理优化实战指南2026
2026年WooCommerce订单管理优化深度实战指南,涵盖HPOS迁移避坑、订单状态自动化、高峰期并发处理和批量操作优化。结合真实电商案例,解析常见误区与具体解决方案,助你打造高性能WooCommerce订单系统。云策WordPress建站提供专业WordPress运维服务与定制开发支持。
|
1月前
|
安全 数据可视化 BI
产科电子病历源码:可无缝对接HIS/LIS/PACS,支持二次开发
产科电子病历系统是覆盖孕前至产后42天的专科化智慧平台,以母婴安全为核心,实现全周期数据贯通、智能“五色”高危评估(绿/黄/橙/红/紫)、自动预警、结构化录入及多系统互联,大幅提升诊疗规范性与管理效率。
74 2
|
27天前
|
安全 Java 编译器
对比C++和Java的异常处理机制
异常处理是现代编程语言处理运行时错误的重要手段。C++和Java都提供了try-catch-finally(或C++的RAII代替finally)机制,但两者的设计哲学、性能开销、检查类型和最佳实践存在显著差异。
67 1
|
1月前
|
自然语言处理 安全 机器人
2026年企业如何把智能客服系统用好?打造高效智能服务体系,实现降本增效
本文剖析2026年企业用好智能客服的关键路径,以瓴羊Quick Service为范例,揭示如何通过数据打通、场景适配、人机协同及服务—数据—业务闭环,将客服从成本中心升级为价值中心,实现真正降本增效。(239字)
|
1月前
|
人工智能 自然语言处理 安全
2026 最新版 OpenClaw 极速安装方案,缩短 90% 部署时间(包含新安装包)
传统 OpenClaw 安装需要依次配置环境、下载依赖、手动汉化,耗时又容易出错。2026 最新版一键安装包对流程进行全面优化,将所有步骤整合为全自动模式,大幅缩短安装时间,无需等待、无需调试、无需重复操作,几分钟内完成全部部署,安装后即可直接使用中文版,高效省心,适合追求快速体验的用户。

热门文章

最新文章