前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter开发-容器类组件

Flutter开发-容器类组件

作者头像
码客说
发布2020-05-14 16:57:13
3.5K0
发布2020-05-14 16:57:13
举报
文章被收录于专栏:码客码客

前言

容器类Widget和布局类Widget都作用于其子Widget,不同的是:

  • 布局类Widget一般都需要接收一个widget数组(children),他们直接或间接继承自(或包含)MultiChildRenderObjectWidget ; 而容器类Widget一般只需要接收一个子Widget(child),他们直接或间接继承自(或包含)SingleChildRenderObjectWidget。
  • 布局类Widget是按照一定的排列方式来对其子Widget进行排列; 而容器类Widget一般只是包装其子Widget,对其添加一些修饰(补白或背景色等)、变换(旋转或剪裁等)、或限制(大小等)。

Padding(填充)

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

代码语言:javascript
复制
Padding({
  ...
  EdgeInsetsGeometry padding,
  Widget child,
})

EdgeInsetsGeometry是一个抽象类,开发中,我们一般都使用EdgeInsets类,它是EdgeInsetsGeometry的一个子类,定义了一些设置填充的便捷方法。

EdgeInsets

我们看看EdgeInsets提供的便捷方法:

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

尺寸限制类容器

尺寸限制类容器用于限制容器大小,Flutter中提供了多种这样的容器,如

  • ConstrainedBox
  • SizedBox
  • UnconstrainedBox
  • AspectRatio

本节将介绍一些常用的。

ConstrainedBox

ConstrainedBox用于对子组件添加额外的约束。

例如,如果你想让子组件的最小高度是80像素,你可以使用const BoxConstraints(minHeight: 80.0)作为子组件的约束。

我们实现一个最小高度为50,宽度尽可能大的红色容器。

代码语言:javascript
复制
ConstrainedBox(
  constraints: BoxConstraints(
    minWidth: double.infinity, //宽度尽可能大
    minHeight: 50.0 //最小高度为50像素
  ),
  child: Container(
      height: 5.0, 
      child: redBox 
  ),
)

多重限制

如果某一个组件有多个父级ConstrainedBox限制,那么最终会是哪个生效?我们看一个例子:

代码语言:javascript
复制
ConstrainedBox(
    constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0), //父
    child: ConstrainedBox(
      constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
      child: redBox,
    )
)

上面我们有父子两个ConstrainedBox,他们的限制条件不同

最终显示效果是宽90,高60,也就是说是子ConstrainedBoxminWidth生效,而minHeight是父ConstrainedBox生效。

BoxConstraints

BoxConstraints用于设置限制条件,它的定义如下:

代码语言:javascript
复制
const BoxConstraints({
  this.minWidth = 0.0, //最小宽度
  this.maxWidth = double.infinity, //最大宽度
  this.minHeight = 0.0, //最小高度
  this.maxHeight = double.infinity //最大高度
})

BoxConstraints还定义了一些便捷的构造函数,用于快速生成特定限制规则的BoxConstraints,

  • BoxConstraints.tight(Size size),它可以生成给定大小的限制;
  • const BoxConstraints.expand()可以生成一个尽可能大的用以填充另一个容器的BoxConstraints。

SizedBox

SizedBox用于给子元素指定固定的宽高,如:

代码语言:javascript
复制
SizedBox(
  width: 80.0,
  height: 80.0,
  child: redBox
)

实际上SizedBox只是ConstrainedBox的一个定制,上面代码等价于:

代码语言:javascript
复制
ConstrainedBox(
  constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),
  child: redBox, 
)

BoxConstraints.tightFor(width: 80.0,height: 80.0)等价于:

代码语言:javascript
复制
BoxConstraints(minHeight: 80.0,maxHeight: 80.0,minWidth: 80.0,maxWidth: 80.0)

UnconstrainedBox

UnconstrainedBox不会对子组件产生任何限制,它允许其子组件按照其本身大小绘制。一般情况下,我们会很少直接使用此组件,但在”去除”多重限制的时候也许会有帮助,我们看下下面的代码:

代码语言:javascript
复制
ConstrainedBox(
    constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0),  //父
    child: UnconstrainedBox( //“去除”父级限制
      child: ConstrainedBox(
        constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
        child: redBox,
      ),
    )
)

上面代码中,如果没有中间的UnconstrainedBox,那么根据上面所述的多重限制规则,那么最终将显示一个90×100的红色框。但是由于UnconstrainedBox “去除”了父ConstrainedBox的限制,则最终会按照子ConstrainedBox的限制来绘制redBox,即90×20

但是,读者请注意,UnconstrainedBox对父组件限制的“去除”并非是真正的去除:上面例子中虽然红色区域大小是90×20,但上方仍然有80的空白空间。也就是说父限制的minHeight(100.0)仍然是生效的,只不过它不影响最终子元素redBox的大小,但仍然还是占有相应的空间,可以认为此时的父ConstrainedBox是作用于子UnconstrainedBox上,而redBox只受子ConstrainedBox限制,这一点请读者务必注意。

DecoratedBox(装饰容器)

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

代码语言:javascript
复制
const DecoratedBox({
  Decoration decoration,
  DecorationPosition position = DecorationPosition.background,
  Widget child
})
  • decoration:代表将要绘制的装饰,它的类型为DecorationDecoration是一个抽象类,它定义了一个接口 createBoxPainter(),子类的主要职责是需要通过实现它来创建一个画笔,该画笔用于绘制装饰。
  • position :此属性决定在哪里绘制Decoration,它接收DecorationPosition的枚举类型,该枚举类有两个值:
    • background:在子组件之后绘制,即背景装饰。
    • foreground:在子组件之上绘制,即前景。

BoxDecoration

我们通常会直接使用BoxDecoration类,它是一个Decoration的子类,实现了常用的装饰元素的绘制。

代码语言:javascript
复制
BoxDecoration({
  Color color, //颜色
  DecorationImage image,//图片
  BoxBorder border, //边框
  BorderRadiusGeometry borderRadius, //圆角
  List<BoxShadow> boxShadow, //阴影,可以指定多个
  Gradient gradient, //渐变
  BlendMode backgroundBlendMode, //背景混合模式
  BoxShape shape = BoxShape.rectangle, //形状
})

各个属性名都是自解释的,详情读者可以查看API文档。下面我们实现一个带阴影的背景色渐变的按钮:

代码语言:javascript
复制
 DecoratedBox(
    decoration: BoxDecoration(
      gradient: LinearGradient(colors:[Colors.red,Colors.orange[700]]), //背景渐变
      borderRadius: BorderRadius.circular(3.0), //3像素圆角
      boxShadow: [ //阴影
        BoxShadow(
            color:Colors.black54,
            offset: Offset(2.0,2.0),
            blurRadius: 4.0
        )
      ]
    ),
  child: Padding(padding: EdgeInsets.symmetric(horizontal: 80.0, vertical: 18.0),
    child: Text("Login", style: TextStyle(color: Colors.white),),
  )
)

Transform(变换)

Transform可以在其子组件绘制时对其应用一些矩阵变换来实现一些特效。Matrix4是一个4D矩阵,通过它我们可以实现各种矩阵操作,下面是一个例子:

代码语言:javascript
复制
Container(
  color: Colors.black,
  child: new Transform(
    alignment: Alignment.topRight, //相对于坐标系原点的对齐方式
    transform: new Matrix4.skewY(0.3), //沿Y轴倾斜0.3弧度
    child: new Container(
      padding: const EdgeInsets.all(8.0),
      color: Colors.deepOrange,
      child: const Text('Apartment for rent!'),
    ),
  ),
);

平移

Transform.translate接收一个offset参数,可以在绘制时沿xy轴对子组件平移指定的距离。

代码语言:javascript
复制
DecoratedBox(
  decoration:BoxDecoration(color: Colors.red),
  //默认原点为左上角,左移20像素,向上平移5像素  
  child: Transform.translate(
    offset: Offset(-20.0, -5.0),
    child: Text("Hello world"),
  ),
)

旋转

Transform.rotate可以对子组件进行旋转变换,如:

代码语言:javascript
复制
DecoratedBox(
  decoration:BoxDecoration(color: Colors.red),
  child: Transform.rotate(
    //旋转90度
    angle:math.pi/2 ,
    child: Text("Hello world"),
  ),
);

注意:要使用math.pi需先进行如下导包。 > import 'dart:math' as math; >

Container(全能)

这是 Container 的三个主要表现:

  • 当没有子 widgets 且没有指定 constraints 时,尽可能地充满可用空间
  • 如果有 constraints 就以 constraints 为准(除非跟 parent constraints 冲突)
  • 如果有子 widgets 则以 children 的 size 为准;可以设置 width, height,constraints 来约束 size

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

代码语言:javascript
复制
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,
})

注意点

  • 容器的大小可以通过widthheight属性来指定,也可以通过constraints来指定; 如果它们同时存在时,widthheight优先。 实际上Container内部会根据widthheight来生成一个constraints
  • colordecoration是互斥的,如果同时设置它们则会报错! 实际上,当指定color时,Container内会自动创建一个decoration

decoration 常用设置

背景圆角

代码语言:javascript
复制
BoxDecoration(
    color: ZJColors.write,
    borderRadius: BorderRadius.all(Radius.circular(6)
)

Clip(剪裁)

Flutter中提供了一些剪裁函数,用于对组件进行剪裁。

剪裁Widget

作用

ClipOval

子组件为正方形时剪裁为内贴圆形,为矩形时,剪裁为内贴椭圆

ClipRRect

将子组件剪裁为圆角矩形

ClipRect

剪裁子组件到实际占用的矩形大小(溢出部分剪裁)

示例

代码语言:javascript
复制
ClipOval(child: avatar),//剪裁为圆形
ClipRRect( //剪裁为圆角矩形
    borderRadius: BorderRadius.circular(5.0),
    child: avatar,
), 
ClipRect(//将溢出部分剪裁
    child: Align(
        alignment: Alignment.topLeft,
        widthFactor: .5,//宽度设为原来宽度一半
        child: avatar,
    ),
),

Scaffold、TabBar、底部导航

一个完整的路由页可能会包含导航栏、抽屉菜单(Drawer)以及底部Tab导航菜单等。如果每个路由页面都需要开发者自己手动去实现这些,这会是一件非常麻烦且无聊的事。幸运的是,Flutter Material组件库提供了一些现成的组件来减少我们的开发任务。Scaffold是一个路由页的骨架,我们使用它可以很容易地拼装出一个完整的页面。

我们实现一个页面,它包含:

  1. 一个导航栏
  2. 导航栏右边有一个分享按钮
  3. 有一个抽屉菜单
  4. 有一个底部导航
  5. 右下角有一个悬浮的动作按钮

代码如下:

代码语言:javascript
复制
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: new 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(){
  }
}

上面代码中我们用到了如下组件:

组件名称

解释

AppBar

一个导航栏骨架

MyDrawer

抽屉菜单

BottomNavigationBar

底部导航栏

FloatingActionButton

漂浮按钮

AppBar

AppBar是一个Material风格的导航栏,通过它可以设置导航栏标题、导航栏菜单、导航栏底部的Tab标题等。下面我们看看AppBar的定义:

代码语言:javascript
复制
AppBar({
  Key key,
  this.leading, //导航栏最左侧Widget,常见为抽屉菜单按钮或返回按钮。
  this.automaticallyImplyLeading = true, //如果leading为null,是否自动实现默认的leading按钮
  this.title,// 页面标题
  this.actions, // 导航栏右侧菜单
  this.bottom, // 导航栏底部菜单,通常为Tab按钮组
  this.elevation = 4.0, // 导航栏阴影
  this.centerTitle, //标题是否居中 
  this.backgroundColor,
  ...   //其它属性见源码注释
})

如果给Scaffold添加了抽屉菜单,默认情况下Scaffold会自动将AppBarleading设置为菜单按钮(如上面截图所示),点击它便可打开抽屉菜单。如果我们想自定义菜单图标,可以手动来设置leading,如:

代码语言:javascript
复制
Scaffold(
  appBar: AppBar(
    title: Text("App Name"),
    leading: Builder(builder: (context) {
      return IconButton(
        icon: Icon(Icons.dashboard, color: Colors.white), //自定义图标
        onPressed: () {
          // 打开抽屉菜单  
          Scaffold.of(context).openDrawer(); 
        },
      );
    }),
    ...  
  )

TabBar

下面我们通过“bottom”属性来添加一个导航栏底部Tab按钮组

Material组件库中提供了一个TabBar组件,它可以快速生成Tab菜单,下面是上图对应的源码:

代码语言:javascript
复制
class _ScaffoldRouteState extends State<ScaffoldRoute>
    with SingleTickerProviderStateMixin {

  TabController _tabController; //需要定义一个Controller
  List tabs = ["新闻", "历史", "图片"];

  @override
  void initState() {
    super.initState();
    // 创建Controller  
    _tabController = TabController(length: tabs.length, vsync: this);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        ... //省略无关代码
        bottom: TabBar(   //生成Tab菜单
          controller: _tabController,
          tabs: tabs.map((e) => Tab(text: e)).toList()
        ),
      ),
      ... //省略无关代码
  }

Tab组件有三个可选参数,除了可以指定文字外,还可以指定Tab菜单图标,或者直接自定义组件样式。Tab组件定义如下:

代码语言:javascript
复制
Tab({
  Key key,
  this.text, // 菜单文本
  this.icon, // 菜单图标
  this.child, // 自定义组件样式
})

Drawer(抽屉菜单)

ScaffolddrawerendDrawer属性可以分别接受一个Widget来作为页面的左、右抽屉菜单。如果开发者提供了抽屉菜单,那么当用户手指从屏幕左(或右)侧向里滑动时便可打开抽屉菜单。本节开始部分的示例中实现了一个左抽屉菜单MyDrawer,它的源码如下:

代码语言:javascript
复制
class MyDrawer extends StatelessWidget {
  const MyDrawer({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: MediaQuery.removePadding(
        context: context,
        //移除抽屉菜单顶部默认留白
        removeTop: true,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
          ],
        ),
      ),
    );
  }
}

抽屉菜单通常将Drawer组件作为根节点,它实现了Material风格的菜单面板,MediaQuery.removePadding可以移除Drawer默认的一些留白(比如Drawer默认顶部会留和手机状态栏等高的留白),读者可以尝试传递不同的参数来看看实际效果。抽屉菜单页由顶部和底部组成,顶部由用户头像和昵称组成,底部是一个菜单列表,用ListView实现

FloatingActionButton

FloatingActionButton是Material设计规范中的一种特殊Button,通常悬浮在页面的某一个位置作为某种常用动作的快捷入口

SafeArea

使用 SafeArea 可以让 child widget 在顶部和底部腾出足够的空间方便处理 iPhoneX 这类的手机。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-05-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Padding(填充)
    • EdgeInsets
    • 尺寸限制类容器
      • ConstrainedBox
        • 多重限制
        • BoxConstraints
      • SizedBox
        • UnconstrainedBox
        • DecoratedBox(装饰容器)
          • BoxDecoration
          • Transform(变换)
            • 平移
              • 旋转
              • Container(全能)
              • Clip(剪裁)
              • Scaffold、TabBar、底部导航
                • AppBar
                  • TabBar
                    • Drawer(抽屉菜单)
                      • FloatingActionButton
                      • SafeArea
                      相关产品与服务
                      容器服务
                      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档