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。

相关推荐
未来猫咪花2 小时前
告别卡顿和耗电!view_model 的 Pause 机制如何拯救你的 Flutter 应用
flutter
metaRTC4 小时前
webRTC IPC客户端Flutter版编程指南
flutter·webrtc·ipc
liuxf12344 小时前
鸿蒙Flutter,No Hmos SDK found.
flutter·华为·harmonyos
西西学代码10 小时前
Flutter---Listview横向滚动列表(1)
flutter
XI锐真的烦13 小时前
Flutter Windows 下“Running Gradle task ‘assembleDebug‘...” 卡住一整天的终极解决办法
windows·flutter
苦逼的搬砖工1 天前
基于 easy_rxdart 的轻量响应式与状态管理架构实践
android·flutter
SoaringHeart1 天前
Flutter组件封装:标签拖拽排序 NDragSortWrap
前端·flutter
天天开发1 天前
Flutter每日库: local_auth本地设备验证插件
flutter