Flutter 基础 | 控件 & 布局(一)

简介: Flutter 基础 | 控件 & 布局(一)

Flutter 中一切皆控件。这一篇会讲述基础控件的使用方法,并以一个实例展示如何综合运用它们。


了解 Dart 的基础语法才能无障碍地阅读本篇内容,具体介绍可以点击Flutter 基础 | Dart 语法


界面入口


当你新建一个 Flutter app 之后,就能深刻地感受到一切皆控件。


Flutter App 程序的入口是在 lib 目录下main.dart文件中的main()方法:


void main() {
  runApp(MyApp());
}


其中MyApp就是一个控件:


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        appBar: AppBar(title: const Text('Welcome to Flutter')),
        body: , // 可以在这里填充想展示的控件
      ),
    );
  }
}


MyApp 继承自StatelessWidget,它是一个自定义的无状态控件。自定义控件时得重写build(),该方法返回一个Widget,它是所有控件的基类。


这次返回的是 MaterialApp,它是一个符合 material design 的控件。给他设置了 3 个属性,其中home属性的类型也是一个控件,构建了一个包含 AppBar 和 body 的控件Scaffold,应用程序的业务界面大多都往 body 中填充。


可以看出 Flutter 中的控件是直接 new 出来的,构建布局是声明式的,布局和逻辑是混合在一起的。


文本


从最基本的文本开始:


Text(
  'abc', //内容
  style: TextStyle(
    fontSize: 12, // 字号
    fontWeight: FontWeight.w400, // 字重
    color: Colors.blue, // 字色
    fontFamily: 'pingfang' // 字体
  ),
  maxLines: 1 // 最大行数
);


自定义字体


上述代码构建了一个文本控件,且为文本定制了样式。其中引用的自定义字体得先将字体文件存放在app/fonts目录下(若没有则新建):


image.png


然后在pubspec.yaml文件中加载字体,然后就能在 dart 中引用了:


image.png


自定义颜色


除了使用系统预定义的Colors.xxx颜色外,也可以使用自定义颜色:


Text(
  'abc', 
  style: TextStyle(
    color: Color(0xFFB9BEC5), // 自定义颜色
  ),
);


直接构造一个Color对象并传入色值的十六进制。


富文本


// ‘我是程序员’被分割成两段,以用不同字号及颜色展示
Text.rich(TextSpan(children: [
  TextSpan(
      text: '我是',
      style: TextStyle(
          fontSize: 10, 
          color: Colors.blue
      )
  ),
  TextSpan(
      text: '程序员', 
      style: TextStyle(
          color: Colors.red, 
          fontSize: 12
      )
  )
]))


Text.rich()是 Text 控件的一个命名构造方法,构造的同时传入了一个TextSpan对象,它有一个children属性,表示可以传入若干个TextSpan对象,每个都可独立地设置文本属性,这样可以将一段文字,分解成若干个 TextSpan 对象,以实现富文本效果。


图片


加载本地图片


Image.asset(
  'images/hot_week.webp', // 本地图片名
),


上述代码构建了一个Image控件用于展示本地图片,其中图片资源 images/hot_week.webp 得先存放在app/images目录下(没有就新建):


image.png


然后在pubspec.yaml文件中加载图片,就能在 dart 中引用了:


image.png


加载网络图片


Image.network(
  circle?.url ?? "",
  fit: BoxFit.cover,
)


Image 控件有一个方便的命名方法network()只需传入图片 url 就能完成异步加载图片。


其中的fit属性表示,图片应该怎么样去适应控件的大小。BoxFit.cover 表示等比例缩放图片,直到将控件宽高填满。


控件尺寸


Flutter 中控件尺寸不由其自身决定的,而是由它的父控件。


这是在 Flutter 中布局时必须铭记在心的一句谚语。


所以如果想展示一个 200 * 200 的图片,只能这样写:


Container(
  width: 200,
  height: 200,
  child: Image.asset('images/hot_week.webp')
)


对!在外面包一层Container,然后再指定 Container 的宽高。


试着将上述代码继承到 MaterialApp 中:


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Container(
        width: 200,
        height: 200,
        child: Image.asset('images/hot_week.webp')
      )
    );
  }
}


运行代码,会惊奇地发现图片撑满了整个屏幕。那条谚语生效了。并不是 Container 说自己想要多大就能展示多大的,这得由它的父控件说了算,很不巧它的父控件 MaterialApp 对孩子有约束,它要求子控件必须撑满整个父控件,所以给 Container 指定的宽高就不生效了。


换一个写法:


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        appBar: AppBar(title: const Text('Welcome to Flutter')),
        body: Container(
          width: 200,
          height: 200,
          color: Colors.red
        ),
      ),
    );
  }
}


这次让 200 * 200 的图片成为 Scaffold 的孩子,效果就符合预期了。因为 Scaffold 对孩子的约束是“孩子想多大就多大,但不能超过我”。


这就引出了 Flutter 布局的第二句谚语:


父控件总是会施加一个约束给孩子,这个约束会决定孩子宽高的取值范围以及相对位置。


边距 背景 圆角


Container 控件除了能指定尺寸外,还能指定内外边距、背景色、圆角。


Container(
  decoration: BoxDecoration(
      borderRadius: BorderRadius.all(Radius.circular(8)), // 圆角
      color:Colors.red // 填充色
  ),
  padding: EdgeInsets.fromLTRB(10, 5, 10, 5),// 内边距,分别指定左上右下
  margin: EdgeInsets.all(20),// 外边距,统一指定上下左右都是 20
  child: Text("这是一个带圆角的文本"),
)


上述代码展示如下:


image.png


“通过包裹一层父控件来决定边距、背景、圆角这些属性”,这个思路和 Android 原生构建界面的思路不太一样。原生的世界里面这些都是控件自身的属性。这样做有一个显而易见的好处就是解耦:边距、背景、圆角,这些特性可以无障碍地附着在任何一个控件上,而不像原生中每自定义一个控件都需要独自考虑边距、背景、圆角问题。


线性容器


Flutter 中有两种不带滚动效果的线性容器,横向的Row,纵向的Column


Row(
  children: [
    Text('look at here--'),
    Text('123--'),
    Text('and--'),
    Text('more--'),
    Text('gift--'),
  ],
)


上述代码的展示效果如下:


image.png


上图中的蓝色线条表示控件的边界,它是 Flutter 自带的界面调试工具,只需要在 main() 方法中设置debugPaintSizeEnabled为 true,然后重新运行 app 即可(hot restart 不生效):


void main() {
  debugPaintSizeEnabled = true; 
  runApp(MyApp());
}


可以看到 Column 的父控件要求它横向撑满屏幕,而 Column 对自己的孩子没有任何约束,它们可以自己决定自己的尺寸。


换成纵向容器,写法是类似的:


Column(
  children: [
    Text('look at here--'),
    Text('123--'),
    Text('and--'),
    Text('more--'),
    Text('gift--'),
  ],
)


上述代码的展示效果如下:


image.png


同样的,Column 也被父控件要求纵向撑满整个屏幕,但并未对子控件的尺寸添加约束。


Expanded


如果某个线性容器的子控件希望撑满容器的剩余空间,可以这样写:


 Row(
  children: [
    Expanded(child: Text('look at here--')),// 撑满容器剩余空间
    Text('123--'),
    Text('and--'),
    Text('more--'),
    Text('gift--'),
  ],
)


上述代码的效果如下:


image.png


如果线性容器的每个孩子都被 Expanded 包裹,则可以通过flex属性来决定他们的比例:


Row(
  children: [
    Expanded(child: Text('look at here--'), flex: 2),
    Expanded( child: Text('123--'), flex: 3, ),
    Expanded( child: Text('and--'), flex: 4, ),
    Expanded( child: Text('more--'), flex: 1, ),
    Expanded( child: Text('gift--'), flex: 2, ),
  ],
)


上述代码展示效果如下:


image.png


对齐方式


线性容器的对齐方式分为两个轴,主轴mainAxisAlignment和交叉轴crossAxisAlignment


对于横向容器,主轴是水平方向的,与水平方向十字交叉的轴是垂直方向的。


对于纵向容器,主轴是垂直方向的,与垂直方向十字交叉的轴是水平方向的。


Column(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,// 在主轴上平分
  children: [
    Text('look at here--'),
    Text('123--'),
    Text('and--'),
    Text('more--'),
    Text('gift--'),
  ],
)


上述代码的效果如下:


image.png


因为父控件要求 Column 纵向撑满整个屏幕,所以在主轴上平分布局的就让子控件平均地分布在屏幕的垂直方向上,从蓝色边框可以看出控件本身的尺寸并未发生变化,只是相对于父控件的位置变化了。


若希望在上图的基础上让子控件左对齐,则可以这样写:


Column(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,// 在主轴上平分
  crossAxisAlignment: CrossAxisAlignment.start, // 在交叉轴上左对齐
  children: [
    Text('look at here--'),
    Text('123--'),
    Text('and--'),
    Text('more--'),
    Text('gift--'),
  ],
)


上述代码展示效果如下:


image.png


溢出


因为 Column 和 Row 都是不可滚动的控件,所以如果主轴上子控件太多,则会导致无法完全展示,这叫溢出:


Row(
  children: [
    Text('look at here--'),
    Text('123--'),
    Text('and--'),
    Text('more--'),
    Text('gift--'),
    Text('gift--'),
    Text('gift--'),
    Text('gift--'),
    Text('gift--'),
    Text('gift--'),
    Text('gift--'),
  ],
)


故意多加了几个控件,想在水平方向上产生溢出:


image.png


当溢出发生时,会展示黄黑相间的警告。IDE 也会报错A RenderFlex overflowed by 28 pixels on the right.


再来看一个报错的例子:


 Row(
  children: [
    Text('look at here--'),
    Text('123--'),
    Text('and--'),
    Text('more--'),
    Text('gift--'),
    ListView(scrollDirection: Axis.horizontal),// 横向列表
  ],
)


在横向容器中添加了一个横向列表。demo 也跑步起来后 IDE 报错如下Horizontal viewport was given unbounded width.,即横向视窗没有水平方向上尺寸的约束。


因为 Column 不会约束子控件,任其在水平和垂直方向上生长,很巧的是其中一个孩子是ListView,它也是一个可以自由生长的控件,并且生长方向是水平。两个在同一方向上自由生长的控件互为父子的时候,父控件就不知道自己的尺寸到底应该有多大了。


解决办法如下:


Container(
  color: Colors.red,
  child: Row(
    children: [
      Text('look at here--'),
      Text('123--'),
      Text('and--'),
      Text('more--'),
      Text('gift--'),
      ListView(
        scrollDirection: Axis.horizontal,
        shrinkWrap: true,
      ),
    ],
  ),
)


增加一个属性shrinkWrap表示 ListView 的尺寸尽可能地小,能正好包裹子控件就好,这样 ListView 的尺寸就确定下来了,从而 Column 的尺寸也就确定了。


为了清晰地看清 Row 的边界,特意用 Container 包裹它并添加了红色背景,运行一下:


image.png


报错是没有了,但效果也和预期不太相符。本来父容器只是让 Row 在水平方向上撑满屏幕,现在因 ListView 的加入,Row 在垂直方向上也撑满了屏幕。


这是因为 ListView 对子控件的高没有约束,并且自己的高就是子控件高的最大值,Row 也是同理。这样的话,只要 ListView 子控件足够高,则 Row 有超出屏幕底部的可能,但为啥这时没有溢出警告?因为父容器对 Row 的约束是“横向撑满屏幕,纵向你爱多高就多高,但最高不能超过我”。


这时 Flutter 布局的第三句谚语就要来了:


子控件尺寸和位置虽然受到父控件的约束,但子控件的尺寸有时候也可以影响到父控件的尺寸。


综合运用


来看一个综合运用的例子:导航栏


image.png


导航栏包含了 3 个横向铺开的按钮,每个按钮又是一个 Image 和 Text 的纵向组合。所以导航栏应该是一个 Row 包含了 三个 Column,先看下每个 Column 怎么写:


Column(
  mainAxisSize: MainAxisSize.min, // 限制列高度,让它总是最小化,正好包裹子控件就好
  mainAxisAlignment: MainAxisAlignment.center, // 子控件居中
  children: [
    Image.asset('images/call.webp'), // 图标
    Container( // 容器(为了增加图标和文字间距)
      margin: EdgeInsets.only(top: 8), // 间距
      child: Text( // 文字
        "CALL",
        style: TextStyle(
          fontSize: 12,
          fontWeight: FontWeight.w400,
          color: Colors.blue,
        ),
      ),
    ),
  ],
)


上述代码展示效果如下:


image.png


然后将 3 个 Column 嵌入 Row


void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Welcome to Flutter'),
        ),
        body: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            // 电话列
            Column(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Image.asset('images/call.webp'),
                Container(
                  margin: EdgeInsets.only(top: 8),
                  child: Text(
                    "CALL",
                    style: TextStyle(
                      fontSize: 12,
                      fontWeight: FontWeight.w400,
                      color: Colors.blue,
                    ),
                  ),
                ),
              ],
            ),
            // 路由列
            Column(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Image.asset('images/route.webp'),
                Container(
                  margin: EdgeInsets.only(top: 8),
                  child: Text(
                    "ROUTE",
                    style: TextStyle(
                      fontSize: 12,
                      fontWeight: FontWeight.w400,
                      color: Colors.blue,
                    ),
                  ),
                ),
              ],
            ),
            // 分享列
            Column(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Image.asset('images/share.webp'),
                Container(
                  margin: EdgeInsets.only(top: 8),
                  child: Text(
                    "SHARE",
                    style: TextStyle(
                      fontSize: 12,
                      fontWeight: FontWeight.w400,
                      color: Colors.blue,
                    ),
                  ),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}


预告


Flutter 中的控件叫 Widget,它分为有状态StatefulWidget和无状态StatelessWidgets。有状态的意思是内容会改变的控件,无状态的就是静态不可改变的。


下一篇会分别自定义一个有状态和无状态控件,在自定义的过程中更好地理解它们的区别。


参考


Flutter 官方所有控件列表


Layouts in Flutter | Flutter


推荐阅读





目录
相关文章
|
7月前
|
编解码 前端开发 开发者
【Flutter前端技术开发专栏】Flutter中的响应式设计与自适应布局
【4月更文挑战第30天】Flutter框架助力移动应用实现响应式设计与自适应布局,通过层次化布局系统和`Widget`树管理,结合`BoxConstraints`定义尺寸范围,实现自适应。利用`MediaQuery`获取设备信息,调整布局以适应不同屏幕。`FractionallySizedBox`按比例设定尺寸,`LayoutBuilder`动态计算布局。借助这些工具,开发者能创建跨屏幕尺寸、方向兼容的应用,提升用户体验。
172 0
【Flutter前端技术开发专栏】Flutter中的响应式设计与自适应布局
|
Android开发 iOS开发 容器
Flutter控件封装之轮播图Banner
Flutter中实现轮播图的方式有很多种,比如使用三方flutter_swiper,card_swiper等等,使用这些三方,可以很快很方便的实现一个轮播图展示,基本上也能满足我们日常的开发需求,如果说,想要一些定制化的操作,那么就不得不去更改源码或者自己自定义一个,自己定义的话,Flutter中提供了原生组件PageView,可以使用它很方便的来实现一个轮播图。
380 0
|
25天前
|
缓存 监控 前端开发
优化 Flutter 应用启动速度的策略,涵盖理解启动过程、资源加载优化、减少初始化工作、界面布局优化、异步初始化、预加载关键数据、性能监控与分析等方面
本文探讨了优化 Flutter 应用启动速度的策略,涵盖理解启动过程、资源加载优化、减少初始化工作、界面布局优化、异步初始化、预加载关键数据、性能监控与分析等方面,并通过案例分析展示了具体措施和效果,强调了持续优化的重要性及未来优化方向。
52 10
|
4月前
|
Android开发
Flutter控件的显示与隐藏
Flutter控件的显示与隐藏
167 3
|
24天前
|
开发框架 数据安全/隐私保护 开发者
Flutter 是一款强大的跨平台移动应用开发框架,本文深入探讨了其布局与样式设计
Flutter 是一款强大的跨平台移动应用开发框架,本文深入探讨了其布局与样式设计,涵盖布局基础、常用组件、样式设计、实战应用、响应式布局及性能优化等方面,助力开发者打造精美用户界面。
39 7
|
1月前
|
开发者 容器
Flutter&鸿蒙next 布局架构原理详解
本文详细介绍了 Flutter 中的主要布局方式,包括 Row、Column、Stack、Container、ListView 和 GridView 等布局组件的架构原理及使用场景。通过了解这些布局 Widget 的基本概念、关键属性和布局原理,开发者可以更高效地构建复杂的用户界面。此外,文章还提供了布局优化技巧,帮助提升应用性能。
99 4
|
1月前
|
容器
深入理解 Flutter 鸿蒙版的 Stack 布局:适配屏幕与层叠样式布局
Flutter 的 Stack 布局组件允许你将多个子组件层叠在一起,实现复杂的界面效果。本文介绍了 Stack 的基本用法、核心概念(如子组件层叠、Positioned 组件和对齐属性),以及如何使用 MediaQuery 和 LayoutBuilder 实现响应式设计。通过示例展示了照片展示与文字描述、动态调整层叠布局等高级用法,帮助你构建更加精美和实用的 Flutter 应用。
133 2
|
2月前
|
容器
Flutter&鸿蒙next 布局架构原理详解
Flutter&鸿蒙next 布局架构原理详解
|
2月前
|
Android开发 开发者 容器
flutter:&UI布局 (六)
本文档介绍了Flutter中的UI布局方式,包括线性布局(如Column和Row)、非线性布局(如Stack、Flex、Positioned)以及Wrap布局等。通过具体示例代码展示了如何使用这些布局组件来构建灵活多变的用户界面,例如使用Column垂直排列文本、使用Stack叠加组件、以及利用Wrap实现自动换行的按钮布局等。
|
7月前
|
开发框架 前端开发 数据安全/隐私保护
【Flutter 前端技术开发专栏】Flutter 中的布局与样式设计
【4月更文挑战第30天】本文探讨了Flutter的布局和样式设计,关键点包括:1) 布局基础如Column、Row和Stack用于创建复杂结构;2) Container、Center和Expanded等常用组件的作用;3) Theme和Decoration实现全局样式和组件装饰;4) 实战应用如登录界面和列表页面的构建;5) 响应式布局利用MediaQuery和弹性组件适应不同屏幕;6) 性能优化,避免过度复杂设计。了解并掌握这些,有助于开发者创建高效美观的Flutter应用。
203 0
【Flutter 前端技术开发专栏】Flutter 中的布局与样式设计