Flutter入门概览4-UI入门篇

写应用最重要的是UI,这里介绍下常用的UI组件、布局概念、约束概念等。 这是Flutter四部曲的最后一篇。

一、Flutter UWidget

1.1 基础类组件

1.1.1 Text

less 复制代码
  // 计数器,显示变量
  Text(
    '$_counter',
    style: const TextStyle(
      color: Colors.red,
      fontSize: 32,
      fontWeight: FontWeight.bold,
    ),
  ),
  
  // 富文本 Text.rich、RichText
  const Text.rich(TextSpan(
    text: 'This is a ',
    children: <TextSpan>[
      TextSpan(
        text: 'rich',
        style: TextStyle(
            fontWeight: FontWeight.bold, color: Colors.blue),
      ),
      TextSpan(
          text: ' text example.',
          style: TextStyle(color: Colors.green)),
    ],
  )),
  • Text
  • TextStyle

1.1.2 Color

scss 复制代码
// `FF` for the alpha
const Color(0xFFE4E4E4)
 
Colors.red
Colors.blue
Colors.green

1.1.3 Button

  • ElevatedButton,凸起按钮,即"漂浮"按钮,它默认带有阴影和灰色背景。按下后,阴影会变大
less 复制代码
ElevatedButton(
  child: Text("normal"),
  onPressed: () {},
);
 
  • TextButton,文本按钮,默认背景透明并不带阴影。按下后,会有背景色
less 复制代码
TextButton(
  child: Text("normal"),
  onPressed: () {},
)
  • OutlinedButton,轮廓按钮,默认有一个边框,不带阴影且背景透明。按下后,边框颜色会变亮、同时出现背景和阴影(较弱)
less 复制代码
OutlinedButton(
  child: Text("normal"),
  onPressed: () {},
)
  • IconButton,图标按钮,是一个可点击的Icon,不包括文字,默认没有背景,点击后会出现背景
less 复制代码
IconButton(
  icon: Icon(Icons.thumb_up),
  onPressed: () {},
)
  • FloatingActionButton,悬浮按钮,圆形悬浮按钮,用于核心全局操作(如"新建")
less 复制代码
  FloatingActionButton(
    onPressed: _incrementCounter,
    tooltip: 'Increment',
    child: const Icon(Icons.add),
  ),
按钮类型 设计特点 典型场景
ElevatedButton 凸起阴影+背景色 主要操作(提交、确认)
TextButton 扁平无边框 次要操作(取消、跳过)
OutlinedButton 边框+无填充 中性操作(筛选、导出)
IconButton 纯图标 工具栏动作(删除、搜索)
FloatingActionButton 圆形悬浮 全局核心功能(新增、刷新)

1.1.4 Image

注:配置本地图片需要在pubspec.yaml中设置

php 复制代码
flutter:
  # 配置路径后才能使用
  assets:
    - assets/images/
 
  // 本地图片
  Image.asset(
    'assets/images/icon_arrow_right.png',
    width: 12,
    height: 14,
  )
  
  // 网络图片
  Image.network(item.bankLogo!)
  

1.1.5 Icon

csharp 复制代码
const Icon(Icons.add)

1.1.6 TextField

less 复制代码
Column(
  children: <Widget>[
    TextField(
      autofocus: true,
      decoration: InputDecoration(
        labelText: "用户名",
        hintText: "用户名或邮箱",
        prefixIcon: Icon(Icons.person)
      ),
    ),
    TextField(
      decoration: InputDecoration(
        labelText: "密码",
        hintText: "您的登录密码",
        prefixIcon: Icon(Icons.lock)
      ),
      obscureText: true,
    ),
  ],
);

1.2 容器装饰类组件

1.2.1 Padding

Padding可以给其子节点添加填充(留白),和边距效果类似。我们在前面很多示例中都已经使用过它了,现在来看看它的定义:

css 复制代码
Padding({
  ...
  EdgeInsetsGeometry padding,
  Widget child,
})

EdgeInsets

EdgeInsets是EdgeInsetsGeometry的子类,一般直接使用EdgeInsets。

EdgeInsets提供的便捷方法:

  • fromLTRB(double left, double top, double right, double bottom):分别指定四个方向的填充。
  • all(double value) : 所有方向均使用相同数值的填充。
  • only({left, top, right ,bottom }):可以设置具体某个方向的填充(可以同时指定多个方向)。
  • symmetric({ vertical, horizontal }):用于设置对称方向的填充,vertical指top和bottom,horizontal指left和right。

Padding示例:

less 复制代码
class PaddingTestRoute extends StatelessWidget {
  const PaddingTestRoute({Key? key}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return Padding(
      //上下左右各添加16像素补白
      padding: const EdgeInsets.all(16),
      child: Column(
        //显式指定对齐方式为左对齐,排除对齐干扰
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: const <Widget>[
          Padding(
            //左边添加8像素补白
            padding: EdgeInsets.only(left: 8),
            child: Text("Hello world"),
          ),
          Padding(
            //上下各添加8像素补白
            padding: EdgeInsets.symmetric(vertical: 8),
            child: Text("I am Jack"),
          ),
          Padding(
            // 分别指定四个方向的补白
            padding: EdgeInsets.fromLTRB(20, 0, 20, 20),
            child: Text("Your friend"),
          )
        ],
      ),
    );
  }
}

1.2.2 DecoratedBox

DecoratedBox可以在其子组件绘制前(或后)绘制一些装饰(Decoration),如背景、边框、渐变等。DecoratedBox定义如下:

less 复制代码
const DecoratedBox({
  Decoration decoration,
  DecorationPosition position = DecorationPosition.background,
  Widget? child
})
 
BoxDecoration({
  Color color, //颜色
  DecorationImage image,//图片
  BoxBorder border, //边框
  BorderRadiusGeometry borderRadius, //圆角
  List<BoxShadow> boxShadow, //阴影,可以指定多个
  Gradient gradient, //渐变
  BlendMode backgroundBlendMode, //背景混合模式
  BoxShape shape = BoxShape.rectangle, //形状
})
 
            DecoratedBox(
                // position: DecorationPosition.foreground,
                decoration: BoxDecoration(
                  color: Colors.blue,
                  border: Border.all(color: Colors.red, width: 2),
                  borderRadius: const BorderRadius.all(Radius.circular(16)),
                ),
                child: const Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text(
                    'This is a decorated box with padding.',
                    style: TextStyle(color: Colors.white),
                  ),
                )),
          ],

1.2.3 Container

Container是一个组合类容器,它本身不对应具体的RenderObject,它是DecoratedBox、ConstrainedBox、Transform、Padding、Align等组件组合的一个多功能容器,所以我们只需通过一个Container组件可以实现同时需要装饰、变换、限制的场景。下面是Container的定义:

less 复制代码
Container({
  this.alignment,
  this.padding, //容器内补白,属于decoration的装饰范围
  Color color, // 背景色
  Decoration decoration, // 背景装饰
  Decoration foregroundDecoration, //前景装饰
  double width,//容器的宽度
  double height, //容器的高度
  BoxConstraints constraints, //容器大小的限制条件
  this.margin,//容器外补白,不属于decoration的装饰范围
  this.transform, //变换
  this.child,
  ...
})
 
 
 
Container(
  margin: EdgeInsets.only(top: 50.0, left: 120.0),
  constraints: BoxConstraints.tightFor(width: 200.0, height: 150.0),//卡片大小
  decoration: BoxDecoration(  //背景装饰
    gradient: RadialGradient( //背景径向渐变
      colors: [Colors.red, Colors.orange],
      center: Alignment.topLeft,
      radius: .98,
    ),
    boxShadow: [
      //卡片阴影
      BoxShadow(
        color: Colors.black54,
        offset: Offset(2.0, 2.0),
        blurRadius: 4.0,
      )
    ],
  ),
  transform: Matrix4.rotationZ(.2),//卡片倾斜变换
  alignment: Alignment.center, //卡片内文字居中
  child: Text(
    //卡片文字
    "5.20", style: TextStyle(color: Colors.white, fontSize: 40.0),
  ),
 )

1.2.4 SizedBox

设置宽高

less 复制代码
  SizedBox(
    width: 50,
    height: 50,
    child: Container(
      margin: const EdgeInsets.only(top: 8.0),
      color: Colors.green,
    ),
  ),

1.2.5 Scaffold

Scaffold 是一个路由页的骨架,我们使用它可以很容易地拼装出一个完整的页面。包含:导航栏、底部导航、悬浮框、抽屉菜单等

less 复制代码
class ScaffoldRoute extends StatefulWidget {
  @override
  _ScaffoldRouteState createState() => _ScaffoldRouteState();
}
 
class _ScaffoldRouteState extends State<ScaffoldRoute> {
  int _selectedIndex = 1;
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( //导航栏
        title: Text("App Name"), 
        actions: <Widget>[ //导航栏右侧菜单
          IconButton(icon: Icon(Icons.share), onPressed: () {}),
        ],
      ),
      drawer: MyDrawer(), //抽屉
      bottomNavigationBar: BottomNavigationBar( // 底部导航
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
          BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
          BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
        ],
        currentIndex: _selectedIndex,
        fixedColor: Colors.blue,
        onTap: _onItemTapped,
      ),
      floatingActionButton: FloatingActionButton( //悬浮按钮
          child: Icon(Icons.add),
          onPressed:_onAdd
      ),
    );
  }
  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }
  void _onAdd(){
  }
}

1.3 滚动类组件

页面滚动需要使用滚动类组件。

1.4.1 SingleChildScrollView

滚动组件,不支持延迟加载模型,内容不超出屏幕太多时使用。

kotlin 复制代码
SingleChildScrollView({
  this.scrollDirection = Axis.vertical, //滚动方向,默认是垂直方向
  this.reverse = false, 
  this.padding, 
  bool primary, 
  this.physics, 
  this.controller,
  this.child,
})

1.4.2 ListVew

ListView是最常用的可滚动组件之一,它可以沿一个方向线性排布所有子组件,并且它也支持列表项懒加载(在需要时才会创建)。

less 复制代码
// 基本使用
ListView(
  shrinkWrap: true, 
  padding: const EdgeInsets.all(20.0),
  children: <Widget>[
    const Text('I'm dedicating every day to you'),
    const Text('Domestic life was never quite my style'),
    const Text('When you smile, you knock me out, I fall apart'),
    const Text('And I thought I was so smart'),
  ],
);
 
// ListView.builder,ListView.builder适合列表项比较多或者列表项不确定的情况,下面看一下ListView.builder的核心参数列表。
 
ListView.builder(
  itemCount: 100,
  itemExtent: 50.0, //强制高度为50.0
  itemBuilder: (BuildContext context, int index) {
    return ListTile(title: Text("$index"));
  }
);

1.4.3 GridView

网格布局是一种常见的布局类型,GridView 组件正是实现了网格布局的组件。GridView和ListView的大多数参数都是相同的,它们的含义也都相同的。

less 复制代码
GridView(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 3, //横轴三个子widget
      childAspectRatio: 1.0 //宽高比为1时,子widget
  ),
  children:<Widget>[
    Icon(Icons.ac_unit),
    Icon(Icons.airport_shuttle),
    Icon(Icons.all_inclusive),
    Icon(Icons.beach_access),
    Icon(Icons.cake),
    Icon(Icons.free_breakfast)
  ]
);

1.4 弹窗类组件

3.4.1 AlertDialog

less 复制代码
  showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: const Text("AlertDialog"),
        content: const Text("This is an AlertDialog"),
        actions: <Widget>[
          TextButton(
            child: const Text("OK"),
            onPressed: () {
              Navigator.of(context).pop();
            },
          ),
        ],
      );
    },
  );

3.4.2 SimpleDialog

SimpleDialog也是Material组件库提供的对话框,它会展示一个列表,用于列表选择的场景。

less 复制代码
  showDialog(
    context: context,
    builder: (context) {
      return SimpleDialog(
        title: const Text("SimpleDialog"),
        children: <Widget>[
          SimpleDialogOption(
            onPressed: () {
              Navigator.of(context).pop();
            },
            child: const Text("Option 1"),
          ),
          SimpleDialogOption(
            onPressed: () {
              Navigator.of(context).pop();
            },
            child: const Text("Option 2"),
          ),
        ],
      );
    },
  );

3.4.3 showModalBottomSheet

底部弹窗

less 复制代码
  showModalBottomSheet<int>(
    context: context,
    builder: (BuildContext context) {
      return ListView.builder(
        itemCount: 30,
        itemBuilder: (BuildContext context, int index) {
          return ListTile(
            title: Text("$index"),
            onTap: () => Navigator.of(context).pop(index),
          );
        },
      );
    },
  );

二、布局常用Widget

重点阅读:

2.1 布局组件

  • Center,居中
  • Row,横向排列,不能换行
  • Column,竖向排列,不能换列
  • Flex、Expanded,实现 flex: 1的效果,注意Row/Column都集成自Flex
  • Wrap,自动换行
  • Flow,比较复杂,使用场景较少
  • Stack、Postioned,实现绝对布局的效果
  • Align、Alignment,实现对齐和相对定位

2.1.1 示例1:

2.1.2 示例2:

2.2 布局约束

重点阅读:深入理解 Flutter 布局约束

2.2.1 约束基本介绍

约束是父组件对子组件的尺寸限制,通过 BoxConstraints 对象定义,包含四个关键属性:

  • minWidth / maxWidth :宽度允许的最小/最大值;
  • minHeight / maxHeight :高度允许的最小/最大值。

约束类型:

  • 严约束(Tight Constraints),minWidth = maxWidth 且 minHeight = maxHeight,子组件必须使用父组件指定的 固定尺寸。
  • 松约束(Loose Constraints),minWidth = 0, minHeight = 0,子组件可在最大范围内自由选择尺寸

有三个核心Widget:

  • BoxConstraints,基础约束信息,存放:最大宽度、最小宽度、最大高度、最小高度。
  • ConstrainedBox,用于给子组件添加约束信息。
  • UnconstrainedBox,子组件不受约束限制。
less 复制代码
// BoxConstraints
const BoxConstraints({
  this.minWidth = 0.0, //最小宽度
  this.maxWidth = double.infinity, //最大宽度
  this.minHeight = 0.0, //最小高度
  this.maxHeight = double.infinity //最大高度
})
 
// ConstrainedBox
ConstrainedBox(
  constraints: BoxConstraints(
    minWidth: double.infinity, //宽度尽可能大
    minHeight: 50.0 //最小高度为50像素
  ),
  child: Container(
    height: 5.0, 
    child: redBox ,
  ),
)
 
// ConstrainedBox
ConstrainedBox(
  constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0),  //父
  child: UnconstrainedBox( //"去除"父级限制
    child: ConstrainedBox(
      constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
      child: redBox,
    ),
  )
)

2.2.2 约束的基本原理

布局过程分为三个阶段:

  1. 向下传递约束​,父组件根据自身约束和布局逻辑,生成子组件的约束并传递。

  2. 子组件确定尺寸,子组件在约束范围内计算自身尺寸,并向上返回结果(如 Text 根据文字内容计算大小)。

  3. ​​父组件定位​,父组件根据子组件尺寸和布局规则(如 Row 的水平排列、Stack 的堆叠)确定子组件的坐标。

Flutter容器组件约束类型汇总:

容器组件 约束类型 具体约束行为 典型使用场景
屏幕(Screen/RenderView) 紧约束 强制最外层组件填满整个屏幕(BoxConstraints.tight(屏幕尺寸)) 应用根布局,确保内容全屏显示
Scaffold.body 宽约束 水平轴:[0, 屏幕宽度] 垂直轴:[0, 屏幕高度-AppBar高度等](允许子组件自由选择尺寸) 页面主体内容区域,支持灵活尺寸
Container 动态约束 若设置宽高则施加紧约束; 未设置尺寸时传递父级约束(通常为宽约束) 自定义尺寸、背景色、边距的容器
Align 宽约束 将父级约束转换为最小值为 0 的宽松约束 (允许子组件自由选择尺寸,不超过父容器范围) 子组件在父容器内自定义对齐位置
Padding 宽约束 保留父级约束,子组件尺寸限制在父约束 - 内边距范围内1 增加内边距
Center 宽约束 将父级紧约束转为最小值为0的宽约束(如 BoxConstraints.loose()) 子组件居中且可小于父容器
ConstrainedBox 紧约束 强制子组件满足额外的最小/最大宽高限制(与父约束取交集) 精确控制子组件尺寸范围
SizedBox 紧约束 强制子组件采用指定宽高(忽略父级约束) 固定尺寸占位或强制子组件尺寸
UnconstrainedBox 无约束 移除父级约束(但自身尺寸仍受父级限制,超出的部分会被裁剪) 需要子组件完全自主决定尺寸的场景
AspectRatio 紧约束 强制子组件满足特定宽高比(在父级约束范围内计算) 保持固定宽高比的元素(如方形图片)
FractionallySizedBox 宽约束 按比例占用父组件空间(widthFactor/heightFactor控制占比) 响应式布局(如按钮宽度占父容器70%)
ListView/ScrollView 无界约束 垂直轴:[0, Infinity] 水平轴:[0, 屏幕宽度] 可滚动内容区域
示例1:
scss 复制代码
Container(color: red)

Container 充满了整个屏幕

示例2:
less 复制代码
Container(width: 100, height: 100, color: red)

Container 充满了整个屏幕

示例3:Center
less 复制代码
Center(child: Container(width: 100, height: 100, color: red))

Center 充满了屏幕,Container 变成 100 × 100。

示例4:Align
less 复制代码
Align(
  alignment: Alignment.bottomRight,
  child: Container(width: 100, height: 100, color: red),
)

Container大小100*100。

相关推荐
恋猫de小郭2 小时前
Android CLI ,谷歌为 Android 开发者专研的 AI Agent,提速三倍
android·前端·flutter
火柴就是我3 小时前
flutter pushAndRemoveUntil 的一次小疑惑
flutter
于慨3 小时前
flutter doctor问题解决
flutter
唔663 小时前
flutter 图片加载类 图片的安全使用
安全·flutter
Nathan202406165 小时前
Flutter - InheritedWidget
flutter·dart
恋猫de小郭5 小时前
JetBrains Amper 0.10 ,期待它未来替代 Gradle
android·前端·flutter
Lanren的编程日记6 小时前
Flutter鸿蒙应用开发:实时聊天功能集成实战
flutter·华为·harmonyos
Utopia^15 小时前
鸿蒙flutter第三方库适配 - 联系人备份工具
flutter·华为·harmonyos
念格1 天前
Flutter 仿微信输入框最佳实践:自适应高度 + 超行数智能切换全屏
前端·flutter
程序员老刘1 天前
《Flutter跨平台开发核心技巧与应用》新书来了
flutter·ai编程·客户端