【Flutter之旅】容器类组件介绍

容器类组件介绍

环境介绍以及参考文献

本示例是在 Linux 16.04.1-Ubuntu 搭配 VS Code 使用。

《Flutter实战》电子书
Flutter中文网

填充

Padding 可以给其子节点添加填充(留白),和边距效果类似。

一般用 EdgeInsets 类去实现 padding,这个类提供了下面的几个方法:

  • 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。
class PaddingTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("填充介绍"),),
      body: Padding(
        // 上下左右各添加16像素补白
        padding: EdgeInsets.all(16.0),
        child: Column(
          // 显式指定对齐方式为左对齐,排除对齐干扰
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Padding(
              // 左边添加8像素补白
              padding: const EdgeInsets.only(left: 8.0),
              child: Text("Hello world"),
            ),
            Padding(
              // 上下各添加8像素补白
              padding: const EdgeInsets.symmetric(vertical: 8.0),
              child: Text("I am Jack"),
            ),
            Padding(
              // 分别指定四个方向的补白
              padding: const EdgeInsets.fromLTRB(20.0,.0,20.0,20.0),
              child: Text("Your friend"),
            )
          ],
        ),
      ),
    );
  }
}

尺寸限制类容器

尺寸限制类容器用于限制容器大小

ConstrainedBox

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

const BoxConstraints({
  this.minWidth = 0.0, //最小宽度
  this.maxWidth = double.infinity, //最大宽度
  this.minHeight = 0.0, //最小高度
  this.maxHeight = double.infinity //最大高度
})

SizedBox

用于给子元素指定固定的宽高。SizedBox 实际上是 ConstrainedBox 的一个子类。

SizedBox(
  width: 80.0,
  height: 80.0,
  child: redBox
)
// 等价于
ConstrainedBox(
  constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),
  child: redBox, 
)

当有多重限制时,对于 minWidth 和 minHeight 来说,是取父子中相应数值较大的。

实际上,多重限制重点在于保证父限制与子限制不冲突。

UnconstrainedBox

不会对子组件产生任何限制,它允许其子组件按照其本身大小绘制。

装饰容器

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

通常使用 BoxDecoration 实现常用的装饰元素的绘制

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

变换

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

  • Transform.translate 接收一个 offset 参数,可以在绘制时沿 x、y 轴对子组件平移指定的距离。
  • Transform.rotate 可以对子组件进行旋转变换。
  • Transform.scale 可以对子组件进行缩小或放大。

Transform 是在其子组件绘制时的变化,所以占用的空间是不会变化的。如果想要修改相应的空间,需要用到 RotatedBox 进行旋转变换。

Container

Container 是一个组合类容器。

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,
})

Scaffold、TabBar、底部导航

Scaffold 是一个路由页的骨架,我们使用它可以很容易地拼装出一个完整的页面。

利用 Scaffold 实现顶部导航,底部导航以及抽屉式菜单。

【Flutter之旅】容器类组件介绍
【Flutter之旅】容器类组件介绍

实例:

class ScaffoldRoute extends StatefulWidget {
  @override
  _ScaffoldRouteState createState() => _ScaffoldRouteState();
}

class _ScaffoldRouteState extends State<ScaffoldRoute> 
  with SingleTickerProviderStateMixin{

  int _selectedIndex = 1;
  final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
  TabController? _tabController = null; //需要定义一个 Controller
  List tabs = ["新闻", "历史", "图片"];
  List buttomTabs = ["Home", "Business", "School"];

  @override
  void initState() {
    super.initState();
    print("initState");
    _tabController = TabController(length: tabs.length, vsync: this)
      ..addListener(() {
        double d1 = _tabController!.index.toDouble();
        double d2 = _tabController!.animation!.value;
        if(d1 != Null && d2 != Null && d1 == d2){
          int idx = _tabController!.index;
          if(idx != Null) {
            print(tabs[idx]);
          }
        }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar( //导航栏
        title: Text("App Name"), 
        actions: <Widget>[ //导航栏右侧菜单
          IconButton(icon: Icon(Icons.share), onPressed: () {}),
        ],
        leading: Builder(builder: (context) {
          return IconButton(
            icon: Icon(Icons.dashboard, color: Colors.white), //自定义图标
            onPressed: () {
              // 打开抽屉菜单  
              Scaffold.of(context).openDrawer(); 
            },
          );
        }),
        bottom: TabBar(   //生成 Tab 菜单
          controller: _tabController,
          tabs: tabs.map((e) => Tab(text: e)).toList()
        ),
      ),
      drawer: new MyDrawer(), //抽屉
      body: TabBarView( // 搭配 TabBar 实现同步切换和滑动状态同步
        controller: _tabController,
        children: tabs.map((e){
          return Container(
            alignment: Alignment.center,
            child: Text(e, textScaleFactor: 5,),
          );
        }).toList(),
      ),
      // bottomNavigationBar: BottomNavigationBar( //底部导航
      //   items: <BottomNavigationBarItem>[
      //     BottomNavigationBarItem(icon: Icon(Icons.home), label: buttomTabs[0]),
      //     BottomNavigationBarItem(icon: Icon(Icons.business), label: buttomTabs[1]),
      //     BottomNavigationBarItem(icon: Icon(Icons.school), label: buttomTabs[2]),
      //   ],
      //   currentIndex: _selectedIndex,
      //   fixedColor: Colors.blue,
      //   onTap: _onItemTapped,
      // ),
      bottomNavigationBar: BottomAppBar(
        color: Colors.white,
        shape: CircularNotchedRectangle(), //底部导航栏打一个圆形的洞
        child: Row(
          children: [
            IconButton(icon: Icon(Icons.home), onPressed: () {_onItemTapped(0); },),
            SizedBox(), //中间位置空出
            IconButton(icon: Icon(Icons.business), onPressed: () {_onItemTapped(1); },),
          ],
          mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部导航栏横向空间
        ),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,

      floatingActionButton: FloatingActionButton( //悬浮按钮
          child: Icon(Icons.add),
          onPressed:_onAdd
      ),
    );
  }
  void _onItemTapped(int index) {
    print("tapped: " + buttomTabs[index]);
    setState(() {
      _selectedIndex = index;
    });
  }
  void _onAdd() {
    _scaffoldKey.currentState!.openDrawer(); //打开 drawer
  }
}

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>[
            Padding(
              padding: const EdgeInsets.only(top: 38.0),
              child: Row(
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16.0),
                    child: ClipOval(
                      child: Image.asset(
                        "image/20411648.png",
                        width: 80,
                      ),
                    ),
                  ),
                  Text(
                    "Test Image",
                    style: TextStyle(fontWeight: FontWeight.bold),
                  )
                ],
              ),
            ),
            Expanded(
              child: ListView(
                children: <Widget>[
                  ListTile(
                    leading: const Icon(Icons.add),
                    title: const Text('Add account'),
                    onTap: () {
                      Navigator.pop(context); //关闭 drawer
                    },
                  ),
                  ListTile(
                    leading: const Icon(Icons.settings),
                    title: const Text('Manage accounts'),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

裁剪

对组件进行裁剪。

剪裁 Widget 右对齐
ClipOval 子组件为正方形时剪裁为内贴圆形,为矩形时,剪裁为内贴椭圆
ClipRRect 将子组件剪裁为圆角矩形
ClipRect 剪裁子组件到实际占用的矩形大小(溢出部分剪裁)
// 剪裁为圆形
ClipOval(child: avatar);
// 剪裁为圆角矩形
ClipRRect(
  borderRadius: BorderRadius.circular(5.0),
  child: avatar,
);
// 将溢出部分剪裁
ClipRect(
  child: Align(
    alignment: Alignment.topLeft,
    widthFactor: .5,//宽度设为原来宽度一半
    child: avatar,
  ),
);
上一篇:Vue电商项目练习-3 主页设计


下一篇:导航栏-1