一、Flutter 布局的核心哲学:约束传递(Constraints)

Flutter 的布局过程遵循一个简单但强大的规则:

“父组件给子组件施加约束(Constraints),子组件在约束范围内决定自己的尺寸。”

这个过程分为两步:

  1. 向下传递约束(Parent → Child)
  2. 向上传递尺寸(Child → Parent)

约束(Constraints)是什么?

  • 通常是一个 BoxConstraints 对象
  • 包含四个属性:minWidthmaxWidthminHeightmaxHeight
  • 子组件必须在这个范围内选择自己的宽高

举个例子:

Container(
  color: Colors.red,
  child: Container(
    color: Colors.blue,
    width: 300,
    height: 200,
  ),
)
  • 外层 Container 没有明确尺寸 → 它会尽可能大(填满父级,如屏幕)
  • 内层 Container 被允许在 [0, ∞] × [0, ∞] 范围内选择尺寸 → 它选了 300×200

但如果把内层放在 Center 里:

Center(
  child: Container(
    color: Colors.blue,
    // width/height 未指定!
  ),
)
  • Center 给子组件的约束是:minWidth=0, maxWidth=∞(宽松约束)
  • 但 Container 没有内容、没有尺寸 → 它会选择 最小尺寸:0×0 → 看不到!

子组件的尺寸不仅取决于自己,更取决于父组件给的“自由度”


二、常用布局组件分类详解

单子布局(Single-child Layout)

组件 作用 特点
Container 万能容器 可设置 padding/margin/color/alignment/constraints
Padding 内边距 比 Container 更轻量
Center 居中对齐 默认填满父级,子居中
Align 自定义对齐 支持 Alignment.topLeft 等
SizedBox 固定尺寸 width: 100 或 height: 50,常用于间隔
ConstrainedBox 施加约束 直接使用 BoxConstraints
 实战技巧:
  • 用 SizedBox(width: 16) 替代 Container(width: 16) 更高效
  • Container 是 Padding + Align + DecoratedBox + ConstrainedBox 的组合体

多子线性布局(Multi-child Linear)

组件 方向 是否可滚动
Row 水平 不可滚动
Column 垂直 不可滚动
ListView 垂直(默认) 可滚动
GridView 网格 可滚动
常见错误:Expanded 与 Flexible
Row(
  children: [
    Text('Left'),
    Expanded(child: Text('Fill remaining space')), // 正确
    Text('Right'),
  ],
)
  • Expanded = Flexible(flex: 1, fit: FlexFit.tight)
  • 必须放在 Row/Column/Flex 内部
  • 如果子项总和超过可用空间 → 溢出错误(Yellow/Black Stripe)
解决溢出:
  • 使用 SingleChildScrollView
  • 限制子项最大宽度:ConstrainedBox(maxWidth: ...)
  • 使用 Text(overflow: TextOverflow.ellipsis)

弹性布局(Flex)

实现了一个简单的垂直方向弹性布局(Flex),将屏幕分为三部分,按比例分配高度。

import 'package:flutter/material.dart';

void main() {
  runApp(MainPage());
}
class MainPage extends StatelessWidget{
  const MainPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context){
    return MaterialApp(
      home:Scaffold(
        body:Container(
          height: MediaQuery.of(context).size.height,
          width: MediaQuery.of(context).size.width,
          decoration: BoxDecoration(
            color: Colors.grey,
          ),
          child:Flex(direction:Axis.vertical,
          children:[
            Expanded(
              flex:2,
              child:Container(
                width:100,
                color: Colors.blue,
              ),
              ),
            Expanded(
              flex:1,
              child:Container(
                width:100,
                color: Colors.red,
              ),
            ),
            Expanded(
              flex:1,
              child:Container(              
                width:100,
                color: Colors.green,
              ),
            ),
          ]
          )
        )
        )
    );
  }
}

实现效果:


层叠布局(Stack)

用于绝对定位、重叠元素(如浮层、徽标、自定义控件):

import 'package:flutter/material.dart';

void main() {
  runApp(MainPage());
}
class MainPage extends StatelessWidget{
  const MainPage({Key? key}) : super(key: key);
  List<Widget> getList(){
    return List.generate(10,(index){
      return Container(
        width:100,
        height:100,
        color: Colors.primaries[index],
      );
    });
    
  }
  @override
  Widget build(BuildContext context){
    return MaterialApp(
      home:Scaffold(
        body:Container(
          width:double.infinity,
          height:double.infinity,
          color: Colors.grey,
          child: 
          Stack(
          alignment:Alignment.center,
          children:[
            Container(
              width:300,
              height:300,
              color: Colors.blue,
            ),
            Container(
              width:200,
              height:200,
              color: Colors.red,
            ),
            Container(
              width:100,
              height:100,
              color: Colors.green,
            ),
            Container(
              width:50,
              height:50,
              color: Colors.yellow,
            ),
            Positioned(
              bottom:100,
              right:100,
              child:Container(
                width:50,
                height:50,
                color: Colors.purple,
              ),
            )
          ]
        )
        )
        )
    );
  }
}

实现效果:


流式布局(Wrap)

实现了一个自动换行的色块网格,结合了静态定义的容器和动态生成的列表

import 'package:flutter/material.dart';

void main() {
  runApp(MainPage());
}
class MainPage extends StatelessWidget{
  const MainPage({Key? key}) : super(key: key);
  List<Widget> getList(){
    return List.generate(10,(index){
      return Container(
        width:100,
        height:100,
        color: Colors.primaries[index],
      );
    });
    
  }
  @override
  Widget build(BuildContext context){
    return MaterialApp(
      home:Scaffold(
        body:Container(
          height: MediaQuery.of(context).size.height,
          width: MediaQuery.of(context).size.width,
          decoration: BoxDecoration(
            color: Colors.grey,
          ),
          child:Wrap(
            direction:Axis.horizontal,
            alignment:WrapAlignment.center,
            spacing:10,
            runSpacing:10,
            children:[
              Container(
                width:100,
                height:100,
                color: Colors.blue,
              ),
              Container(
                width:100,
                height:100,
                color: Colors.red,
              ),
              Container(
                width:100,
                height:100,
                color: Colors.green,
              ),
              ...getList()
            ]
          )
        )
        )
    );
  }
}

实现效果:


SingleChildScrollView组件案例

实现了一个带滚动功能的长列表页面,并提供了两个悬浮按钮:

  • 顶部“向下箭头”:点击后平滑滚动到列表底部
  • 底部“向上箭头”:点击后平滑滚动回顶部

整体使用 Stack 布局将可滚动内容与悬浮按钮叠加显示

import 'package:flutter/material.dart';

void main() {
  runApp(MainPage());
}
class MainPage extends StatefulWidget {
  MainPage({Key? key}) : super(key: key);

  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  final ScrollController _controller = ScrollController();//滚动控制器
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
       home:Scaffold(appBar:AppBar(title:Text("滚动列表"),) ,
       body:Stack(
        children: [
          SingleChildScrollView(
            controller: _controller,
            padding: EdgeInsets.all(20),
            child: Column(
            children:List.generate(100, (index){
              return Container(
                margin: EdgeInsets.all(10),
                width: double.infinity,
                color: Colors.pink,
                height: 100,
                child:Text("我是第${index+1}个容器",style: TextStyle(color: Colors.white,fontSize: 20,)),
                alignment: Alignment.center,
              );
            }),
       ),
       ),
       Positioned(
        right: 20,
        top: 20,
        child: GestureDetector(
          onTap: (){
            _controller.animateTo(_controller.position.maxScrollExtent, duration: Duration(seconds: 1), curve: Curves.ease);
            print("点击了向下箭头");
          },
          child:Container(
          decoration: BoxDecoration(
            color: Colors.grey,
            borderRadius: BorderRadius.circular(40),
          ),
          padding: EdgeInsets.all(10),
          child: Icon(Icons.arrow_circle_down,color: Colors.white,),
        ),
        )
       ),
       Positioned(
        right: 20,
        bottom: 20,
        child: GestureDetector(
          onTap: (){
            _controller.animateTo(0, duration: Duration(seconds: 1), curve: Curves.ease);
            print("点击了向上箭头");
          },
          child:Container(
          decoration: BoxDecoration(
            color: Colors.grey,
            borderRadius: BorderRadius.circular(40),
          ),
          padding: EdgeInsets.all(10),
          child: Icon(Icons.arrow_circle_up,color: Colors.white,),
        )
          )
          
       )
        ]
    )));  
  }
}

实现效果:


CustomScrollView吸顶案例

使用 CustomScrollView + Sliver 系列组件 构建了一个复杂的、具有 吸顶效果(Sticky Header) 的滚动页面,包含轮播图、分类导航栏、商品网格和列表项

import 'package:flutter/material.dart';

void main() {
  runApp(MainPage());
}
class MainPage extends StatefulWidget {
  MainPage({Key? key}) : super(key: key);

  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  final ScrollController _controller = ScrollController();//滚动控制器
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
       home:Scaffold(appBar:AppBar(title:Text("滚动列表"),) ,
       body:CustomScrollView(
        slivers: [
          SliverToBoxAdapter(
            child: Container(
              height: 100,
              color: Colors.blue,
              child:Text("轮播图",
              style:TextStyle(
                fontSize: 20,
                color: Colors.white,
              )),
              alignment: Alignment.center,
            ),
          ),
          SliverToBoxAdapter(
            child:SizedBox(
              height: 20,
            )
          ),
          SliverPersistentHeader(delegate: _StickyCategory(), pinned: true,),
          SliverToBoxAdapter(
            child:SizedBox(
              height: 20,
            )
          ),
          SliverGrid.count(
            crossAxisCount: 2,
            mainAxisSpacing: 10,
            crossAxisSpacing: 10,
            children:List.generate(100,(index){
              return Container(alignment: Alignment.center,child: Text("第${index+1}个商品",style: TextStyle(fontSize: 20,color: Colors.white,),),color: Colors.blue,);

            }),
          ),
          SliverList.separated(
            itemCount: 100,
            separatorBuilder: (BuildContext context, int index) => SizedBox(height: 10,),
            itemBuilder: (BuildContext context, int index) {
              return Container(
                height: 100,
                color: Colors.blue,
                child: Text('Item $index'),
              );
            },
          ),
        ],
       )
    ));  
  }
}

class _StickyCategory extends SliverPersistentHeaderDelegate {
  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      child:ListView.builder(
        scrollDirection: Axis.horizontal,
        itemCount: 10,
        itemBuilder: (BuildContext context,int index){
          return Container(
            width: 100,
            color: Colors.blue,
            alignment: Alignment.center,
            margin: EdgeInsets.symmetric(horizontal: 10),
            child:Text("第${index+1}个分类",
            style:TextStyle(
              color: Colors.white,
            )),
          );
        },
      ),
      alignment: Alignment.center,
    );
  }
  
  @override
  // TODO: implement maxExtent
  double get maxExtent => 100;//最大展开高度
  
  @override
  // TODO: implement minExtent
  double get minExtent => 60;//最小折叠高度
  
  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    // TODO: implement shouldRebuild
    return false;
  }
}

实现效果:

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐