跳至主要內容

Flutter基本组件

xiaoye大约 29 分钟Flutter基础组件FlutterFlutter基础基础组件

官网地址:选择你的开发平台,开始使用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutteropen in new window

文档地址:第二版序 | 《Flutter实战·第二版》 (flutterchina.club)open in new window

Flutter

1、使用MaterialApp 和 Scaffold两个组件装饰App

1.1、MaterialAppopen in new window

MaterialApp是一个方便的Widget,它封装了应用程序实现Material Design所需要的一些Widget。一

般作为顶层widget使用。

常用的属性:

home(主页)

title(标题) --任务管理器上面看到的名字

color(颜色)

theme(主题)open in new window

routes(路由)

...

1.2、Scaffoldopen in new window

Scaffold是Material Design布局结构的基本实现。此类提供了用于显示drawer、snackbar和底部sheet

的API。

Scaffold 有下面几个主要属性:

appBar - 显示在界面顶部的一个 AppBar。

body - 当前界面所显示的主要内容 Widget。

drawer - 抽屉菜单控件。

1.3、示例

import 'package:flutter/material.dart';

void main() {
  return runApp(MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('首页'),
      ),
      body: const Center(
        child: Text(
          'this is home',
          textDirection: TextDirection.ltr,
          style:
              TextStyle(color: Color.fromRGBO(255, 145, 123, 1), fontSize: 40),
        ),
      ),
    ),
  ));
}

2、Flutter把内容单独抽离成一个组件

在Flutter中自定义组件其实就是一个类,这个类需要继承StatelessWidget或StatefulWidget

前期我们都继承StatelessWidget。后期给大家讲StatefulWidget的使用。

StatelessWidget 是无状态组件,状态不可变的widget

StatefulWidget 是有状态组件,持有的状态可能在widget生命周期改变

2.1、新建一个dart文件

VsCode安装Awesome Flutter Snippets插件,输入stless会有快捷生成提示提示

import 'package:flutter/material.dart';//importM快捷引入

//继承无状态组件StatelessWidget   按照提示生成
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
      //需要返回构建的内容
    return const Center(
      child: Text(
        '我是一个自定义组件',
        textDirection: TextDirection.ltr,
        style: TextStyle(color: Color.fromRGBO(255, 145, 123, 1), fontSize: 40),
      ),
    );
  }
}

2.2、在main.dart中使用

import 'package:flutter/material.dart';
import 'pages/home//home_page.dart';//引入刚才那个组件

void main() {
  return runApp(MaterialApp(
      home: Scaffold(
    appBar: AppBar(
      title: const Text('首页'),
    ),
    body: MyApp(),//使用组件
  )));
}

2、基础组件

2.1 、Container容器组件open in new window

  • 常用属性
名称功能
alignment容器对齐方式
decoration容器装饰
margin,padding,height,width...
transform变换
child容器子元素

和div有点相似主要用于布局

import 'package:flutter/material.dart';

void main() {
  return runApp(MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: const Text('首页')),
      body: homePage(),
    ),
  ));
}

class homePage extends StatelessWidget {
  const homePage({super.key});

  
  Widget build(BuildContext context) {
    //返回一个居中组件
    return Center(
        //容器
        child: Container(
      alignment: Alignment.center,//内容显示的方位
      width: 100,
      height: 100,
      transform: Matrix4.rotationZ(0.5),//旋转
      //设置背景装饰
      decoration: const BoxDecoration(
        color:Colors.blue,//背景颜色
        gradient: LinearGradient(colors: [Colors.red, Colors.orange]), //背景渐变
        boxShadow: [
          BoxShadow(
              color: Colors.blue, offset: Offset(2, 2), blurRadius: 20), //阴影
        ],
      ),
      //在容器里居中一个文字
      child: const Text(
        'xxx',
        style: TextStyle(color: Colors.amberAccent),
      ),
    ));
  }
}

2.2、Text组件open in new window

名称功能
textAlign文本对齐方式(center居中,left左对齐,right右对齐,justfy两端对齐)
textDirection文本方向(ltr从左至右,rtl从右至左)
overflow文字超出屏幕之后的处理方式(clip裁剪,fade渐隐,ellipsis省略号)
textScaleFactor字体显示倍率
maxLines文字显示最大行数
style字体的样式设置

下面是 TextStyle 的参数 :

名称功能
decoration文字装饰线(none没有线,lineThrough删除线,overline上划线,underline下划线)
decorationColor文字装饰线颜色
decorationStyle文字装饰线风格([dashed,dotted]虚线,double两根线,solid一根实线,wavy波浪线)
wordSpacing单词间隙(如果是负值,会让单词变得更紧凑)
letterSpacing字母间隙(如果是负值,会让字母变得更紧凑)
fontStyle文字样式(italic斜体,normal正常体)
fontSize...
color...
fontWeight...
import 'package:flutter/material.dart';

class MyText extends StatelessWidget {
  const MyText({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      width: 200,
      height: 200,
      margin: const EdgeInsets.fromLTRB(0, 10, 0, 0),
      padding: const EdgeInsets.all(10),
      decoration: const BoxDecoration(color: Colors.blueGrey),
      child: const Text(
        'this is a Text ldawdawdwadadwddhuawiadwuwduduuiuiwauwd',
        textAlign: TextAlign.center,
        maxLines: 1,
        overflow: TextOverflow.ellipsis,
        style: TextStyle(
          color: Colors.white,
          fontSize: 20,
          fontWeight: FontWeight.bold, //加粗
          fontStyle: FontStyle.italic, //倾斜
          letterSpacing: 3, //字间距
          decoration: TextDecoration.underline, //线条装饰
          decorationColor: Colors.red,//线条颜色
          decorationStyle: TextDecorationStyle.dashed, //虚线
        ),
      ),
    );
  }
}

2.3、图片组件open in new window

常用属性

名称类型说明
alignmentAlignment图片的对齐方式
color和colorBlendMode设置图片的背景颜色,通常和colorBlendMode配合一起使用,这样可以是图片颜色和背景色混合。上面的图片就是进行了颜色的混合,绿色背景和图片红色的混合
fitBoxFitBoxFit.fill:全图显示,图片会被拉伸,并充满父容器。
BoxFit.contain:全图显示,显示原比例,可能会有空隙。
BoxFit.cover:显示可能拉伸,可能裁切,充满(图片要充满整个容器,还不变形)。
BoxFit.fitWidth:宽度充满(横向充满),显示可能拉伸,可能裁切
BoxFit.fitHeight :高度充满(竖向充满),显示可能拉伸,可能裁切。
BoxFit.scaleDown:效果和contain差不多,但是此属性不允许显示超过源图片大小,可小不可大。
repeatImageRepeat.repeat : 横向和纵向都进行重复,直到铺满整个画布。
ImageRepeat.repeatX: 横向重复,纵向不重复。
ImageRepeat.repeatY:纵向重复,横向不重复。
width宽度 一般结合ClipOval才能看到效果
height高度 一般结合ClipOval才能看到效果

2.3.1、本地图片:Image.asset('url')

  1. 新建文件夹 名字随便取 设置2.0x和3.0x会自动根据当前手机的dpi匹配对应尺寸 当然也可以不设置2.0x和3.0x

    ​ images

    ​ home.png

    ​ 2.0x

    ​ home.png

    ​ 3.0x

    ​ home.png

  2. 在pubspec.yaml中的assets:下面添加你要引入的图片

      assets:
      	# 你也可以设置- images/  #表示加载images目录下的全部
        - images/home.png
        - images/2.0x/home.png
        - images/3.0x/home.png    
    
  3. 使用Image.asset()加载本地图片

    Image.asset(
          'images/home.png',
          width: 24,
          height: 24,
        );
    

2.3.2、网络图片:Image.network('url')

import 'package:flutter/material.dart';

class MyImages extends StatelessWidget {
  const MyImages({super.key});

  
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: 200,
        height: 200,
        margin: const EdgeInsets.fromLTRB(0, 10, 0, 0),
        decoration: const BoxDecoration(color: Colors.yellow),
        child: Image.network(
          'https://picsum.photos/50/50',
          alignment: Alignment.topLeft, //t图片对齐位置
          // fit: BoxFit.cover, //图片剪切
          repeat: ImageRepeat.repeat, //设置平铺
        ),
      ),
    );
  }
}

2.3.3、圆形图片

  • Container实现圆形图片
import 'package:flutter/material.dart';

class MyImages extends StatelessWidget {
  const MyImages({super.key});

  
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: 200,
        height: 200,
        margin: const EdgeInsets.fromLTRB(0, 10, 0, 0),
        decoration: BoxDecoration(
            color: Colors.yellow,
            borderRadius: BorderRadius.circular(100), //圆角设置
            image: const DecorationImage(
              //设置背景图
              image: NetworkImage('https://picsum.photos/200/200'),
              fit: BoxFit.cover, //填充
            )),
        //普通图片
        /* child: Image.network(
          'https://picsum.photos/200/200',
          fit: BoxFit.cover,
        ), */
      ),
    );
  }
}
  • ClipOval实现圆形图片
import 'package:flutter/material.dart';

class ClipOvalImage extends StatelessWidget {
  const ClipOvalImage({super.key});

  
  Widget build(BuildContext context) {
    return ClipOval(
      child: Image.network(
        'https://picsum.photos/100/100?id=1',
        width: 100,
        height: 100,
        fit: BoxFit.cover,
      ),
    );
  }
}

  • CircleAvatar实现圆形图片
import 'package:flutter/material.dart';

class MyClipOvalImage extends StatelessWidget {
  const MyClipOvalImage({super.key});

  
  Widget build(BuildContext context) {
    return const CircleAvatar(
      radius: 100,
      backgroundImage:
          NetworkImage("https://www.itying.com/images/flutter/3.png"),
    );
  }
}

基本上,CircleAvatar 不提供设置边框的属性。但是,可以将其包裹在具有更大半径和不同背景颜色的

不同 CircleAvatar 中,以创建类似于边框的内容。

import 'package:flutter/material.dart';

class MyClipOvalImage extends StatelessWidget {
  const MyClipOvalImage({super.key});

  
  Widget build(BuildContext context) {
    return const CircleAvatar(
        radius: 110,
        backgroundColor: Color(0xffFDCF09),//背景色就是边框
        child: CircleAvatar(
          radius: 100,
          backgroundImage:
              NetworkImage("https://www.itying.com/images/flutter/3.png"),
        ));
  }
}

2.4、自带图标组件open in new window件和自定义图标

2.4.1、系统图标使用Icon.(Icons.home)

import 'package:flutter/material.dart';

class MyIcon extends StatelessWidget {
  const MyIcon({super.key});

  
  Widget build(BuildContext context) {
    return const Column(
      children: [
        SizedBox(
          height: 20,
        ),
        Icon(
          Icons.home,
          color: Colors.red,
          size: 50,
        ),
        Icon(Icons.person_pin)
      ],
    );
  }
}

2.4.2、使用自定义图标

前往阿里巴巴的iconfont

2.4.2.1、下载图标复制iconfont.json和iconfont.ttf到项目
2.4.2.2、在项目根目录新建fonts文件夹后复制刚才的两个文件
2.4.2.3、在pubspec.yaml中找到fonts: 进行配置
  fonts:
    - family: MyiCON  #名字可以自定义
      fonts:
        - asset: fonts/iconfont.ttf  #引入字体文件
  #   - family: Trajan Pro  
  #     fonts:     #也可以引入多个fonts
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
2.4.2.4、为了方便使用 定义一个MyIcons类 功能和Icons类一样

MyIcon.dart

import 'package:flutter/material.dart';

class MyIcon {
  // 定义静态属性  直接调用
  // 支付宝
  static const IconData aliPay =IconData(
      0xe634,//可在iconfont.json中查看unicode码 要加上0x
      fontFamily: 'MyiCON',//pubspec.yaml中定义的family
      matchTextDirection: true//文本方向绘制图表
  );
  // 微信
  static const IconData weixinPay =IconData(
      0xe607,
      fontFamily: 'MyiCON',
      matchTextDirection: true
  );
  // 蓝牙
  static const IconData lanya =IconData(
      0xee65f,
      fontFamily: 'MyiCON',
      matchTextDirection: true
  );
}

页面中使用


import 'package:flutter/material.dart';
//引入刚才定义的图标
import 'pages/TestComponents/MyIcon.dart';

void main() {
  return runApp(MaterialApp(
    theme: ThemeData(primarySwatch: Colors.blue),
    home: Scaffold(
      appBar: AppBar(
        title: const Text('首页'),
      ), //导航标题
      // 主体内容
      body: const Column(children: [
        Icon(
          MyIcon.aliPay,//通过MyIcon.aliPay直接访问
          size: 40,
          color: Colors.blue,
        )
      ]),
    ),
  ));
}

2.5、ListView列表组价open in new window

*列表组件常用参数

名称类型说明
scrollDirectionAxisAxis.horizontal水平列表Axis.vertical垂直列表
paddingEdgeInsetsGeometry内边距
resolvebool组件反向排序
childrenList列表元素

2.5.1、垂直列表

由于Column中内容太多 需用使用ListView展示列表

body:  ListView(children:const [//我们只需要把Column换成ListView  就可以滚动了
        MyApp(),
        MyButton(),
        MyText(),
        MyImages(),
        SizedBox(
          height: 20,
        ),
        // MyCricularImage(),
        MyClipOvalImage(),
        SizedBox(
          height: 20,
        ),
        Icon(
          MyIcon.aliPay,
          size: 40,
          color: Colors.blue,
        )
      ]),

搭配ListTile()使用

// 定义body
class MyHome extends StatelessWidget {
  const MyHome({super.key});

  
  Widget build(BuildContext context) {
    return ListView(
      children: const <Widget>[
        ListTile(
          leading: Icon(Icons.home),
          title: Text('首页'),
        ),
        Divider(), //虚线
        ListTile(
          leading: Icon(Icons.person),
          title: Text('个人中心'),
        ),
        Divider(),
        ListTile(
          leading: Icon(Icons.car_rental),
          title: Text('订单'),
        ),
        Divider(),
        ListTile(
          leading: Icon(MyIcon.jntm), //列表左侧的内容
          title: Text('鸡哥'),
          trailing: Icon(Icons.chevron_right), //列表右侧的内容
        ),
        Divider(),
      ],
    );
  }
}

2.5.2、设置水平列表

设置scrollDirection: Axis.horizontal,

注意:ListView垂直布局:Container中是需要设置高度,设置宽度无效

​ ListView水平布局:Container中是需要设置宽度,设置高度无效 ,但是想要显示出水平列表 需要在listView外面再嵌套一个SizedBox或者Container来设置高度

// 水平列表
class MyListView4 extends StatelessWidget {
  const MyListView4({super.key});

  
  Widget build(BuildContext context) {
    return SizedBox(
        height: 120,
        child: ListView(
          scrollDirection: Axis.horizontal,
          children: <Widget>[
            Container(
              width: 120,
              decoration: const BoxDecoration(
                color: Colors.blue,
              ),
            ),
            Container(
              width: 120,
              decoration: const BoxDecoration(
                color: Colors.yellow,
              ),
            ),
            Container(
              width: 120,
              decoration: const BoxDecoration(
                color: Colors.red,
              ),
            ),
            Container(
              width: 120,
              decoration: const BoxDecoration(
                color: Colors.green,
              ),
            ),
            Container(
              height: 120,
              decoration: const BoxDecoration(
                color: Colors.black,
              ),
            ),
            Container(
              height: 120,
              decoration: const BoxDecoration(
                color: Colors.cyan,
              ),
            )
          ],
        ));
  }
}

2.6、ListView动态列表

2.6.1、基础生成列表

通过循环实现动态列表

// 以下是动态列表
import 'package:flutter/material.dart';

//
class MyListViewTrends extends StatelessWidget {
  const MyListViewTrends({super.key});

  // 定义一个返回List<Widget>的函数来渲染list列表
  List<Widget> _getInitData() {
    // 定义一个List<Widget>类型的list数组
    List<Widget> list = [];
    for (var i = 0; i < 20; i++) {
      list.add(ListTile(
        title: Text('我是列表---$i'),
      ));
    }
    return list;
  }

  
  Widget build(BuildContext context) {
    return ListView(
      // ListView的children需要返回一个List<Widget>来与渲染列表
      children: _getInitData(),
    );
  }
}

使用map更方便

// 模拟网络请求的列表
class RequestTrendsList extends StatelessWidget {
  const RequestTrendsList({super.key});
  // 从服务器获取数据然后遍历list
  List<Widget> getDataList() {
    // 使用map渲染
    var list = dataList.map((item) {
      return ListTile(
        leading: Image.network("${item["img"]}"),
        title: Text('${item["title"]}'),
        subtitle: Text('${item["author"]}'),
      );
    });
    return list.toList(); //需要调用toList()转换为数组
  }

  
  Widget build(BuildContext context) {
    return ListView(
      children: getDataList(),
    );
  }
}

2.6.1、ListBuilder生成列表

推荐使用ListBuilder的方式

class ListBuilderTrendsList extends StatelessWidget {
  const ListBuilderTrendsList({super.key});

  
  Widget build(BuildContext context) {
    return ListView.builder(
        itemCount: dataList.length, //循环次数
        itemBuilder: (context, i) {
          //类似js的forEach
          // 需要返回构Widget
          return ListTile(
            leading: Image.network('${dataList[i]["img"]}'),
            title: Text('${dataList[i]["title"]}'),
            subtitle: Text('${dataList[i]["author"]}'),
          );
        });
  }
}

2.7、GridView网格布局组件open in new window

*常用属性

名称类型说明
scrollDirectionAxis滚动方法
paddingEdgeInsetsGeometry内边距
resolvebool组件反向排序
crossAxisSpacingdouble水平子Widget之间间距
mainAxisSpacingdouble垂直子Widget之间间
crossAxisCountint 用在GridView.count一行的Widget数量
maxCrossAxisExtentdouble 用在GridView.extent横轴子元素的最大长度
childAspectRatiodouble子Widget宽高比例
children[ ]
gridDelegateSliverGridDelegateWithFixedCrossAxisCount
SliverGridDelegateWithMaxCrossAxisExtent
控制布局主要用在GridView.builder里面

*实现效果

*以下案例定义一个公共List方法

List<Widget> _getInitData() {
  // 定义一个List<Widget>类型的list数组
  List<Widget> list = [];
  for (var i = 0; i < 36; i++) {
    list.add(
      Image.network('https://picsum.photos/120?id=${i + 1}'),
    );
  }
  return list;
}

1、通过GridView.count 实现网格布局

// 1、通过GridView.count 实现网格布局
class YGridViewCount extends StatelessWidget {
  const YGridViewCount({super.key});

  
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 3, //一行显示多少个Weight
      children: _getInitData(),
    );
  }
}

2、通过GridView.extent 实现网格布局

// 通过GridView.extent 实现网格布局
class YGridViewExtent extends StatelessWidget {
  const YGridViewExtent({super.key});

  
  Widget build(BuildContext context) {
    return GridView.extent(
        padding: const EdgeInsets.all(20), //内边距
        maxCrossAxisExtent: 180, //横轴子元素的最大长度  会根据设置的值,自动计算,排列
        crossAxisSpacing: 10, //纵向间距
        mainAxisSpacing: 10, //横向间距
        childAspectRatio: 1, //改变子元素宽高的比例  默认1.0  正方形
        children: _getInitData()); //调用Weight方法
  }
}

3、通过GridView.builder实现动态网格布局

SliverGridDelegateWithFixedCrossAxisCount中实现GridView.count的功能

itemBuilder是一个函数 需要返回Widget

// 通过GridView.builder实现动态网格布局
// 构建函数返回Widget
Widget _buildWidget(context, index) {
  return Container(
    padding: const EdgeInsets.all(5),
    alignment: Alignment.center,
    decoration: BoxDecoration(border: Border.all(color: Colors.black26)),
    child: Column(children: [
      Image.network(
        '${dataList[index]["img"]}',
      ),
      const SizedBox(
        height: 10,
      ),
      Text(
        '${dataList[index]["title"]}',
      )
    ]),
  );
}

class YGridViewBuilder extends StatelessWidget {
  const YGridViewBuilder({super.key});

  
  Widget build(BuildContext context) {
    return GridView.builder(
      padding: const EdgeInsets.all(10), //内边距
      itemCount: dataList.length, //传入循环次数
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2, //横轴子元素的最大长度  会根据设置的值,自动计算,排列
        crossAxisSpacing: 10, //纵向间距
        mainAxisSpacing: 10, //横向间距
        childAspectRatio: 0.8, //改变子元素宽高的比例  默认1.0  正方形
      ),
      itemBuilder: _buildWidget, //  注册方法  不需要加上()
    ); //调用Weight方法
  }
}

SliverGridDelegateWithMaxCrossAxisExtent中实现类似GridView.count的功能

//_buildWidget方法不变
class YGridViewBuilder extends StatelessWidget {
  const YGridViewBuilder({super.key});

  
  Widget build(BuildContext context) {
    return GridView.builder(
      padding: const EdgeInsets.all(10), //内边距
      itemCount: dataList.length, //传入循环次数
      gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
        crossAxisSpacing: 10, //纵向间距
        mainAxisSpacing: 10, //横向间距
        childAspectRatio: 0.8, //改变子元素宽高的比例  默认1.0  正方形
        maxCrossAxisExtent: 220,//横轴子元素的最大长度
      ),
      itemBuilder: _buildWidget, //  注册方法  不需要加上()
    ); //调用Weight方法
  }
}

2.8、页面布局组件

2.8.1、Padding组件

padding组件功能单一 就是内边距,单纯只需要添加边距 使用Padding比Container的性能消耗少

class YLayoutPadding extends StatelessWidget {
  const YLayoutPadding({super.key});

  
  Widget build(BuildContext context) {
    return const Padding(
      padding: EdgeInsets.all(10),
      child: Text('你好'),
    );
  }
}

2.8.2、线性布局 (Column和Row)

*共同属性

属性类型说明
mainAxisAlignmentMainAxisAlignment主轴的排序方式
crossAxisAlignmentCrossAxisAlignment次轴的排序方式
children组件子元素
2.8.2.1、Column组件(垂直)
// 实现传参控制背景色,icon等
class IconContainer extends StatelessWidget {
  Color color;
  IconData icon;

  IconContainer(this.icon, {Key? key, this.color = Colors.red})
      : super(key: key);

  
  Widget build(BuildContext context) {
    return Container(
      width: 120,
      height: 120,
      color: color,
      alignment: Alignment.center,
      child: Icon(
        icon,
        color: Colors.white,
        size: 30,
      ),
    );
  }
}

使用

import 'package:flutter/material.dart';

import 'pages/TestComponents/YLayout.dart';

void main() {
  return runApp(MaterialApp(
    theme: ThemeData(
      colorScheme: const ColorScheme.light(primary: Colors.blue),
    ),
    home: Scaffold(
      appBar: AppBar(
        title: const Text('首页'),
      ), //导航标题
      // 主体内容
      body: IconContainer(color: Colors.blue, Icons.home),//传入参数
    ),
  ));
}
2.8.2.2、Row组件(水平)
// Row
class IconContainerRow extends StatelessWidget {
  const IconContainerRow({super.key});

  
  Widget build(BuildContext context) {
    return Container(
        width: double.infinity,//也可以设为double.maxFinite  或可以设置非常大 都可以达到效果  99999
		height: double.infinity,
        decoration: const BoxDecoration(color: Colors.black54),
        //Row改成Column则垂直显示
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            IconContainerColumn(Icons.home, color: Colors.blue),
            IconContainerColumn(Icons.search, color: Colors.green),
            IconContainerColumn(Icons.ac_unit)
          ],
        ));
  }
}

让Container组检撑满屏幕可以把width和高设置非常大的值 或者使用double.infinity以达到撑满全屏幕的效果

2.8.3、Flex弹性布局组件

使用Expanded组件

import 'package:flutter/material.dart';
//引入IconContainerColumn组件
import './YLayout.dart';

// 弹性布局
class ElasticityFlex extends StatelessWidget {
  const ElasticityFlex({super.key});

  
  Widget build(BuildContext context) {
    return Flex(
      direction: Axis.horizontal, //设置水平排列还是垂直方向
      children: [
        // 左边占1份
        Expanded(
          flex: 1,
          child: IconContainerColumn(Icons.home,
              color: Colors.blue), //设置元素宽度是没有效果的
        ),
        // 右边占2份
        Expanded(
          flex: 2,
          child: IconContainerColumn(Icons.search, color: Colors.green),
        ),
      ],
    );
  }
}

右侧固定宽度 左侧自适应

不是使用Expanded包裹组件即可

//其余代码与上方相同
children: [
    // 左边占1份
    Expanded(
        flex: 2,
        child: IconContainerColumn(Icons.home,
                                   color: Colors.blue), //设置元素宽度是没有效果的
    ),
    // 右边占2份
    IconContainerColumn(Icons.search, color: Colors.green)
],
2.8.4、使用Row Column 结合Expanded实现下面示例
import 'package:flutter/material.dart';

class YFlexDome extends StatelessWidget {
  const YFlexDome({super.key});

  
  Widget build(BuildContext context) {
    return ListView(
      children: [
        Container(
          width: double.infinity,
          height: 200,
          decoration: const BoxDecoration(
              image: DecorationImage(
                  image: NetworkImage('https://picsum.photos/800?id=1'),
                  fit: BoxFit.cover)),
        ),
        const SizedBox(
          height: 10,
        ),
        Row(
          children: [
            Expanded(
                flex: 2,
                child: SizedBox(
                  height: 180,
                  child: Image.network(
                    'https://picsum.photos/800?id=2',
                    fit: BoxFit.cover,
                  ),
                )),
            const SizedBox(
              width: 10,
            ),
            Expanded(
              flex: 1,
              child: SizedBox(
                height: 180,
                child: Column(
                  children: [
                    Expanded(
                        flex: 1,
                        child: SizedBox(
                          width: double.infinity,
                          child: Image.network(
                            'https://picsum.photos/800?id=3',
                            fit: BoxFit.cover,
                          ),
                        )),
                    const SizedBox(
                      height: 10,
                    ),
                    Expanded(
                        flex: 1,
                        child: SizedBox(
                          width: double.infinity,
                          child: Image.network(
                            'https://picsum.photos/800?id=4',
                            fit: BoxFit.cover,
                          ),
                        ))
                  ],
                ),
              ),
            )
          ],
        ),
      ],
    );
  }
}

2.9、层叠布局

Stack是堆的意思,你可以理解成H5的定位

2.9.1、Stack组件

属性说明
alignment配置所有子元素的显示位置
children子组件
import 'package:flutter/material.dart';

class YStack extends StatelessWidget {
  const YStack({super.key});

  
  Widget build(BuildContext context) {
    return Stack(
      //alignment: Alignment.center, //设置居中
      children: [
        Container(
          width: 400,
          height: 300,
          color: Colors.red,
        ),
        Container(
          width: 200,
          height: 200,
          color: Colors.yellow,
        ),
        const Text('你好'),
        const Text('wdawda'),
      ],
    );
  }
}

2.9.2 、Align组件

属性说明
alignment配置所有子元素的显示位置
children子组件

Align结合Container

// Align组件
class YAlign extends StatelessWidget {
  const YAlign({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      width: 300,
      height: 300,
      color: Colors.red,
      child: const Align(
        alignment: Alignment(1, 0), //可以使用方位   也可以使用Alignment.center  ....
        child: Text('123'),
      ),
    );
  }
}

2.9.3 、Positioned组件

在Stack中的children下使用Positioned设置位置

Stack相对于外部容器进行定位,如果没有外部容器就相对于整个屏幕进行定位

import 'package:flutter/material.dart';

class YStack extends StatelessWidget {
  const YStack({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: 300,
      color: Colors.red,
      child: Stack(//相对于外部日期进行定位,如果没有外部容器就相对于整个屏幕进行定位
        children: [
          // 使用Positioned包裹后设置位置
          Positioned(
            left: 10,
            bottom: 10,
            child: Container(width: 100, height: 100, color: Colors.yellow),
          ),
          const Positioned(right: 0, top: 150, child: Text('你好'))
        ],
      ),
    );
  }
}

2.9.4 、MediaQuery获取屏幕宽度和高度

final size =MediaQuery.of(context).size;

组件的build方法中可以通过,=MediaQuery.of(context).size;

Widget build(BuildContext context) {
    final size =MediaQuery.of(context).size;
    final width =size.width;
    final height =size.height;
}

2.9.5、Stack Positioned 固定导航案例

import 'package:flutter/material.dart';

// 顶部固定导航
class YFloatNav extends StatelessWidget {
  const YFloatNav({super.key});

  List<Widget> _getList() {
    List<Widget> list = [];
    for (var i = 0; i < 20; i++) {
      list.add(ListTile(title: Text('我是列表---${i + 1}')));
    }
    return list;
  }

  
  Widget build(BuildContext context) {
    // 获取设备的宽高
    final size = MediaQuery.of(context).size;
    return Stack(
      children: [
        ListView(
            padding: const EdgeInsets.only(top: 50), //解决列表1被挡住
            children: _getList()),
        Positioned(
          width:
              size.width, //Positioned中无法设置double.infinity  使用MediaQuery获取页面宽度
          height: 44, //需要配置宽高
          left: 0,
          top: 0,
          child: Container(
            alignment: Alignment.center, //设置居中
            height: 44,
            color: Colors.red,
            child: const Text(
              '二级导航',
              style: TextStyle(color: Colors.white),
            ),
          ),
        )
      ],
    );
  }
}

2.10、AspectRatio

AspectRatio的作用是根据设置调整子元素child的宽高比。

属性说明
aspectRatio宽高比,最终可能不会根据这个值去布局,具体则要看综合因素,外层是否允许按照这种比率进行布局,这只是一个参考值
child子组件
import 'package:flutter/material.dart';

// AspectRatio调节宽高比
class YAspectRatio extends StatelessWidget {
  const YAspectRatio({super.key});

  
  Widget build(BuildContext context) {
    // 页面上显示一个容器  宽度为屏幕的的宽度 高度为容器宽度的一半
    return AspectRatio(
      aspectRatio: 2 / 1, //宽高2/1
      child: Container(
        color: Colors.red,
      ),
    );
  }

2.11、Card组件

Card是卡片组件块,内容可以由大多数类型的Widget构成,Card具有圆角和阴影,这让它看起来有立体感。

属性说明
margin外边距
child子组件
elevation阴影值的深度
color背景颜色
shadowColor阴影颜色
clipBehaviorclipBehavior 内容溢出的剪切方式 :
Clip.none不剪切
Clip.hardEdge裁剪但不应用抗锯齿
Clip.antiAlias裁剪而且抗锯齿
Clip.antiAliasWithSaveLayer带有抗锯齿的剪辑,并在剪辑之后立即保存saveLayer
ShapeCard的阴影效果,默认的阴影效果为圆角的长方形边。
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))

使用Card实现以下效果

//引入循环的数据
import 'package:bangda/pages/TestComponents/datas.dart';

// 实现一个卡片图文卡片列表
class YImageTextCard extends StatelessWidget {
  const YImageTextCard({super.key});
  // 循环生成card
  List<Widget> _initCardData() {
    var cardList = dataList.map((item) {
      return Card(
        clipBehavior: Clip.antiAlias, //超出部分剪切掉
        margin: const EdgeInsets.all(10),
        elevation: 20,
        shadowColor: const Color.fromARGB(255, 221, 218, 208),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10), //圆角设置
        ),
        child: Column(
          children: [
            AspectRatio(
              aspectRatio: 16 / 9, //图片的宽高比16/9
              child: Image.network(
                '${item["img"]}',
                fit: BoxFit.cover,
              ),
            ),
            ListTile(
              leading: CircleAvatar(
                radius: 25, //配置半径
                backgroundImage: NetworkImage('${item["img"]}1'),
              ),
              title: Text('${item['title']}'),
              subtitle: Text('${item['author']}'),
            )
          ],
        ),
      );
    });
    return cardList.toList();
  }

  
  Widget build(BuildContext context) {
    return ListView(
      children: _initCardData(),
    );
  }
}

2.12、按钮组件open in new window

常用属性

属性说明
onPressed必填参数,按下按钮时触发的回调,接收一个方法,传null表示按钮禁用,会显示禁用相关样式
style通过ButtonStyle装饰
child子组件
  • ButtonStylee里面的常用的参数
属性值类型说明
foregroundColorColor文本颜色
backgroundColorColor按钮的颜色
shadowColorColor阴影颜色
elevationdouble阴影的范围,值越大阴影范围越大
padding内边距
shape设置按钮的形状 shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)))
side设置边框MaterialStateProperty.all(BorderSide(width:1,color:Colors.red))

按钮示例

import 'package:flutter/material.dart';

class YButtons extends StatelessWidget {
  const YButtons({super.key});

  
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(10),
      children: [
        Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // ElevatedButton按钮
            const Text('ElevatedButton'),
            const SizedBox(
              height: 10,
            ),
            Row(
              children: [
                ElevatedButton(onPressed: () {}, child: const Text('凸起按钮')),
              ],
            ),
            const SizedBox(
              height: 10,
            ),
            // 文本按钮
            const Text('TextButton'),
            TextButton(
              child: const Text("文本按钮"),
              onPressed: () {},
            ),
            const SizedBox(
              height: 10,
            ),
            // 边框按钮
            const Text('OutlinedButton'),
            OutlinedButton(
              child: const Text("边框按钮"),
              onPressed: () {},
            ),
            const SizedBox(
              height: 10,
            ),
            // IconButton图标按钮
            const Text('IconButton'),
            IconButton(
              icon: const Icon(Icons.thumb_up),
              onPressed: () {},
            ),
            const SizedBox(
              height: 10,
            ),
            // 带图标的按钮
            const Text('带图标的按钮'),
            Row(
              children: [
                ElevatedButton.icon(
                  icon: const Icon(Icons.send),
                  label: const Text("发送"),
                  onPressed: () {},
                ),
                const SizedBox(
                  width: 5,
                ),
                OutlinedButton.icon(
                  icon: const Icon(Icons.add),
                  label: const Text("添加"),
                  onPressed: () {},
                ),
                const SizedBox(
                  width: 5,
                ),
                TextButton.icon(
                  icon: const Icon(Icons.info),
                  label: const Text("详情"),
                  onPressed: () {},
                ),
              ],
            ),
            const SizedBox(
              height: 10,
            ),
            // 设置按钮宽高和圆角
            const Text('设置按钮宽高和圆角'),
            SizedBox(
              height: 80,
              width: 200,
              child: ElevatedButton(
                style: ButtonStyle(
                    shape: MaterialStateProperty.all(RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(10))), //设置圆角
                    backgroundColor:
                        MaterialStateProperty.all(Colors.red), //按钮背景色
                    foregroundColor:
                        MaterialStateProperty.all(Colors.black)), //按钮的文字颜色
                onPressed: () {},
                child: const Text('宽度高度'),
              ),
            ),
            const SizedBox(
              height: 10,
            ),
            // 自适应按钮
            const Text('自适应按钮'),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Expanded(
                  child: Container(
                    height: 60,
                    margin: const EdgeInsets.all(10),
                    child: ElevatedButton(
                      child: const Text('自适应按钮'),
                      onPressed: () {
                        print("自适应按钮");
                      },
                    ),
                  ),
                )
              ],
            ),
            const SizedBox(
              height: 10,
            ),
            // 圆形按钮
            const Text('圆形按钮'),
            SizedBox(
              height: 80,
              child: ElevatedButton(
                style: ButtonStyle(
                    backgroundColor: MaterialStateProperty.all(Colors.blue),
                    foregroundColor: MaterialStateProperty.all(Colors.white),
                    elevation: MaterialStateProperty.all(20),
                    shape: MaterialStateProperty.all(//设置圆形+白色边框
                        const CircleBorder(
                            side: BorderSide(color: Colors.white)))),
                onPressed: () {},
                child: const Text('圆形按钮'),
              ),
            ),
            const SizedBox(
              height: 10,
            ),
            // 修改OutlinedButton边框
            const Text('修改OutlinedButton边框'),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Expanded(
                  child: Container(
                    margin: const EdgeInsets.all(20),
                    height: 50,
                    child: OutlinedButton(
                        style: ButtonStyle(
                            foregroundColor:
                                MaterialStateProperty.all(Colors.black),
                            side: MaterialStateProperty.all(const BorderSide(
                                width: 1, color: Colors.red))), //边框颜色以及宽度
                        onPressed: () {},
                        child: const Text("注册 配置边框")),
                  ),
                )
              ],
            )
          ],
        ),
      ],
    );
  }
}

2.12.1自定义一个按钮组件传入文本和点击事件

import 'package:flutter/material.dart';

// 自定义一个按钮组件
// ignore: must_be_immutable
class YCustomButton extends StatelessWidget {
  String text = "按钮"; // 按钮文字
  // 事件
  Function onPressed; //按钮事件
  // 构造函数
  YCustomButton(this.text, {Key? key, required this.onPressed})
      : super(key: key);
  
  Widget build(BuildContext context) {
    return ElevatedButton(
        style: ButtonStyle(
          backgroundColor: MaterialStateProperty.all(Colors.blue),
          foregroundColor: MaterialStateProperty.all(Colors.white),
        ),
        onPressed: onPressed(),
        child: const Text('第一集'));
  }
}

使用

import 'package:flutter/material.dart';

import 'pages/TestComponents/YButtons.dart';//引入刚才的组件

void main() {
  return runApp(MaterialApp(
    theme: ThemeData(),
    home: Scaffold(
        appBar: AppBar(
          title: const Text('首页'),
        ), //导航标题
        // 主体内容
        body: YCustomButton('自定义按钮', onPressed: () {})),
  ));
}

2.13、Wrap组件

Wrap可以实现流布局,单行的Wrap跟Row表现几乎一致,单列的Wrap则跟Column表现几乎一致。但

Row与Column都是单行单列的,Wrap则突破了这个限制,mainAxis上空间不足时,则向crossAxis上

去扩展显示。

属性说明
direction主轴的方向,默认水平
alignment主轴的对其方式
spacing主轴方向上的间距
textDirection文本方向
verticalDirection定义了children摆放顺序,默认是down,见Flex相关属性介绍。
runAlignmentrun的对齐方式。run可以理解为新的行或者列,如果是水平方向布局的话,run可以理解为新的一行
runSpacingrun的间距

实现一个搜索记录

// 搜索+记录
class YSearchRecord extends StatelessWidget {
  const YSearchRecord({super.key});

  
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(10),
      children: [
        Row(
          children: [
            Text(
              '热搜',
              style: Theme.of(context).textTheme.titleLarge,
            ),
          ],
        ),
        const Divider(),
        Wrap(
          spacing: 10,
          runSpacing: 10,
          children: [
            YCustomButton(
              '女装',
              onPressed: () {},
            ),
            YCustomButton(
              '男装',
              onPressed: () {},
            ),
            YCustomButton(
              '笔记本',
              onPressed: () {},
            ),
            YCustomButton(
              '玩具',
              onPressed: () {},
            ),
            YCustomButton(
              '文学',
              onPressed: () {},
            ),
            YCustomButton(
              '时尚',
              onPressed: () {},
            ),
            YCustomButton(
              '电脑',
              onPressed: () {},
            ),
            YCustomButton(
              '手机',
              onPressed: () {},
            ),
            YCustomButton(
              '数码',
              onPressed: () {},
            ),
            YCustomButton(
              '宠物',
              onPressed: () {},
            ),
          ],
        ),
        const SizedBox(
          height: 10,
        ),
        Row(
          children: [
            Text(
              '历史记录',
              style: Theme.of(context).textTheme.titleLarge,
            ),
          ],
        ),
        const Divider(),
        const Column(
          children: [
            ListTile(
              title: Text('女装'),
            ),
            Divider(),
            ListTile(
              title: Text('男装'),
            ),
            Divider(),
            ListTile(
              title: Text('手机'),
            ),
            Divider(),
          ],
        ),
        const SizedBox(
          height: 40,
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            OutlinedButton.icon(
                style: ButtonStyle(
                    foregroundColor: MaterialStateProperty.all(Colors.black54)),
                onPressed: () {},
                icon: const Icon(Icons.delete),
                label: const Text('清空历史记录'))
          ],
        )
      ],
    );
  }
}

3.StatefulWidget有状态组件

在Flutter中自定义组件其实就是一个类,这个类需要继承StatelessWidget/StatefulWidget。

  • StatelessWidget是无状态组件,状态不可变的widget

  • StatefulWidget是有状态组件,持有的状态可能在widget生命周期改变。

通俗的讲:如果我们想改变页面中的数据的话这个时候就需要用到StatefulWidget

下载插件后 statefw 快速生成有状态组件

3.1、使用StatefulWidget实现一个计数器的功能

import 'package:flutter/material.dart';

// 使用有状态组件实现计数器
class SfHomePage extends StatefulWidget {
  const SfHomePage({super.key});
  
  State<SfHomePage> createState() => _SfHomePageState();
}

class _SfHomePageState extends State<SfHomePage> {
  int countNum = 0;
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Flutter App')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              '$countNum',
              style: Theme.of(context).textTheme.headlineLarge,
            ),
            const SizedBox(
              height: 100,
            ),
            ElevatedButton(
                onPressed: () {
                  setState(() {
                    countNum++;
                  });
                  print(countNum);
                },
                child: const Text('点击'))
          ],
        ),
      ),
      // Scaffold下可设置浮动按钮
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            countNum++;
          });
          print(countNum);
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

3.2、使用StatefulWidget实现一个动态列表功能

class YDynamicList extends StatefulWidget {
  const YDynamicList({super.key});

  
  State<YDynamicList> createState() => _YDynamicListState();
}

class _YDynamicListState extends State<YDynamicList> {
  List<String> list = [];

  int count = 0;
  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text('动态列表')),
        body: ListView(
          children: [
            Column(
              children: list.map((item) {
                return ListTile(
                  title: Text(item),
                );
              }).toList(),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                ElevatedButton(
                    onPressed: () {
                      setState(() {
                        list.removeLast();
                        if (list.isEmpty) {
                          count = 0;
                        }
                      });
                    },
                    child: const Text('删除数据')),
                ElevatedButton(
                    onPressed: () {
                      setState(() {
                        count++;
                        list.add("新数据$count");
                      });
                    },
                    child: const Text('新增数据')),
              ],
            ),
          ],
        ));
  }
}

4.BottonmNavigationBar自定义底部导航条

常用属性

属性说明
itemsList 底部导航条按钮集合
iconSizeicon
currentIndex默认选中第几个
onTap选中变化回调函数
fixedColor选中的颜色
typeBottomNavigationBarType.fixed BottomNavigationBarType.shifting,如果tab超过三个需要配置

在items中定义每一项

4.1、自定义底部导航+点击切换状态

import 'package:flutter/material.dart';

class YBottonmNavigationBar extends StatefulWidget {
  const YBottonmNavigationBar({super.key});

  
  State<YBottonmNavigationBar> createState() => _YBottonmNavigationBarState();
}

class _YBottonmNavigationBarState extends State<YBottonmNavigationBar> {
  
  int tabIndex = 0;

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('自定义底部')),
      body: const Text('自定义底部菜单'),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: tabIndex,
        onTap: (e) {
          setState(() {
            tabIndex = e;
          });
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: '设置',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          )
        ],
      ),
    );
  }
}

4.2、自定义底部导航实现页面切换

import 'package:flutter/material.dart';

// 引入tabbar对应页面
import '../home/home_page.dart';
import '../user/user_page.dart';
import '../order/order_page.dart';

// 自定义底部导航
class TabBars extends StatefulWidget {
  const TabBars({super.key});

  
  State<TabBars> createState() => _TabBarsState();
}

class _TabBarsState extends State<TabBars> {
  // 所有tabbar列表
  final List<Widget> _pageList = const [
    HomePage(),
    OrderPage(),
    UserPage(),
  ];
  int _currentTabs = 0; // 当前激活

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('自定义底部')),
      body: _pageList[_currentTabs], //激活index对应的_pageList
      bottomNavigationBar: BottomNavigationBar(
        iconSize: 24, //底部菜单的大小默认24
        fixedColor: Colors.red, //选中的颜色
        currentIndex: _currentTabs, //当前激活index
        // type: BottomNavigationBarType.fixed, //如果超过三个tab需要设置为fixed来固定
        onTap: (e) {
          setState(() {
            _currentTabs = e; //重新设置激活的index,重新build
          });
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.article),
            label: '订单',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

5、通过FloatingActionButton实现tabbar凸起按钮

常用属性

属性说明
child子视图,一般为Icon,不推荐使用文字
tooltipFAB被长按时显示,也是无障碍功能
backgroundColor背景颜色
elevation未点击的时候的阴影
hignlightElevation点击时阴影值,默认12.0
onPressed点击事件回调
shape可以定义FAB的形状等
mini是否是mini类型默认false
import 'package:flutter/material.dart';

// 引入tabbar对应页面
import '../home/home_page.dart';
import '../user/user_page.dart';
import '../order/order_page.dart';

// 自定义底部导航
class TabBars extends StatefulWidget {
  const TabBars({super.key});

  
  State<TabBars> createState() => _TabBarsState();
}

class _TabBarsState extends State<TabBars> {

  // 所有tabbar列表
  final List<Widget> _pageList = const [
    HomePage(),
    OrderPage(),
    UserPage(),
  ];
  int _currentTabs = 0; // 当前激活

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('自定义底部')),
      body: _pageList[_currentTabs], //激活index对应的_pageList
      bottomNavigationBar: BottomNavigationBar(
        iconSize: 24, //底部菜单的大小默认24
        fixedColor: Colors.red, //选中的颜色
        currentIndex: _currentTabs, //当前激活index
        // type: BottomNavigationBarType.fixed, //如果超过三个tab需要设置为fixed来固定
        onTap: (e) {
          setState(() {
            _currentTabs = e; //重新设置激活的index,重新build
          });
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.article),
            label: '订单',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

6、Scaffold属性 抽屉菜单Drawer

在Scaffold组件里面传入drawer参数可以定义左侧边栏,传入endDrawer可以定义右侧边栏。侧边栏默

认是隐藏的,我们可以通过手指滑动显示侧边栏,也可以通过点击按钮显示侧边栏。

return Scaffold(
    appBar: AppBar(
    	title: Text("Flutter App"),
    ),
    drawer: Drawer(
        child: Text('左侧边栏'),
    ),
    endDrawer: Drawer(
        child: Text('右侧侧边栏'),
    ),
);

6.1、DrawerHeader

  • 常见属性:
属性说明
decoration设置顶部背景颜色
child配置子元素
padding内边距
margin外边距
import 'package:flutter/material.dart';

class YDrawer extends StatefulWidget {
  const YDrawer({super.key});

  
  State<YDrawer> createState() => _YDrawerState();
}

class _YDrawerState extends State<YDrawer> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Drawer 抽屉')),
      drawer: Drawer(
        child: Column(
          children: [
            // DrawerHeader用于定义drawer的头部信息
            DrawerHeader(
                decoration: const BoxDecoration(
                  image: DecorationImage(
                    image: NetworkImage('https://picsum.photos/500'),
                    fit: BoxFit.cover, //铺满
                  ),
                ),
                //ListView撑满行
                child: ListView(
                  children: const [Text('Drawer的头部')],
                )),
            //下面的列表
            ListTile(
              leading: const CircleAvatar(
                child: Icon(Icons.person),
              ),
              title: const Text('个人中心'),
              onTap: () {},
            ),
            ListTile(
              leading: const CircleAvatar(
                child: Icon(Icons.settings),
              ),
              title: const Text('系统设置'),
              onTap: () {},
            ),
          ],
        ),
      ),
      body: const Center(
        child: Text('Drawer 抽屉'),
      ),
    );
  }
}

6.2、UserAccountsDrawerHeader

  • 常见属性:
属性说明
decoration设置顶部背景颜色
accountName账户名称
accountEmail账户邮箱
currentAccountPicture用户头像
otherAccountsPictures用来设置当前账户其他账户头像
margin
class YUserAccountsDrawerHeader extends StatefulWidget {
  const YUserAccountsDrawerHeader({super.key});

  
  State<YUserAccountsDrawerHeader> createState() =>
      _YUserAccountsDrawerHeaderState();
}

class _YUserAccountsDrawerHeaderState extends State<YUserAccountsDrawerHeader> {
  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text('YUserAccountsDrawerHeader')),
        drawer: Drawer(
          child: Column(
            children: [
              UserAccountsDrawerHeader(
                // 用户头像
                currentAccountPicture: const CircleAvatar(
                  backgroundImage:
                      NetworkImage('https://picsum.photos/100?id=0'),
                ),
                accountName: const Text('YeKun'), //用户名
                accountEmail: const Text('2356009030@qq.com'), //用户邮箱
                //当前账户其他账户头像
                otherAccountsPictures: [
                  Image.network('https://picsum.photos/50?id=5'),
                  Image.network('https://picsum.photos/50?id=6'),
                  Image.network('https://picsum.photos/50?id=7'),
                ],
                decoration: const BoxDecoration(
                    image: DecorationImage(
                  image: NetworkImage('https://picsum.photos/500?id=1'),
                  fit: BoxFit.cover,
                )),
              ),
              //下面的列表
              ListTile(
                leading: const CircleAvatar(
                  child: Icon(Icons.person),
                ),
                title: const Text('个人中心'),
                onTap: () {},
              ),
              const Divider(),
              ListTile(
                leading: const CircleAvatar(
                  child: Icon(Icons.settings),
                ),
                title: const Text('系统设置'),
                onTap: () {},
              ),
            ],
          ),
        ));
  }
}

7、AppBar TabBar TabBarView

7.1、AppBar自定义顶部按钮图标、颜色

  • AppBar常用属性
属性描述
leading在标题前面显示的一个控件,在首页通常显示应用的 logo;在其他界面通
title标题,通常显示为当前界面的标题文字,可以放组件
actions右侧图显示的东西,通常使用 IconButton 来表示,可以放按钮组等多个图标
bottom通常放tabBar,标题下面显示一个 Tab 导航栏
backgroundColor导航背景颜色
iconTheme图标样式
centerTitle标题是否居中显示

7.2、AppBar+TabBar实现顶部Tab切换

  • TabBar常见属性:
属性描述
tabs显示的标签内容,一般使用Tab对象,也可以是其他的Widget
controllerTabController对象
isScrollable是否可滚动
indicatorColor指示器颜色
indicatorWeight指示器高度
indicatorPadding底部指示器的Padding
indicator指示器decoration,例如边框等
indicatorSize指示器大小计算方式,TabBarIndicatorSize.label跟文字等宽,TabBarIndicatorSize.tab跟每个tab等宽
labelColor选中label颜色
labelStyle选中label的Style
labelPadding每个label的padding值
unselectedLabelColor未选中label颜色
unselectedLabelStyle未选中label的Style

7.3、Tabbar TabBarView实现类似头条顶部导航

7.3.1、混入SingleTickerProviderStateMixin

class _YAppbarState extends State<YAppbar> with SingleTickerProviderStateMixin{
    ...
}

7.3.2、定义TabController

class _YAppbarState extends State<YAppbar> with SingleTickerProviderStateMixin {
  late TabController _tabController;
  // 生命周期函数:当组件初始化时触发
  void initState() {
    super.initState();
    // 在生命周期中给_tabController赋值
    _tabController = TabController(length: 3, vsync: this);
  }
  ...
}

7.3.3、配置TabBar和TabBarView

完整代码

import 'package:flutter/material.dart';

class YAppbar extends StatefulWidget {
  const YAppbar({super.key});

  
  State<YAppbar> createState() => _YAppbarState();
}

class _YAppbarState extends State<YAppbar> with SingleTickerProviderStateMixin {
  late TabController _tabController;
  // 生命周期函数:当组件初始化时触发
  void initState() {
    super.initState();
    // 在生命周期中给_tabController赋值
    _tabController = TabController(length: 3, vsync: this);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Appbar'), //标题
        backgroundColor: Colors.blue, //导航的背景颜色
        centerTitle: true, //标题是否居中
        //左侧图标按钮
        leading: IconButton(
            onPressed: () {
              print('左侧按钮图标');
            },
            icon: const Icon(Icons.menu)),
        //右侧按钮图标
        actions: [
          IconButton(onPressed: () {}, icon: const Icon(Icons.search)),
          IconButton(onPressed: () {}, icon: const Icon(Icons.more_horiz))
        ],
        bottom: TabBar(
          controller: _tabController, //配置controller需要去掉TabBar的const
          tabs: const [
            Tab(
              child: Text('关注'),
            ),
            Tab(
              child: Text('热门'),
            ),
            Tab(
              child: Text('视频'),
            )
          ],
        ),
      ),
      body:
          //TabBarView的子元素需要和TabBar对应
          TabBarView(
        controller: _tabController, //TabBar和TabBarView都需要配置controller
        //可以自定义组件
        children: [
          ListView(
            children: const [
              ListTile(title: Text('我是关注')),
            ],
          ),
          ListView(
            children: const [
              ListTile(title: Text('我是热门')),
            ],
          ),
          ListView(
            children: const [
              ListTile(title: Text('我是视频')),
            ],
          )
        ],
      ),
    );
  }
}

7.4、BottomNavigationBar的页面中使用Tabbar

组件代码

PreferredSize可以改变appBar的高度

mport 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;
  
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      //PreferredSize可以配置appbar的高度
      appBar: PreferredSize(
        preferredSize: const Size.fromHeight(50), //自定义高度
        child: AppBar(
          elevation: 100, //底部阴影
          backgroundColor: Colors.white, //背景色
          //可以把TabBar放在title中
          title: TabBar(
            controller: _tabController,
            indicatorSize: TabBarIndicatorSize.label, //指示器和label等宽
            indicatorColor: Colors.black, //底部指示器颜色
            labelColor: Colors.red, //label选中的颜色
            unselectedLabelColor: Colors.black, //label未选择的颜色
            tabs: const [
              Tab(child: Text('关注')),
              Tab(child: Text('热门')),
              Tab(child: Text('推荐')),
            ],
          ),
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          ListView(
            children: const [Text('我是关注')],
          ),
          ListView(
            children: const [Text('我是热门')],
          ),
          ListView(
            children: const [Text('我是推荐')],
          ),
        ],
      ),
    );
  }
}

使用页面为上方案例5的HomePage()页面

7.5、自定义KeepAliveWrapper 缓存页面

AutomaticKeepAliveClientMixin 可以快速的实现页面缓存功能,但是通过混入的方式实现不是很优

雅, 所以我们有必要对AutomaticKeepAliveClientMixin 混入进行封装

例如:在页面A滑动到底部,切换页面B后再切回页面A会回到顶部 使用缓存页面后再切回页面A会停留在刚才的位置

  • 封装KeepAliveWrapper缓存组件

    import 'package:flutter/material.dart';
    
    class YKeepAliveWrapper extends StatefulWidget {
      const YKeepAliveWrapper(
          {Key? key,  this.child, this.keepAlive = true})
          : super(key: key);
      final Widget? child;
      final bool keepAlive;
      
      State<YKeepAliveWrapper> createState() => _YKeepAliveWrapperState();
    }
    
    class _YKeepAliveWrapperState extends State<YKeepAliveWrapper>
        with AutomaticKeepAliveClientMixin {
      
      Widget build(BuildContext context) {
        return widget.child!;
      }
    
      
      bool get wantKeepAlive => widget.keepAlive;
      
      void didUpdateWidget(covariant YKeepAliveWrapper oldWidget) {
        if (oldWidget.keepAlive != widget.keepAlive) {
    // keepAlive 状态需要更新,实现在 AutomaticKeepAliveClientMixin 中
          updateKeepAlive();
        }
      }
    }
    
  • 使用

import 'package:bangda/components/YStateComponents/YKeepAliveWrapper.dart';//引入组件
...
	body: TabBarView(
        controller: _tabController,
        children: [
          YKeepAliveWrapper(//使用缓存组件包裹后再次切换,保留状态
            child: ListView(
              children: getList(),
            ),
          ),
          ListView(
            children: const [Text('我是热门')],
          ),
          ListView(
            children: const [Text('我是推荐')],
          ),
        ],
      ),
...

7.6 、监听TabController改变事件

  • 方法1:在initState方法里使用addListener监听[推荐]

    
      void initState() {
        super.initState();
        _tabController = TabController(length: 3, vsync: this);
        // 监听tab切换
        _tabController.addListener(() {
          //print(_tabController.index); //会获取两次,不推荐1
          // 推荐判断一下,只触发一次
          if (_tabController.animation!.value == _tabController.index) {
            print(_tabController.index); //推荐
          }
        });
      }
    
  • 方法2[不推荐]

    TabBar(
        onTap: (value) {//只能监听点击切换,不能监听滑动切换
            print(value);
        },
        tabs: const [
            Tab(child: Text('关注')),
            Tab(child: Text('热门')),
            Tab(child: Text('推荐')),
        ],
    ),
    

7.7、页面销毁时销毁tabbar[优化]

// 页面销毁
  
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    // 页面销毁时把_tabController销毁掉
    _tabController.dispose();
  }

7.8、MaterialApp 去掉debug图标

return MaterialApp(
debugShowCheckedModeBanner:false , //去掉debug图标
home:Tabs(),
	...
);