Flutter Positioned 组件总结

133 阅读14分钟

Flutter Positioned 组件总结

概述

Positioned 是Flutter中用于在Stack布局中精确控制子组件位置的定位组件。它必须作为Stack的直接子组件使用,通过设置距离Stack边界的偏移量来实现绝对定位布局,类似于网页开发中的CSS绝对定位。

原理说明

核心工作原理

  1. 绝对定位机制

    • Positioned 组件通过设置 left、top、right、bottom 等属性实现绝对定位
    • 所有位置参数都是相对于父级 Stack 组件的边界计算
    • 子组件脱离正常文档流,不占用Stack中其他子组件的布局空间
  2. 约束系统

    • 在水平方向上:left、right、width 三者中最多只能指定两个
    • 在垂直方向上:top、bottom、height 三者中最多只能指定两个
    • 第三个参数会根据前两个自动计算得出
  3. 尺寸确定规则

    • 如果指定了 width/height,则使用指定的尺寸
    • 如果同时指定了 left 和 right,则 width = Stack宽度 - left - right
    • 如果同时指定了 top 和 bottom,则 height = Stack高度 - top - bottom
    • 如果某个方向上未指定任何参数,则使用 Stack 的 alignment 属性进行定位

构造函数

const Positioned({
  Key? key,                          // 组件的唯一标识符
  double? left,                      // 距离Stack左边界的距离
  double? top,                       // 距离Stack上边界的距离
  double? right,                     // 距离Stack右边界的距离
  double? bottom,                    // 距离Stack下边界的距离
  double? width,                     // 子组件的宽度
  double? height,                    // 子组件的高度
  required Widget child,             // 被定位的子组件(必填)
})

核心属性

位置属性

属性名类型说明注意事项
leftdouble?距离Stack左边界的距离与right、width形成约束关系
topdouble?距离Stack上边界的距离与bottom、height形成约束关系
rightdouble?距离Stack右边界的距离与left、width形成约束关系
bottomdouble?距离Stack下边界的距离与top、height形成约束关系

尺寸属性

属性名类型说明注意事项
widthdouble?子组件的宽度与left、right形成约束关系
heightdouble?子组件的高度与top、bottom形成约束关系

约束规则详解

// 水平方向约束(只能指定其中两个)
Positioned(
  left: 10,     // ✓ 指定左边距
  right: 20,    // ✓ 指定右边距
  // width 自动计算 = Stack宽度 - 10 - 20
  child: Container(color: Colors.red),
)

// 垂直方向约束(只能指定其中两个)
Positioned(
  top: 30,      // ✓ 指定上边距
  height: 100,  // ✓ 指定高度
  // bottom 由其他因素决定
  child: Container(color: Colors.blue),
)

// 错误示例:同时指定三个参数会导致冲突
Positioned(
  left: 10,     // ❌ 冲突
  right: 20,    // ❌ 冲突  
  width: 100,   // ❌ 冲突
  child: Container(color: Colors.red),
)

其他构造函数

Positioned.fill - 填充整个Stack

const Positioned.fill({
  Key? key,
  double left = 0.0,
  double top = 0.0,
  double right = 0.0,
  double bottom = 0.0,
  required Widget child,
})

用途:快速创建填充整个Stack的定位组件

Stack(
  children: [
    // 背景容器
    Container(width: 300, height: 200, color: Colors.grey[300]),
    
    // 填充整个Stack的遮罩层
    Positioned.fill(
      child: Container(
        color: Colors.black.withOpacity(0.5),
        child: Center(
          child: Text(
            '遮罩层',
            style: TextStyle(color: Colors.white, fontSize: 24),
          ),
        ),
      ),
    ),
  ],
)

Positioned.fromRect - 使用Rect定位

Positioned.fromRect({
  Key? key,
  required Rect rect,
  required Widget child,
})

用途:使用Rect对象直接指定位置和尺寸

Stack(
  children: [
    Container(width: 300, height: 200, color: Colors.grey[300]),
    
    Positioned.fromRect(
      rect: Rect.fromLTWH(50, 30, 100, 80), // left, top, width, height
      child: Container(
        color: Colors.red,
        child: Center(child: Text('Rect定位')),
      ),
    ),
  ],
)

Positioned.fromRelativeRect - 相对定位

Positioned.fromRelativeRect({
  Key? key,
  required RelativeRect rect,
  required Size size,
  required Widget child,
})

用途:基于相对比例进行定位

Stack(
  children: [
    Container(width: 300, height: 200, color: Colors.grey[300]),
    
    Positioned.fromRelativeRect(
      rect: RelativeRect.fromLTRB(0.1, 0.1, 0.1, 0.1), // 相对比例
      size: Size(300, 200), // Stack的尺寸
      child: Container(
        color: Colors.blue,
        child: Center(child: Text('相对定位')),
      ),
    ),
  ],
)

实现方式

1. 基础定位示例

import 'package:flutter/material.dart';

class BasicPositionedExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('基础定位示例'),
        backgroundColor: Colors.blue,
      ),
      body: Center(
        child: Container(
          width: 300,
          height: 300,
          decoration: BoxDecoration(
            border: Border.all(color: Colors.grey, width: 2),
          ),
          child: Stack(
            children: [
              // 左上角定位
              Positioned(
                left: 10,
                top: 10,
                child: Container(
                  width: 60,
                  height: 60,
                  color: Colors.red,
                  child: Center(
                    child: Text(
                      '左上',
                      style: TextStyle(color: Colors.white, fontSize: 12),
                    ),
                  ),
                ),
              ),
              
              // 右上角定位
              Positioned(
                right: 10,
                top: 10,
                child: Container(
                  width: 60,
                  height: 60,
                  color: Colors.green,
                  child: Center(
                    child: Text(
                      '右上',
                      style: TextStyle(color: Colors.white, fontSize: 12),
                    ),
                  ),
                ),
              ),
              
              // 左下角定位
              Positioned(
                left: 10,
                bottom: 10,
                child: Container(
                  width: 60,
                  height: 60,
                  color: Colors.orange,
                  child: Center(
                    child: Text(
                      '左下',
                      style: TextStyle(color: Colors.white, fontSize: 12),
                    ),
                  ),
                ),
              ),
              
              // 右下角定位
              Positioned(
                right: 10,
                bottom: 10,
                child: Container(
                  width: 60,
                  height: 60,
                  color: Colors.purple,
                  child: Center(
                    child: Text(
                      '右下',
                      style: TextStyle(color: Colors.white, fontSize: 12),
                    ),
                  ),
                ),
              ),
              
              // 中心定位(使用left和top计算)
              Positioned(
                left: 300 / 2 - 30, // 居中:(容器宽度 - 组件宽度) / 2
                top: 300 / 2 - 30,  // 居中:(容器高度 - 组件高度) / 2
                child: Container(
                  width: 60,
                  height: 60,
                  color: Colors.blue,
                  child: Center(
                    child: Text(
                      '中心',
                      style: TextStyle(color: Colors.white, fontSize: 12),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

2. 约束系统演示

import 'package:flutter/material.dart';

class ConstraintDemoExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('约束系统演示'),
        backgroundColor: Colors.green,
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            // 演示1:left + right 自动计算width
            _buildDemo(
              title: '1. 指定 left + right,自动计算 width',
              child: Container(
                height: 100,
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.grey),
                ),
                child: Stack(
                  children: [
                    Positioned(
                      left: 20,  // 距左边20px
                      right: 30, // 距右边30px
                      top: 10,
                      height: 40,
                      child: Container(
                        color: Colors.red,
                        child: Center(
                          child: Text(
                            'width = Stack宽度 - 20 - 30',
                            style: TextStyle(color: Colors.white, fontSize: 12),
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            
            SizedBox(height: 20),
            
            // 演示2:top + bottom 自动计算height
            _buildDemo(
              title: '2. 指定 top + bottom,自动计算 height',
              child: Container(
                height: 120,
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.grey),
                ),
                child: Stack(
                  children: [
                    Positioned(
                      left: 20,
                      width: 200,
                      top: 15,    // 距顶部15px
                      bottom: 25, // 距底部25px
                      child: Container(
                        color: Colors.blue,
                        child: Center(
                          child: Text(
                            'height = Stack高度 - 15 - 25',
                            style: TextStyle(color: Colors.white, fontSize: 12),
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            
            SizedBox(height: 20),
            
            // 演示3:left + width 自动计算right位置
            _buildDemo(
              title: '3. 指定 left + width,确定位置',
              child: Container(
                height: 100,
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.grey),
                ),
                child: Stack(
                  children: [
                    Positioned(
                      left: 50,  // 距左边50px
                      width: 120, // 宽度120px
                      top: 10,
                      height: 40,
                      child: Container(
                        color: Colors.green,
                        child: Center(
                          child: Text(
                            'left=50, width=120',
                            style: TextStyle(color: Colors.white, fontSize: 12),
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
  
  Widget _buildDemo({required String title, required Widget child}) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          title,
          style: TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
            color: Colors.green[800],
          ),
        ),
        SizedBox(height: 8),
        child,
      ],
    );
  }
}

3. 复杂布局示例

import 'package:flutter/material.dart';

class ComplexLayoutExample extends StatefulWidget {
  @override
  _ComplexLayoutExampleState createState() => _ComplexLayoutExampleState();
}

class _ComplexLayoutExampleState extends State<ComplexLayoutExample> {
  bool _showOverlay = false;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('复杂布局示例'),
        backgroundColor: Colors.deepPurple,
      ),
      body: Stack(
        children: [
          // 主要内容
          _buildMainContent(),
          
          // 浮动操作按钮组
          _buildFloatingButtons(),
          
          // 顶部状态栏
          _buildTopStatusBar(),
          
          // 底部信息栏
          _buildBottomInfoBar(),
          
          // 侧边快捷操作
          _buildSideActions(),
          
          // 可选的遮罩层
          if (_showOverlay) _buildOverlay(),
        ],
      ),
    );
  }
  
  Widget _buildMainContent() {
    return Positioned.fill(
      child: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [Colors.purple[50]!, Colors.blue[50]!],
          ),
        ),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(
                Icons.layers,
                size: 80,
                color: Colors.deepPurple[300],
              ),
              SizedBox(height: 20),
              Text(
                '复杂定位布局',
                style: TextStyle(
                  fontSize: 28,
                  fontWeight: FontWeight.bold,
                  color: Colors.deepPurple[800],
                ),
              ),
              SizedBox(height: 10),
              Text(
                '展示多层次的Positioned组件应用',
                style: TextStyle(
                  fontSize: 16,
                  color: Colors.deepPurple[600],
                ),
              ),
              SizedBox(height: 30),
              ElevatedButton(
                onPressed: () {
                  setState(() {
                    _showOverlay = !_showOverlay;
                  });
                },
                child: Text(_showOverlay ? '隐藏遮罩' : '显示遮罩'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.deepPurple,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
  
  Widget _buildTopStatusBar() {
    return Positioned(
      top: 0,
      left: 0,
      right: 0,
      height: 60,
      child: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.deepPurple.withOpacity(0.8), Colors.transparent],
          ),
        ),
        child: SafeArea(
          child: Padding(
            padding: EdgeInsets.symmetric(horizontal: 16),
            child: Row(
              children: [
                Icon(Icons.signal_cellular_4_bar, color: Colors.white),
                SizedBox(width: 8),
                Text(
                  '在线',
                  style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
                ),
                Spacer(),
                Icon(Icons.battery_full, color: Colors.white),
                SizedBox(width: 4),
                Text(
                  '100%',
                  style: TextStyle(color: Colors.white, fontSize: 12),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
  
  Widget _buildBottomInfoBar() {
    return Positioned(
      bottom: 0,
      left: 0,
      right: 0,
      height: 80,
      child: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [Colors.transparent, Colors.black.withOpacity(0.7)],
          ),
        ),
        child: Padding(
          padding: EdgeInsets.all(16),
          child: Row(
            children: [
              CircleAvatar(
                radius: 20,
                backgroundColor: Colors.white,
                child: Icon(Icons.person, color: Colors.deepPurple),
              ),
              SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text(
                      '当前用户',
                      style: TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Text(
                      '活跃状态 · 2分钟前',
                      style: TextStyle(
                        color: Colors.white70,
                        fontSize: 12,
                      ),
                    ),
                  ],
                ),
              ),
              Icon(Icons.more_horiz, color: Colors.white),
            ],
          ),
        ),
      ),
    );
  }
  
  Widget _buildFloatingButtons() {
    return Column(
      children: [
        // 右上角关闭按钮
        Positioned(
          top: 100,
          right: 16,
          child: FloatingActionButton(
            mini: true,
            onPressed: () {
              Navigator.pop(context);
            },
            child: Icon(Icons.close),
            backgroundColor: Colors.red,
            heroTag: "close",
          ),
        ),
        
        // 右中间功能按钮组
        Positioned(
          right: 16,
          top: MediaQuery.of(context).size.height * 0.4,
          child: Column(
            children: [
              _buildMiniActionButton(
                icon: Icons.favorite,
                color: Colors.pink,
                onPressed: () => _showMessage('收藏'),
                heroTag: "favorite",
              ),
              SizedBox(height: 10),
              _buildMiniActionButton(
                icon: Icons.share,
                color: Colors.blue,
                onPressed: () => _showMessage('分享'),
                heroTag: "share",
              ),
              SizedBox(height: 10),
              _buildMiniActionButton(
                icon: Icons.download,
                color: Colors.green,
                onPressed: () => _showMessage('下载'),
                heroTag: "download",
              ),
            ],
          ),
        ),
      ],
    );
  }
  
  Widget _buildSideActions() {
    return Positioned(
      left: 0,
      top: 150,
      bottom: 150,
      width: 50,
      child: Container(
        decoration: BoxDecoration(
          color: Colors.deepPurple.withOpacity(0.2),
          borderRadius: BorderRadius.only(
            topRight: Radius.circular(25),
            bottomRight: Radius.circular(25),
          ),
        ),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            _buildSideActionItem(Icons.home, '首页'),
            _buildSideActionItem(Icons.search, '搜索'),
            _buildSideActionItem(Icons.notifications, '通知'),
            _buildSideActionItem(Icons.settings, '设置'),
          ],
        ),
      ),
    );
  }
  
  Widget _buildOverlay() {
    return Positioned.fill(
      child: Container(
        color: Colors.black.withOpacity(0.8),
        child: Center(
          child: Container(
            margin: EdgeInsets.all(40),
            padding: EdgeInsets.all(30),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(20),
              boxShadow: [
                BoxShadow(
                  color: Colors.black26,
                  blurRadius: 20,
                  offset: Offset(0, 10),
                ),
              ],
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Icon(
                  Icons.info_outline,
                  size: 60,
                  color: Colors.deepPurple,
                ),
                SizedBox(height: 20),
                Text(
                  '遮罩层演示',
                  style: TextStyle(
                    fontSize: 24,
                    fontWeight: FontWeight.bold,
                    color: Colors.deepPurple[800],
                  ),
                ),
                SizedBox(height: 10),
                Text(
                  '这是一个使用 Positioned.fill 创建的\n全屏遮罩层,常用于弹窗、加载状态等场景。',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 16,
                    color: Colors.grey[600],
                  ),
                ),
                SizedBox(height: 30),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    ElevatedButton(
                      onPressed: () {
                        setState(() {
                          _showOverlay = false;
                        });
                      },
                      child: Text('关闭'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.grey,
                      ),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        _showMessage('确认操作');
                        setState(() {
                          _showOverlay = false;
                        });
                      },
                      child: Text('确认'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.deepPurple,
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
  
  Widget _buildMiniActionButton({
    required IconData icon,
    required Color color,
    required VoidCallback onPressed,
    required String heroTag,
  }) {
    return FloatingActionButton(
      mini: true,
      onPressed: onPressed,
      child: Icon(icon, size: 20),
      backgroundColor: color,
      heroTag: heroTag,
    );
  }
  
  Widget _buildSideActionItem(IconData icon, String tooltip) {
    return GestureDetector(
      onTap: () => _showMessage(tooltip),
      child: Container(
        width: 35,
        height: 35,
        decoration: BoxDecoration(
          color: Colors.deepPurple.withOpacity(0.3),
          borderRadius: BorderRadius.circular(17.5),
        ),
        child: Icon(
          icon,
          size: 20,
          color: Colors.deepPurple[800],
        ),
      ),
    );
  }
  
  void _showMessage(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('$message 被点击'),
        duration: Duration(seconds: 1),
      ),
    );
  }
}

4. 动画定位示例

import 'package:flutter/material.dart';

class AnimatedPositionedExample extends StatefulWidget {
  @override
  _AnimatedPositionedExampleState createState() => _AnimatedPositionedExampleState();
}

class _AnimatedPositionedExampleState extends State<AnimatedPositionedExample>
    with TickerProviderStateMixin {
  bool _moved = false;
  late AnimationController _controller;
  late Animation<double> _positionAnimation;
  late Animation<double> _scaleAnimation;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    
    _positionAnimation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.elasticOut,
    ));
    
    _scaleAnimation = Tween<double>(
      begin: 1.0,
      end: 1.5,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('动画定位示例'),
        backgroundColor: Colors.teal,
      ),
      body: Container(
        width: double.infinity,
        height: double.infinity,
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [Colors.teal[50]!, Colors.cyan[100]!],
          ),
        ),
        child: Stack(
          children: [
            // 静态背景网格
            _buildGridBackground(),
            
            // 动画定位的小球
            AnimatedBuilder(
              animation: _controller,
              builder: (context, child) {
                double left = 50 + (200 * _positionAnimation.value);
                double top = 100 + (150 * _positionAnimation.value);
                
                return Positioned(
                  left: left,
                  top: top,
                  child: Transform.scale(
                    scale: _scaleAnimation.value,
                    child: Container(
                      width: 60,
                      height: 60,
                      decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        gradient: RadialGradient(
                          colors: [Colors.teal[300]!, Colors.teal[600]!],
                        ),
                        boxShadow: [
                          BoxShadow(
                            color: Colors.teal.withOpacity(0.5),
                            blurRadius: 20,
                            offset: Offset(0, 10),
                          ),
                        ],
                      ),
                      child: Icon(
                        Icons.star,
                        color: Colors.white,
                        size: 30,
                      ),
                    ),
                  ),
                );
              },
            ),
            
            // 使用AnimatedPositioned的简化版本
            AnimatedPositioned(
              duration: Duration(milliseconds: 800),
              curve: Curves.bounceOut,
              left: _moved ? 280 : 50,
              top: _moved ? 400 : 300,
              child: GestureDetector(
                onTap: () {
                  setState(() {
                    _moved = !_moved;
                  });
                },
                child: Container(
                  width: 80,
                  height: 80,
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(20),
                    gradient: LinearGradient(
                      colors: [Colors.orange[300]!, Colors.red[400]!],
                    ),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.orange.withOpacity(0.4),
                        blurRadius: 15,
                        offset: Offset(0, 8),
                      ),
                    ],
                  ),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.touch_app, color: Colors.white, size: 30),
                      Text(
                        'TAP',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 12,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
            
            // 控制面板
            Positioned(
              bottom: 50,
              left: 20,
              right: 20,
              child: Container(
                padding: EdgeInsets.all(20),
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(15),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black12,
                      blurRadius: 10,
                      offset: Offset(0, 5),
                    ),
                  ],
                ),
                child: Column(
                  children: [
                    Text(
                      '动画控制',
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                        color: Colors.teal[800],
                      ),
                    ),
                    SizedBox(height: 15),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: [
                        ElevatedButton.icon(
                          onPressed: () {
                            _controller.forward();
                          },
                          icon: Icon(Icons.play_arrow),
                          label: Text('播放'),
                          style: ElevatedButton.styleFrom(
                            backgroundColor: Colors.teal,
                          ),
                        ),
                        ElevatedButton.icon(
                          onPressed: () {
                            _controller.reverse();
                          },
                          icon: Icon(Icons.replay),
                          label: Text('重置'),
                          style: ElevatedButton.styleFrom(
                            backgroundColor: Colors.orange,
                          ),
                        ),
                      ],
                    ),
                    SizedBox(height: 10),
                    Text(
                      '点击橙色方块查看 AnimatedPositioned 效果',
                      style: TextStyle(
                        fontSize: 14,
                        color: Colors.grey[600],
                      ),
                      textAlign: TextAlign.center,
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
  
  Widget _buildGridBackground() {
    return Positioned.fill(
      child: CustomPaint(
        painter: GridPainter(),
      ),
    );
  }
}

class GridPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.teal.withOpacity(0.1)
      ..strokeWidth = 1.0;
    
    // 绘制网格线
    for (double x = 0; x < size.width; x += 40) {
      canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
    }
    
    for (double y = 0; y < size.height; y += 40) {
      canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
    }
  }
  
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

实际应用场景

1. 图片标签和徽章

Widget _buildImageWithBadges() {
  return Container(
    width: 250,
    height: 200,
    child: Stack(
      children: [
        // 主图片
        Positioned.fill(
          child: ClipRRect(
            borderRadius: BorderRadius.circular(12),
            child: Image.network(
              'https://picsum.photos/250/200',
              fit: BoxFit.cover,
            ),
          ),
        ),
        
        // 新品标签 - 左上角
        Positioned(
          top: 8,
          left: 8,
          child: Container(
            padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
            decoration: BoxDecoration(
              color: Colors.red,
              borderRadius: BorderRadius.circular(12),
            ),
            child: Text(
              'NEW',
              style: TextStyle(
                color: Colors.white,
                fontSize: 12,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),
        
        // 折扣标签 - 右上角
        Positioned(
          top: 8,
          right: 8,
          child: Container(
            padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
            decoration: BoxDecoration(
              color: Colors.orange,
              borderRadius: BorderRadius.circular(12),
            ),
            child: Text(
              '-20%',
              style: TextStyle(
                color: Colors.white,
                fontSize: 12,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),
        
        // 喜欢按钮 - 右下角
        Positioned(
          bottom: 8,
          right: 8,
          child: CircleAvatar(
            radius: 20,
            backgroundColor: Colors.white.withOpacity(0.9),
            child: Icon(
              Icons.favorite_border,
              color: Colors.red,
              size: 20,
            ),
          ),
        ),
        
        // 底部渐变信息栏
        Positioned(
          left: 0,
          right: 0,
          bottom: 0,
          height: 60,
          child: Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.only(
                bottomLeft: Radius.circular(12),
                bottomRight: Radius.circular(12),
              ),
              gradient: LinearGradient(
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter,
                colors: [
                  Colors.transparent,
                  Colors.black.withOpacity(0.7),
                ],
              ),
            ),
            child: Padding(
              padding: EdgeInsets.all(12),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisAlignment: MainAxisAlignment.end,
                children: [
                  Text(
                    '产品名称',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  Text(
                    '¥299.00',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 14,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ],
    ),
  );
}

2. 自定义AppBar样式

Widget _buildCustomAppBar() {
  return Container(
    height: 200,
    child: Stack(
      children: [
        // 背景图片
        Positioned.fill(
          child: Container(
            decoration: BoxDecoration(
              gradient: LinearGradient(
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter,
                colors: [Colors.blue[400]!, Colors.blue[600]!],
              ),
            ),
          ),
        ),
        
        // 返回按钮
        Positioned(
          top: 40,
          left: 16,
          child: CircleAvatar(
            backgroundColor: Colors.white.withOpacity(0.2),
            child: IconButton(
              icon: Icon(Icons.arrow_back, color: Colors.white),
              onPressed: () => Navigator.pop(context),
            ),
          ),
        ),
        
        // 操作按钮
        Positioned(
          top: 40,
          right: 16,
          child: Row(
            children: [
              CircleAvatar(
                backgroundColor: Colors.white.withOpacity(0.2),
                child: IconButton(
                  icon: Icon(Icons.share, color: Colors.white),
                  onPressed: () {},
                ),
              ),
              SizedBox(width: 8),
              CircleAvatar(
                backgroundColor: Colors.white.withOpacity(0.2),
                child: IconButton(
                  icon: Icon(Icons.more_vert, color: Colors.white),
                  onPressed: () {},
                ),
              ),
            ],
          ),
        ),
        
        // 标题
        Positioned(
          bottom: 20,
          left: 16,
          right: 16,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                '自定义AppBar',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 28,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Text(
                '使用Positioned创建的复杂布局',
                style: TextStyle(
                  color: Colors.white.withOpacity(0.8),
                  fontSize: 16,
                ),
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

3. 浮动消息通知

class FloatingNotificationExample extends StatefulWidget {
  @override
  _FloatingNotificationExampleState createState() => _FloatingNotificationExampleState();
}

class _FloatingNotificationExampleState extends State<FloatingNotificationExample> {
  List<NotificationItem> _notifications = [];
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('浮动通知示例')),
      body: Stack(
        children: [
          // 主要内容
          Center(
            child: ElevatedButton(
              onPressed: _addNotification,
              child: Text('添加通知'),
            ),
          ),
          
          // 通知列表
          ..._notifications.map((notification) => _buildNotification(notification)),
        ],
      ),
    );
  }
  
  Widget _buildNotification(NotificationItem item) {
    return Positioned(
      top: 100 + (_notifications.indexOf(item) * 80.0),
      right: 16,
      child: SlideTransition(
        position: item.slideAnimation,
        child: FadeTransition(
          opacity: item.fadeAnimation,
          child: Container(
            width: 300,
            padding: EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(12),
              boxShadow: [
                BoxShadow(
                  color: Colors.black26,
                  blurRadius: 10,
                  offset: Offset(0, 4),
                ),
              ],
            ),
            child: Row(
              children: [
                CircleAvatar(
                  backgroundColor: item.color,
                  child: Icon(item.icon, color: Colors.white),
                ),
                SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        item.title,
                        style: TextStyle(fontWeight: FontWeight.bold),
                      ),
                      Text(
                        item.message,
                        style: TextStyle(fontSize: 12, color: Colors.grey[600]),
                      ),
                    ],
                  ),
                ),
                IconButton(
                  icon: Icon(Icons.close, size: 18),
                  onPressed: () => _removeNotification(item),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
  
  void _addNotification() {
    final notifications = [
      NotificationItem(
        title: '新消息',
        message: '您有一条新的消息',
        icon: Icons.message,
        color: Colors.blue,
      ),
      NotificationItem(
        title: '系统通知',
        message: '系统将在5分钟后维护',
        icon: Icons.notifications,
        color: Colors.orange,
      ),
      NotificationItem(
        title: '下载完成',
        message: '文件下载已完成',
        icon: Icons.download_done,
        color: Colors.green,
      ),
    ];
    
    setState(() {
      _notifications.add(notifications[_notifications.length % notifications.length]);
    });
    
    // 自动移除通知
    Timer(Duration(seconds: 3), () {
      if (_notifications.isNotEmpty) {
        _removeNotification(_notifications.first);
      }
    });
  }
  
  void _removeNotification(NotificationItem item) {
    setState(() {
      _notifications.remove(item);
    });
  }
}

class NotificationItem {
  final String title;
  final String message;
  final IconData icon;
  final Color color;
  late final AnimationController slideController;
  late final AnimationController fadeController;
  late final Animation<Offset> slideAnimation;
  late final Animation<double> fadeAnimation;
  
  NotificationItem({
    required this.title,
    required this.message,
    required this.icon,
    required this.color,
  });
}

性能优化建议

1. 避免频繁重建

// ❌ 错误:每次重建都创建新的Positioned
Widget badExample() {
  return Stack(
    children: [
      Positioned(
        left: _animationValue * 100,
        top: 50,
        child: ExpensiveWidget(), // 昂贵的组件每次都重建
      ),
    ],
  );
}

// ✅ 正确:缓存昂贵的子组件
class GoodExample extends StatefulWidget {
  @override
  _GoodExampleState createState() => _GoodExampleState();
}

class _GoodExampleState extends State<GoodExample> {
  late final Widget _expensiveChild;
  
  @override
  void initState() {
    super.initState();
    _expensiveChild = ExpensiveWidget(); // 只创建一次
  }
  
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Positioned(
          left: _animationValue * 100,
          top: 50,
          child: _expensiveChild, // 复用缓存的组件
        ),
      ],
    );
  }
}

2. 使用RepaintBoundary

// 对不经常变化的Positioned子组件使用RepaintBoundary
Stack(
  children: [
    Positioned(
      left: 0,
      top: 0,
      child: RepaintBoundary(
        child: StaticComplexWidget(), // 静态复杂组件
      ),
    ),
    Positioned(
      left: animatedValue,
      top: 50,
      child: AnimatedWidget(), // 经常变化的组件
    ),
  ],
)

3. 合理使用const构造函数

// 使用const构造函数减少重建
static const Widget _staticButton = Positioned(
  top: 50,
  right: 16,
  child: FloatingActionButton(
    onPressed: null, // const要求为null或静态函数
    child: Icon(Icons.add),
  ),
);

常见问题与解决方案

1. RenderFlex overflow 错误

问题:Positioned子组件超出Stack边界导致溢出错误

解决方案

// 方案1:设置Stack的clipBehavior
Stack(
  clipBehavior: Clip.hardEdge, // 或 Clip.antiAlias
  children: [
    Positioned(
      left: -50, // 可能超出边界
      child: Widget(),
    ),
  ],
)

// 方案2:使用约束确保在边界内
Stack(
  children: [
    Positioned(
      left: math.max(0, calculatedLeft), // 确保不小于0
      top: math.max(0, calculatedTop),   // 确保不小于0
      child: Widget(),
    ),
  ],
)

2. 布局约束冲突

问题:同时指定了相冲突的约束参数

解决方案

// ❌ 错误:同时指定了三个相冲突的参数
Positioned(
  left: 10,
  right: 20,
  width: 100, // 冲突!
  child: Widget(),
)

// ✅ 正确:只指定其中两个
Positioned(
  left: 10,
  right: 20,
  // width 会自动计算
  child: Widget(),
)

// 或者
Positioned(
  left: 10,
  width: 100,
  // right 位置由 left + width 决定
  child: Widget(),
)

3. 嵌套Stack中的定位问题

问题:在嵌套的Stack中Positioned定位错乱

解决方案

// 明确每个Positioned所属的Stack层级
Stack( // 外层Stack
  children: [
    Positioned(
      left: 50,
      top: 50,
      child: Container(
        width: 200,
        height: 200,
        child: Stack( // 内层Stack
          children: [
            Positioned(
              left: 10, // 相对于内层Stack的left
              top: 10,  // 相对于内层Stack的top
              child: Text('内层定位'),
            ),
          ],
        ),
      ),
    ),
    Positioned(
      right: 20, // 相对于外层Stack的right
      bottom: 20, // 相对于外层Stack的bottom
      child: Text('外层定位'),
    ),
  ],
)

最佳实践

1. 设计原则

  • 明确层级关系:合理规划Stack中各层组件的视觉层次
  • 避免过度定位:只在必要时使用Positioned,简单布局优先考虑其他组件
  • 保持响应式:使用相对单位或动态计算确保适配不同屏幕尺寸

2. 代码组织

class PositionedLayoutExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        _buildBackground(),      // 背景层
        _buildMainContent(),     // 主内容层
        _buildFloatingElements(), // 浮动元素层
        _buildOverlay(),         // 遮罩层
      ],
    );
  }
  
  Widget _buildBackground() => Positioned.fill(child: /*...*/);
  Widget _buildMainContent() => Positioned(/*...*/);
  Widget _buildFloatingElements() => Column(children: [/*...*/]);
  Widget _buildOverlay() => Positioned.fill(child: /*...*/);
}

3. 性能考虑

  • 缓存静态子组件:避免重复创建相同的组件
  • 使用RepaintBoundary:为复杂的静态组件添加重绘边界
  • 合理使用AnimatedPositioned:对于位置动画,考虑使用专门的动画组件

4. 测试建议

  • 多屏幕尺寸测试:确保定位在不同设备上正常显示
  • 边界情况测试:验证极端位置值的表现
  • 性能测试:监控复杂定位布局的渲染性能

总结

Positioned组件是Flutter中实现精确定位布局的核心工具,它提供了:

  1. 灵活的定位机制:支持相对于Stack边界的任意位置定位
  2. 智能的约束系统:自动计算缺失的位置或尺寸参数
  3. 丰富的构造函数:提供多种便捷的定位方式
  4. 良好的性能特性:适合复杂的层叠布局场景

掌握Positioned组件的原理和最佳实践,能够帮助开发者创建出专业、流畅的用户界面。在使用过程中需要注意约束规则、性能优化和响应式设计,确保应用在各种场景下都能提供优秀的用户体验。