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

Flutter容器类组件

原创
作者头像
白白白小艾
发布2022-03-24 17:09:55
3.8K0
发布2022-03-24 17:09:55
举报
文章被收录于专栏:Flutter学习专栏Flutter学习专栏

Flutter容器类组件

容器类Widget与布局类Widget都用作用户界面设计,两者的不同在于:

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

⚠️注意, Flutter官方并没有对Widget进行如此划分。中文版《Flutter实战》对其分类主要是方便讨论和对Widget功能区分记忆。

1.填充(Paddinig)
1.1 Padding介绍

Padding组件在Android、IOS端只是一个属性,但在Flutter中Padding是一个独立的Widget。

Padding通常用于设置子Widget到父Widget的边距,这部分边距可以称为父组件的内边距,或者子Widget的外边距。

⚠️注意:在Flutter中不存在名为Margin的Widget,因为内外边距也可以通过Padding来完成。

源码分析:

代码语言:txt
复制
const Padding({
  Key key,
  @required this.padding, // EdgeInsetsGeometry类型(抽象类),使用EdgeInsets
  Widget child,
})

其中的属性this.padding要求传入抽象类EdgeInsetsGeometry,常用其子类EdgeInsets。

1.2 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。
1.3 Padding演练
代码语言:java
复制
class PaddingDemo extends StatelessWidget {
  const PaddingDemo({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
      	 // Padding Widget
        Padding(
	      padding: EdgeInsets.all(8),
	      child: Text("莫听穿林打叶声,何妨吟啸且徐行,竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生!"),
	     ),
	     
	     // 普通Widget
        Text("莫听穿林打叶声,何妨吟啸且徐行,竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生!"),
      ],
    );
  }
}

效果图如下:

<center>

undefined

</center>

2.装饰容器(DecoratedBox)
2.1 DecoratedBox介绍

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

DecoratedBox的定义如下:

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

其中的属性this.decoration要求传入抽象类Decoration,常用其子类BoxDecoration。

2.2 BoxDecoration类介绍

BoxDecoration的定义如下:

代码语言:txt
复制
const BoxDecoration({
  Color color, //颜色
  DecorationImage image,//图片
  BoxBorder border, //边框
  BorderRadiusGeometry borderRadius, //圆角
  List<BoxShadow> boxShadow, //阴影,可以指定多个
  Gradient gradient, //渐变
  BlendMode backgroundBlendMode, //背景混合模式
  BoxShape shape = BoxShape.rectangle, //形状
})
2.3 DecoratedBox演练
代码语言:txt
复制
class DecoratedBoxDemo extends StatelessWidget {
  const DecoratedBoxDemo({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: DecoratedBox(
        decoration: BoxDecoration(
          // 线性颜色渐变
          gradient: LinearGradient(
            colors: [Colors.blue, Colors.lightBlueAccent],
          ),
          
          // 圆角
          borderRadius: BorderRadius.circular(3.0),
          
          // 阴影
          boxShadow: [
            BoxShadow(
              color: Colors.black54,
              offset: Offset(2.0, 2.0),
              blurRadius: 4.0
            )
          ]
        ),
        
        // DecoratedBox的子Widget
        child: Padding(
          padding: EdgeInsets.symmetric(horizontal: 80, vertical: 18),
          child: Text(
            "装饰测试",
            style: TextStyle(color: Colors.white),
          ),
        ),
      ),
    );
  }
}

效果图如下:

<center>

undefined

</center>

3.变换(Transform)

Transform可以在其子组件绘制时对其应用一些矩阵变换来实现一些特效。

3.1 Transform - 倾斜
代码语言:txt
复制
class HomeBody extends StatelessWidget {
  const HomeBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: DecoratedBox(
        decoration: BoxDecoration(color: Colors.red),
        child: Transform(
          alignment: Alignment.topRight,
          transform: Matrix4.skewY(0.3),
          child: Text("Transform倾斜示例"),
        ),
      ),
    );
  }
}

效果图如下:

<center>

undefined

</center>

3.2 Transform - 平移

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

代码语言:txt
复制
class HomeBody extends StatelessWidget {
  const HomeBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: DecoratedBox(
        decoration:BoxDecoration(color: Colors.red),

        //默认原点为左上角,左移20像素,向上平移5像素
        child: Transform.translate(
          offset: Offset(-20.0, -5.0),
          child: Text("Transform平移"),
        ),
      ),
    );
  }
}

效果图如下:

<center>

undefined

</center>

3.3 Transform - 旋转

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

代码语言:txt
复制
class HomeBody extends StatelessWidget {
  const HomeBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: DecoratedBox(
        decoration: BoxDecoration(color: Colors.red),
        child: Transform.rotate(
          angle: math.pi, // 旋转180度
          child: Text("Transform旋转"),
        )
      ),
    );
  }
}

效果图如下:

<center>

undefined

</center>

3.4 Transform - 缩放

Transform.scale可以对子组件进行缩小或放大,如:

代码语言:txt
复制
class HomeBody extends StatelessWidget {
  const HomeBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: DecoratedBox(
          decoration: BoxDecoration(color: Colors.red),
          child: Transform.scale(
            scale: 1.5,
            child: Text("Transform缩放"),
          )
      ),
    );;
  }
}

效果图如下:

<center>

undefined

</center>

3.5 Transform注意事项
  • Transform的变换是应用在绘制阶段,而并不是应用在布局(layout)阶段,所以无论对子组件应用何种变化,其占用空间的大小和在屏幕上的位置都是固定不变的,因为这些是在布局阶段就确定的。
代码语言:txt
复制
class HomeBody extends StatelessWidget {
  const HomeBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          DecoratedBox(
            decoration: BoxDecoration(color: Colors.red),
            child: Transform.scale(scale: 1.5, child: Text("绘制阶段")),
          ),
          Text("紧靠重叠", style: TextStyle(color: Colors.green))
        ],
      ),
    );
  }
}

效果图如下:

<center>

undefined

</center>

由于第一个Text应用变换(放大)后,其在绘制时会放大,但其占用的空间依然为红色部分,所以第二个Text会紧挨着红色部分,最终就会出现文字重合。

  • 由于矩阵变化只会作用在绘制阶段,所以在某些场景下,在UI需要变化时,可以直接通过矩阵变化来达到视觉上的UI改变,而不需要去重新触发build流程,这样会节省layout的开销,所以性能会比较好。如之前介绍的Flow组件,它内部就是用矩阵变换来更新UI,除此之外,Flutter的动画组件中也大量使用了Transform以提高性能。
3.6 RotatedBox

RotatedBoxTransform.rotate功能相似,它们都可以对子组件进行旋转变换,但是有一点不同:RotatedBox的变换是在layout阶段,会影响在子组件的位置和大小。我们将上面介绍Transform.rotate时的示例改一下:

代码语言:txt
复制
class HomeBody extends StatelessWidget {
  const HomeBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 第一个组件使用 RotatedBox进行旋转
            DecoratedBox(
              decoration: BoxDecoration(color: Colors.red),
              child: RotatedBox(
                quarterTurns: 2,
                child: Text("RotatedBox示例"),
              ),
            ),
            
            // 第二个组件紧邻左组件
            Text("紧靠", style: TextStyle(color: Colors.green))
          ]
      )
    );
  }
}

效果图如下:

<center>

undefined

</center>

由于RotatedBox是作用于layout阶段,所以子组件会旋转180度(而不只是绘制的内容),decoration会作用到子组件所占用的实际空间上,所以最终就是上图的效果。该示例可以和前面Transform.rotate示例对比理解。

4.Container(组合类容器)
4.1 Container介绍

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

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

大多数属性在介绍其它容器时都已经介绍过了,不再赘述,但有两点需要说明:

  • 容器的大小可以通过width、height属性来指定,也可以通过constraints来指定,如果同时存在时,width、height优先。实际上Container内部会根据width、height来生成一个constraints。
  • color和decoration是互斥的,实际上,当指定color时,Container内会自动创建一个decoration。
4.2 Container演练
代码语言:txt
复制
class HomeBody extends StatelessWidget {
  const HomeBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.blueAccent, Colors.lightBlueAccent],
          ),
          boxShadow: [BoxShadow(
            color: Colors.black54,
            offset: Offset(2.0, 2.0),
            blurRadius: 4.0
          )]
        ),
        transform: Matrix4.rotationZ(0.4),
        width: 100,
        height: 100,
        child: Icon(Icons.pets, size: 32, color: Colors.white,),
      ),
    );
  }
}

效果图如下:

<center>

undefined

</center>

4.3 Padding和Margin介绍

padding与margin都是常用的设置空间的属性,下面用代码演示区分二者的区别。

代码语言:txt
复制
class HomeBody extends StatelessWidget {
  const HomeBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Container(
            margin: EdgeInsets.all(20),
            color: Colors.blue,
            child: Text("Margin and Padding"),
          ),
          Container(
            padding: EdgeInsets.all(20),
            color: Colors.blue,
            child: Text("Margin and Padding"),
          )
        ],
      ),
    );
  }
}

效果图如下:

<center>

undefined

</center>

直观的感觉就是margin的留白是在容器外部,而padding的留白是在容器内部,读者需要记住这个差异。事实上,Container内margin和padding都是通过Padding 组件来实现的,上面的示例代码实际上等价于:

代码语言:txt
复制
class HomeBody extends StatelessWidget {
  const HomeBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Padding(padding: EdgeInsets.all(20),
            child: DecoratedBox(
              decoration: BoxDecoration(color: Colors.blue),
              child: Text("Margin and Padding"),
            ),
          ),
          DecoratedBox(
            decoration: BoxDecoration(color: Colors.blue),
            child: Padding(
              padding: EdgeInsets.all(20),
              child: Text("Margin and Padding"),
            ),
          )
        ],
      ),
    );
  }
}
5. Scaffold(脚手架)
5.1 Scaffold介绍

一个完整的路由页可能会包含导航栏、抽屉菜单(Drawer)以及底部 Tab 导航菜单等。Flutter Material 组件库提供了一些现成的组件来减少我们的开发任务。Scaffold,中文称之为脚手架,为开发者提供了路由页面的整体架构,开发者可以借助它快速便携地实现一个完整的页面。

下面构造一个完整的路由页面对其进行讲解:

  1. 导航栏
  2. 导航栏右侧分享按钮
  3. 抽屉菜单
  4. 底部导航栏
  5. 悬浮按钮
代码语言:txt
复制
class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  int _selectedIndex = 1;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Scaffold示例'),
        actions: [
          IconButton(
            onPressed: () {
              print("分享按钮被点击");
            },
            icon: Icon(Icons.share)
          )
        ],
      ),

      drawer: null, // 后面会对drawer进行讲解

      bottomNavigationBar: BottomNavigationBar(
        backgroundColor: Colors.white,
        unselectedItemColor: Colors.grey,
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页", activeIcon: Icon(Icons.home, color: Colors.deepOrange)),//, activeIcon: Icon(Icons.home, color: Colors.deepOrange,)),
          BottomNavigationBarItem(icon: Icon(Icons.search), label: "分类", activeIcon: Icon(Icons.search, color: Colors.deepOrange)),
          BottomNavigationBarItem(icon: Icon(Icons.shopping_cart), label: "购物车", activeIcon: Icon(Icons.shopping_cart, color: Colors.deepOrange)),
          BottomNavigationBarItem(icon: Icon(Icons.perm_identity), label: "我", activeIcon: Icon(Icons.perm_identity, color: Colors.deepOrange)),
        ],
        currentIndex: _selectedIndex,
        fixedColor: Colors.deepOrange,
        onTap: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
      ),
      body: Text("Scaffold示例"),

      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          print("浮窗按钮被点击");
        },
      ),
    );
  }
}

效果图如下:

<center>

undefined

</center>

在上面这段代码中,用到了以下组件:

<center>

组件名称

解释

appBar

导航栏

drawer

抽屉菜单

bottomNavigationBar

底部导航栏

floatingActionButton

浮动按钮

</center>

5.2 AppBar

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

代码语言:txt
复制
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首页导航栏左侧增加菜单按钮,正如上面所示。

若想自定义菜单图标,则可以手动设置AppBar的Leading属性。如:

代码语言:txt
复制
appBar: AppBar(
        title: Text('Scaffold示例'),
        leading: Builder(builder: (context) {
          return IconButton(
            onPressed: (){
              // 调用父类打开抽屉
              Scaffold.of(context).openDrawer();
            },
            icon: Icon(Icons.dashboard, color:Colors.orange));
        }),
        actions: [
          IconButton(
            onPressed: () {
              print("分享按钮被点击");
            },
            icon: Icon(Icons.share)
          )
        ],
      ),

效果图如下:

<center>

undefined

</center>

代码中打开抽屉菜单的方法在ScaffoldState中,通过Scaffold.of(context)可以获取父级最近的Scaffold 组件的State对象。

5.3 Drawer介绍

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

代码语言:txt
复制
drawer: Drawer(
 child: Column(
   children: [
     Padding(padding: EdgeInsets.only(left: 20, top: 100),
       child: Row(
         children: [
           Container(
             height:100,
             width: 100,
             child: Image.asset("assets/images/room.jpg"),
           ),
           SizedBox(width: 10),
           Text("leiyangyuan", style: TextStyle(fontWeight: FontWeight.bold))
         ],
       ),
     ),
   ],
 ),
),

效果图如下:

<center>

undefined

</center>

5.4 FloatingActionButton介绍

FloatingActionButton是Material设计规范中的一种特殊Button,通常悬浮在页面的某一个位置作为某种常用动作的快捷入口,如首页示例中页面右下角的"➕"号按钮。我们可以通过Scaffold的floatingActionButton属性来设置一个FloatingActionButton,同时通过floatingActionButtonLocation属性来指定其在页面中悬浮的位置。

5.5 BottomNavigationBar介绍

我们可以通过Scaffold的bottomNavigationBar属性来设置底部导航,如本节开始示例所示,我们通过Material组件库提供的BottomNavigationBar和BottomNavigationBarItem两种组件来实现Material风格的底部导航栏。可以看到首页示例的代码实现非常简单。

下面这部分代码可以绘出一个美观的底部导航栏:

代码语言:txt
复制
  bottomNavigationBar: BottomAppBar(
    color: Colors.white,
    shape: CircularNotchedRectangle(), // 导航栏底部打圆形孔
    child: Row(
      children: [
        IconButton(onPressed: (){

        },icon: Icon(Icons.home)),
        SizedBox(),
        IconButton(onPressed: () {

        }, icon: Icon(Icons.business))
      ],
      mainAxisAlignment: MainAxisAlignment.spaceAround,
    ),
  ),

  floatingActionButton: FloatingActionButton(
    child: Icon(Icons.add),
    onPressed: () {
      print("浮窗按钮被点击");
    },
  ),
  floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,

上面的代码通过将底部导航栏挖出一个圆形孔,然后将浮动按钮至于底部导航栏中间,以达到下面的效果:

<center>

undefined

</center>

5.6 页面body

最后就是页面的 Body 部分了,Scaffold 有一个 body 属性,接收一个 Widget,我们可以传任意的 Widget ,在后面介绍滑动组件时,会涉及到 TabBarView,它是一个可以进行页面切换的组件,在多 Tab 的 App 中,一般都会将 TabBarView 作为 Scaffold 的 Body。

undefined

undefined

undefined

undefined

undefined

undefined

undefined

undefined

undefined

undefined

undefined

undefined

undefined

undefined

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Flutter容器类组件
    • 1.填充(Paddinig)
      • 1.1 Padding介绍
      • 1.2 EdgeInsets类
      • 1.3 Padding演练
    • 2.装饰容器(DecoratedBox)
      • 2.1 DecoratedBox介绍
      • 2.2 BoxDecoration类介绍
      • 2.3 DecoratedBox演练
    • 3.变换(Transform)
      • 3.1 Transform - 倾斜
      • 3.2 Transform - 平移
      • 3.3 Transform - 旋转
      • 3.4 Transform - 缩放
      • 3.5 Transform注意事项
      • 3.6 RotatedBox
    • 4.Container(组合类容器)
      • 4.1 Container介绍
      • 4.2 Container演练
      • 4.3 Padding和Margin介绍
    • 5. Scaffold(脚手架)
      • 5.1 Scaffold介绍
      • 5.2 AppBar
      • 5.3 Drawer介绍
      • 5.4 FloatingActionButton介绍
      • 5.5 BottomNavigationBar介绍
      • 5.6 页面body
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档