Flutter常用组件

前言

本片文章仅供学习参考,如有不对的地方,请在评论区留言,我会立即改正。
如果你觉得有用的话,一键三连就是对我更新的最大动力!

模拟器输入中文

有些安卓模拟器不支持输入中文,就算将系统语言设置为简体中文之后,还是不能输入中文,所以,你可以将模拟器换成最新的,就可以输入中文了。
打开模拟器的setting->Stystem->Langugages &input->选择Langugages ->添加Langugages 之后,将简体中文挪到最上面就行了。
在这里插入图片描述

然后添加一个输入框,输入的语言也是简体中文

国际化

添加依赖

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

MaterialApp中添加下面代码:

localizationsDelegates: const [
            GlobalWidgetsLocalizations.delegate,
            GlobalMaterialLocalizations.delegate
          ],
          
supportedLocales: const [
	const Locale('en', 'US'), // 美国英语
	const Locale('zh', 'CN'), // 中文简体
    //其它Locales
],

LayoutBuilder 布局模式

使用LayoutBuilder可以根据约束大小进行显示子组件,constraints可以得到父盒子的约束是多少。

Center(
        child: LayoutBuilder(builder: (context, constraints) {
          if (constraints.maxHeight < 150) {
            return Container(
              color: Colors.amber,
              width: 100,
              height: 100,
            );
          } else if (constraints.maxHeight > 350) {
            print(constraints);
            return Container(
              color: Colors.deepOrange,
              width: 300,
              height: 300,
              child: LayoutBuilder(builder: (context, constraints) {
                print(constraints);
                return Column(
                  children: [
                    AnimatedContainer(
                      duration: Duration(seconds: 1),
                      width: 150,
                      height: 150,
                      color: Colors.green,
                    ),
                  ],
                );
              }),
            );
          }
          return Container(
            color: Colors.cyan,
            width: 200,
            height: 200,
          );
        }),
      )

效果如下:
在这里插入图片描述在这里插入图片描述

Material

该组件可以为他的子组件设置阴影效果等属性。

 const Material({
    Key? key,
    this.type = MaterialType.canvas,
    this.elevation = 0.0,
    this.color,
    this.shadowColor,
    this.textStyle,
    this.borderRadius,
    this.shape,
    this.borderOnForeground = true,
    this.clipBehavior = Clip.none,
    this.animationDuration = kThemeChangeDuration,
    this.child,
Material(
  elevation: 10,
  shadowColor: Colors.green,
  child: Container(
    width: 150,
    height: 150,
    color: Colors.green,
  ),
)

效果如下:
在这里插入图片描述在这里插入图片描述

Snack 消息弹窗

使用ScaffoldMessenger.of(context).showSnackBar()进行显示消息弹窗。

SnackBar

ElevatedButton(
  onPressed: () {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10)),
        content: Text("这是Scaffold展示的内容"),
        backgroundColor: Theme.of(context).primaryColor,
        duration: Duration(seconds: 1),
        behavior: SnackBarBehavior.floating,
      ),
    );
  },
  child: Text("SnackBar"),
)

SnackBarAction

ElevatedButton(
  onPressed: () {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10)),
        content: Text("这是Scaffold展示的内容"),
        backgroundColor: Theme.of(context).primaryColor,
        duration: Duration(seconds: 1),
        action: SnackBarAction(
          onPressed: () {
            Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (context) => Scaffold(
                          appBar: AppBar(),
                        )));
          },
          label: '跳转',
          textColor: Colors.white,
        ),
        behavior: SnackBarBehavior.floating,
      ),
    );
  },
  child: Text("SnackBarAction"),
)

效果如下:
在这里插入图片描述

对话框

AlertDialog

使用它需要用 showDiglog组件将它显示出来,并且barrierDismissible设置为false,点击周围区域不能退出,只能使用路由进行退出。
showDialog.then()可以将路由返回的值取出。

ElevatedButton(
    onPressed: () {
      showDialog(
        context: context,
        builder: (context) {
          return AlertDialog(
            title: const Text("提示"),
            content: const Text("确定删除吗?"),
            actions: [
              ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).pop("确定");
                  },
                  child: const Text("确定")),
              ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).pop("取消");
                  },
                  child: const Text("取消")),
            ],
          );
        },
        // 点击周围区域不能退出
        barrierDismissible: false,
      ).then(
      // 使用异步获取数据的方法将按钮返回的值取出
        (value) => print(value),
      );
    },
    child: const Text("AlertDialog"))

SimpleDialog

SimpleDialog需要和SimpleDialogOption一起使用,使用then可以将选择的结果返回。

ElevatedButton(
    onPressed: () {
      showDialog(
        barrierDismissible:false,
          context: context,
          builder: (context) => SimpleDialog(
                title: Text("请选择科目"),
                children: [
                  SimpleDialogOption(
                      onPressed: () =>
                          Navigator.of(context).pop("语文"),
                      child: Text("语文")),
                  SimpleDialogOption(
                      onPressed: () =>
                          Navigator.of(context).pop("数学"),
                      child: Text("数学")),
                  SimpleDialogOption(
                      onPressed: () =>
                          Navigator.of(context).pop("英语"),
                      child: Text("英语")),
                ],
              )).then((value) => print(value));
    },
    child: Text("SimpleDialog"))

Dialog

使用它你就可以自定义弹框。

ElevatedButton(
    onPressed: () {
      showDialog(
        context: context,
        builder: (context) => Dialog(
          child: Container(
              padding: EdgeInsets.all(30),
              height: MediaQuery.of(context).size.width,
              child: ListView(
                children: [
                  ...List.generate(
                      20,
                      (index) => GestureDetector(
                            child: Card(
                              child: Text(index.toString()),
                            ),
                            onTap: () => Navigator.of(context)
                                .pop("返回的数字:${index}"),
                          ))
                ],
              )),
        ),
      ).then((value) => print(value));
    },
    child: Text("Dialog"))

效果如下:
在这里插入图片描述

ListView 滚动列表布局

ListView

Container(
  padding: EdgeInsets.all(50),
  height: size.height*0.5,
  color: Colors.amber,
  child: ListView(
    // 反转列表
    reverse: true,
    // 对其方向
    scrollDirection: Axis.horizontal,
    itemExtent: 100,
    children: [
      ...List.generate(
          10,
              (index) => Card(
            child: Center(
              child: Text(index.toString()),
            ),
          ))
    ],
  ),
)

ListView.builder

Container(
  color: Colors.black12,
  padding: EdgeInsets.all(50),
  height: size.height,
  child: ListView.builder(
    itemBuilder: (context, index) => Container(
      margin: EdgeInsets.all(10),
      color: Colors.primaries[index],
      height: 30,
    ),
    itemCount: 18,
  ),
)

ListView.custom

Container(
  color: Colors.cyan,
  padding: EdgeInsets.all(50),
  height: size.height,
  child: ListView.custom(
    childrenDelegate: SliverChildBuilderDelegate((context, index) {
      return Container(
        color: Colors.primaries[index],
        height: 30,
        margin: EdgeInsets.all(10),
      );
    }, childCount: 18),
  ),
)

ListView.separated

Container(
  padding: const EdgeInsets.all(50),
  color: Colors.green,
  height: size.height,
  child: ListView.separated(
      itemBuilder: (context, index) => Container(
            color: Colors.primaries[index],
            margin: EdgeInsets.all(10),
            height: 30,
          ),
      separatorBuilder: (context, index) => const Divider(
            thickness: 0.5,
          ),
      itemCount: 18),
)

样式如下:
在这里插入图片描述小结:

  1. 使用ListVew可以制作多个可以滚动的列表,并且它的操作方式和GirdView类似,因为他们都是继承BoxScrollView

返回顶部

class _ListView_DemoState extends State<ListView_Demo> {
  // 添加控制器
  ScrollController _scrollController = ScrollController();
  bool showToUp = false;

  @override
  void initState() {
    super.initState();
    // 添加监听控制器
    _scrollController.addListener(() {
      print(_scrollController.offset.toInt().ceil());
      if (_scrollController.offset < 200 && showToUp) {
        // 当没有滑到200的时候,返回顶部按钮不显示
        setState(() {
          showToUp = false;
        });
      } else if (_scrollController.offset > 200 && showToUp == false) {
        // 当滑到200的时候,讲返回顶部按钮显示出来
        setState(() {
          showToUp = true;
        });
      }
    });
  }

  // 防止内存泄漏
  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _scrollController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;

    return Scaffold(
      floatingActionButton: showToUp == false
          ? null
          : FloatingActionButton(
              onPressed: () {
                setState(() {
                  // 返回顶部
                  // _scrollController.jumpTo(0.0);
                  // 添加返回顶部动画
                  _scrollController.animateTo(.0,
                      duration: Duration(milliseconds: 200),
                      curve: Curves.ease);
                });
              },
              child: Icon(Icons.arrow_upward),
            ),
      appBar: AppBar(
        title: Text("ListView"),
      ),
      body: Container(
        padding: EdgeInsets.all(10),
        // width: size.width,
        height: size.height,
        color: Colors.amber,
        child: ListView(
          key: PageStorageKey("a"),
          controller: _scrollController,
          itemExtent: 100,
          children: [
            ...List.generate(
                10,
                (index) => Card(
                      child: Center(
                        child: Text(index.toString()),
                      ),
                    ))
          ],
        ),
      ),
    );
  }
}

样式如下:
在这里插入图片描述

GridVeiw 滚动网格布局

GridView是一个可以滚动的网格布局,可以用在多个格子布局形势下。

使用physics: NeverScrollableScrollPhysics(),可以将它滚动取消。缺点:但是如果父盒子设置了高度,就不能显示不能滚动的地方。

GridView

Container(
  color: Colors.blueGrey,
  padding: const EdgeInsets.all(10),
  width: size.width,
  height: size.width,
  child: GridView(
    gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
      mainAxisSpacing: 3,
      crossAxisSpacing: 3,
      crossAxisCount: 3,
      // 元素在主轴方向的长度
      mainAxisExtent: 90,
    ),
    children: [
      ...List.generate(
          18,
          (index) => Container(
                width: 100, // 无用
                height: 100,
                color: Colors.primaries[index],
              ))
    ],
  ),
)

GridView.count

Container(
  // width: MediaQuery.of(context).size.width*0.6,
  // height: MediaQuery.of(context).size.height*0.6,
  color: Colors.amber,
  child: GridView.count(
    controller: ScrollController(),
    // 控制器
    physics: NeverScrollableScrollPhysics(),
    // 设置不能滚动
    crossAxisCount: 3,
    // 次轴个数
    shrinkWrap: true,
    // 收缩包裹范围
    mainAxisSpacing: 2,
    crossAxisSpacing: 2,
    // 间隙
    padding: EdgeInsets.all(10),
    children: List.generate(
        9,
        (index) => ElevatedButton(
              onPressed: () {},
              child: Text(
                '${index + 1}',
                style: TextStyle(fontSize: 24),
              ),
            )),
  ),
)

GridView.builder

Container(
  width: size.width,
  padding: EdgeInsets.all(10),
  height: size.height * 0.6, // 需要设置高度,但是当不能滚动的时候,就不能显示需要滚动部分内容。
  child: GridView.builder(
    // 设置不能滚动
    physics: NeverScrollableScrollPhysics(),
    // 设置网格的代表
    gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisSpacing: 3,
      mainAxisSpacing: 3, // 间隙
      mainAxisExtent: 50, // 设置范围
      crossAxisCount: 3,
    ),
    itemBuilder: (context, index) {
      return InkWell(
        onTap: () {},
        child: Container(
          color: Colors.primaries[index],
        ),
      );
    },
    itemCount: 18, // 个数
  ),
)

GridView.custom

Container(
  width: size.width,
  height: size.width,
  color: Colors.green,
  padding: const EdgeInsets.all(10),
  child: GridView.custom(
    physics: const NeverScrollableScrollPhysics(),
    gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        mainAxisSpacing: 3, crossAxisSpacing: 3, crossAxisCount: 3),
    childrenDelegate: SliverChildBuilderDelegate((context, index) {
      return Container(
        color: Colors.primaries[index],
      );
    }, childCount: 9),
  ),
)

GridView.extent

Container(
  padding: const EdgeInsets.all(10),
  width: size.width,
  height: size.width,
  color: Colors.black26,
  // 用于创建指定宽高
  child: GridView.extent(
    maxCrossAxisExtent: size.width / 3,
    mainAxisSpacing: 3,
    crossAxisSpacing: 3,
    // physics: NeverScrollableScrollPhysics(),
    children: [
      ...List.generate(
          90,
          (index) => Container(
                width: 100, // 无作用
                color: Colors.primaries[index % 18],
              ))
    ],
  ),
)

效果如下:
在这里插入图片描述小结

  1. 对于GridViewGridView.customGridView.buildergridDelegate属性,需要使用SliverGridDelegateWithFixedCrossAxisCount进行设置次轴显示的个数等属性;
  2. 他三还有childrenDelegate属性,SliverChildBuilderDelegate((context, index) { return Container();}, childCount: 9)就是设置需要显示的组件及个数,GridView.bilder则使用的是itemBuilder,同理。
  3. 如果设置了SliverGridDelegateWithFixedCrossAxisCountGridView,它里面就可以设置格子间隙等属性,则只能在外面设置间隙…

PageView 页面视图布局

可以用于布局多个页面的布局,适合做轮播图,多个页面左右、上下滑动效果。

PageView

Container(
  padding: EdgeInsets.all(10),
  height: size.width * 0.5,
  width: size.width * 0.5,
  color: Colors.black12,
  child: PageView(
    // 回调方法
    onPageChanged: (value) {
      print(value);
    },
    controller: PageController(
      // 不占满全部
      viewportFraction: 0.8,
      // 初始页面
      initialPage: 2,
    ),
    scrollDirection: Axis.vertical,
    children: [
      ...List.generate(
          18,
          (index) => Container(
                child: Center(child: Text(index.toString())),
                color: Colors.primaries[index],
              ))
    ],
  ),
)

PageView.builder

Container(
  // width: size.width,
  height: size.width * 0.5,
  child: PageView.builder(
      controller: PageController(viewportFraction: 0.8),
      // 跳动物理效果
      physics: BouncingScrollPhysics(),
      itemCount: 18,
      itemBuilder: (context, index) => Container(
            height: double.maxFinite,
            width: size.width * 0.9,
            color: Colors.primaries[index],
          )),
)

PageView.custom

Container(
  width: size.width,
  height: size.width * 0.5,
  child: PageView.custom(
    childrenDelegate: SliverChildBuilderDelegate(
      (context, index) {
        return Container(
          color: Colors.primaries[index],
        );
      },
      childCount: 18
    ),
  ),
)

效果如下:
在这里插入图片描述
小结: 使用` physics: BouncingScrollPhysics()可以设置弹性效果。

Drawer 侧边页、bottomNavigationBar 底部导航栏、persistentFooterButtons 持续的页脚按钮

// ignore_for_file: unused_import, file_names, prefer_const_constructors, prefer_const_literals_to_create_immutables, use_key_in_widget_constructors, deprecated_member_use

import 'package:flutter/material.dart';

class DrawerPage extends StatelessWidget {
  var selector = 0;
  List<String> _list = ["游泳馆", "羽毛球场", "篮球场", "拳击场"];

  List<Widget> _result() {
    return _list
        .map((itme) => ListTile(
              title: Text(itme),
              leading: Icon(Icons.sports_soccer),
              subtitle: Text("挥洒汗水逐梦的土地"),
              onTap: () {},
            ))
        .toList();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("DrawerPage"),
        // 添加图标按钮,并为它绑定点击事件,用于打开Drawer
        leading: Builder(builder: (BuildContext context){
          return IconButton(icon: Icon(Icons.list), onPressed: () { 
            Scaffold.of(context).openDrawer();
           },);
        },),
        elevation: 15.0,
      ),
      
      //  底部导航栏
      bottomNavigationBar: BottomNavigationBar(
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
              icon: Icon(
                Icons.add_a_photo,
              ),
              title: Text("照相")),
          BottomNavigationBarItem(
              icon: Icon(
                Icons.center_focus_strong,
              ),
              title: Text("扫码")),
          BottomNavigationBarItem(
              icon: Icon(Icons.safety_divider), title: Text("pay"))
        ],
        // 获取底部 BottomNavigationBarItem的索引下标
        onTap: (value) {
          selector = value;
          print(value);
        },
        // 设置底部选择,结合点击事件可以切换选项
        currentIndex: selector,
      ),
      
      // 左侧侧边栏
      drawer: Drawer(
        // 添加listView
        child: ListView(
          children:
              // 使用遍历方式将数据取出放入children,注意这个这个函数是widget数组,不需要加中括号
              // _result(),
            [
              UserAccountsDrawerHeader(accountName: Text("泸州彭于晏"), accountEmail: Text("Ciy@163.com"),currentAccountPicture: CircleAvatar(backgroundImage: AssetImage("images/girl.jpg"),),onDetailsPressed: ()=>print("你点击用户名片"),),
              ListTile(title: Text("学校"),leading: Icon(Icons.school),subtitle: Text("充满师生情怀的圣地"),onTap: ()=>print("你点击了学校"),),
              ListTile(title: Text("食堂"),leading: Icon(Icons.dining),subtitle: Text("皆是学生恋爱的宝地"),onTap: ()=>print("你点击了食堂"),),
              ListTile(title: Text("操场"),leading: Icon(Icons.sports_soccer),subtitle: Text("挥洒汗水逐梦的土地"),onTap: (){},),
          ]
        ),
      ),
      
      // 浮动按钮
      floatingActionButton: FloatingActionButton(onPressed: () { print("你点击了打印按钮"); },
      child: Icon(Icons.print),
      // 长按提示文本
      tooltip: '打印文档',
      // 设置前景色,默认是白色
      foregroundColor: Colors.red,
      // 设置背景色,默认是洋红色
      backgroundColor: Colors.teal,
      // 设置焦点颜色
      focusColor: Colors.orange,
      // 设置点击时的阴影颜色
      highlightElevation: 30,
      // 设置没有点击时的阴影颜色
      elevation: 30,
      // 设置图标为mini类型
      mini: true,
      ),
      
      // 设置浮动按钮的显示位置
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
      
      // 允许底部Button,这里面可以添加多个Button,使用的不多
      persistentFooterButtons: <Widget>[
        FlatButton(onPressed: (){print("确定");}, child: Text("确定")),
        TextButton(onPressed: (){print("取消");}, child: Text("取消"))
      ],
    );
  }
}

在这里插入图片描述

TextFeilfdController

用于控制输入框中的内容,包括想输入框中赋值和从输入框中取值,该属性值为TextEditingController类型。

import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  // 定义初始值
  String info ="初始值";
  // 定义一个controller对象之后,整个类里面都可以使用,并且将它与TextFeild的controller进行绑定
  TextEditingController controller1 = TextEditingController();
  // 创建一个方法并重写setState方法,告诉脚手架进行重写绘制该界面
  void getValue(){
    setState(() {
      // 取出controller1的值赋值给info变量,进行传递给Text进行显示
      info = controller1.text;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          "ContorllerDemo",
        ),
      ),
      body: Column(
        children: [
          TextField(controller: controller1,),
          Text(info),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: Text("确定"),
        onPressed: () {
          getValue();
        },
      ),
    );
  }
}

在这里插入图片描述

Button

按钮的属性大致都一样。

// 漂浮按钮
RaisedButton(
            onPressed: () {}, // 单击事件,
            child: Text("漂浮按钮"), // 设置按钮文本
            textColor: Colors.blue, // 设置按钮颜色
            highlightColor: Colors.lightGreen[100], // 设置长按按钮颜色
            splashColor: Colors.orangeAccent, // 设置水波纹颜色
            colorBrightness: Brightness.light, //设置高亮显示,默认不高亮
            elevation: 10,  // 设置按钮阴影高度
            highlightElevation: 50,// 设置高亮时的阴影颜色
            // 带水波纹高亮变化的回调函数,按下为true,松开
            onHighlightChanged: (value) {
              print(value);
            }, // 按钮名称
          ),
class buttonPage extends StatelessWidget {
  const buttonPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: [
          RaisedButton(
            // 设置漂浮按钮
            onPressed: () {}, // 单击事件,
            child: Text("漂浮按钮"), // 设置按钮文本
            textColor: Colors.blue, // 设置按钮颜色
            highlightColor: Colors.lightGreen[100], // 设置长按按钮颜色
            splashColor: Colors.orangeAccent, // 设置水波纹颜色
            colorBrightness: Brightness.light, //设置高亮显示,默认不高亮
            elevation: 10, // 设置按钮阴影高度
            highlightElevation: 50, // 设置高亮时的阴影颜色
            // 带水波纹高亮变化的回调函数,按下为true,松开
            onHighlightChanged: (value) {
              print(value);
            }, // 按钮名称
          ),
          // 漂浮按钮
          FlatButton(
            onPressed: () {}, // 设置漂浮按钮
            child: Text("扁平按钮"), // 点击事件
            textColor: Colors.deepOrange, //文本颜色
            height: 50, // 按钮高度
            color: Colors.lightGreen[100], //设置背景颜色
          ),

          //带图标的漂浮按钮
          FlatButton.icon(
            // 带图标的漂浮按钮
            onPressed: () {},
            icon: Icon(Icons.add),
            label: Text("添加"),
            color: Colors.blueGrey,
            textColor: Colors.white,
          ),

          // 边框按钮
          OutlineButton(
            // 有边框不带阴影的按钮,用的比较少
            // 单击事件
            onPressed: () {
              print("你点击了outlineButton");
            },
            // 设置长按事件
            onLongPress: () {
              print("你长按了outlineButton");
            },
            child: Column(
              children: [Icon(Icons.input), Icon(Icons.outbox)],
            ),
            color: Colors.green,
            focusColor: Colors.black45,
            textColor: Colors.green,
            padding: const EdgeInsets.all(30.0), // 设置按钮的内边距
            borderSide: BorderSide(color: Colors.red),
          ),

          // 图标按钮
          IconButton(
            onPressed: () {}, // 按钮点击事件
            icon: Icon(Icons.home), // 按钮图标
            color: Colors.green[200], // 按钮颜色
            iconSize: 30.0, // 按钮大小
            padding: const EdgeInsets.all(30.0), // 按钮内边距
            tooltip: "home", // 长按显示提示文本
          ),

          // 文本按钮
          TextButton(onPressed: () {}, child: Text("文本按钮"),)
        ],
      ),
    );
  }
}

在这里插入图片描述

Slider 滑块组件

他是一个滑块的组件,可以滑动,懂我意思吧!

这是原始的样子:

Slider(value: 0, onChanged: (value) {}),

在这里插入图片描述这是设置过样式Slider

class _SliderDemoState extends State<SliderDemo> {
// 设置回调值为零,每次拖动就会改变这个值
  double _value = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Slider"),
        ),
        body: Container(
          child: Column(
            children: [
              // 不能滑动
              Slider(value: 0, onChanged: (value) {}),
              Slider(
                // 当拖动的时候,确定当前的圆点的位置
                value: _value,
                
                // 标签,需要和divisions连用
                label: "${_value}",
                
                // 设置刻度,将这个其分为四份
                divisions: 4,
                
                // 设置最小值
                min: 0,
                
                // 设置最大值
                max: 100,
                
                // 设置已滑动过的颜色
                activeColor: Colors.teal,
                
                // 设置还没有滑到的颜色
                inactiveColor: Colors.redAccent,
                
                // 设置圆点的颜色
                thumbColor: Colors.blueGrey,
                
                // 回调方法,用于返回当前圆点的位置
                onChanged: (value) {
                  setState(() {
                    _value = value;
                  });
                },
              ),
            ],
          ),
        ));
  }
}

在这里插入图片描述他的源码如下:

const Slider({
    Key? key,
    required this.value,
    required this.onChanged,
    this.onChangeStart,
    this.onChangeEnd,
    this.min = 0.0,
    this.max = 1.0,
    this.divisions,
    this.label,
    this.activeColor,
    this.inactiveColor,
    this.thumbColor,
    this.mouseCursor,
    this.semanticFormatterCallback,
    this.focusNode,
    this.autofocus = false,
  }) : _sliderType = _SliderType.material,
       assert(value != null),
       assert(min != null),
       assert(max != null),
       assert(min <= max),
       assert(value >= min && value <= max),
       assert(divisions == null || divisions > 0),
       super(key: key);

Switch 开关组件

可以实现开和关的组件。
原始样子:

Switch(value: false, onChanged: (v){}),

在这里插入图片描述用回调改变他的值之后就可以实现开关效果:

Text("当前开关为:${_value == false ? "关闭状态" : "开启状态"}"),
Switch(
    // 设置当前开关是否 开启/关闭 状态
    value: _value,
    // 回调当前是否点击了开关
    onChanged: (v) {
      setState(() {
        _value = v;
      });
    }),

在这里插入图片描述他的源码如下:

const Switch.adaptive({
    Key? key,
    required this.value,
    required this.onChanged,
    this.activeColor,
    this.activeTrackColor,
    this.inactiveThumbColor,
    this.inactiveTrackColor,
    this.activeThumbImage,
    this.onActiveThumbImageError,
    this.inactiveThumbImage,
    this.onInactiveThumbImageError,
    this.materialTapTargetSize,
    this.thumbColor,
    this.trackColor,
    this.dragStartBehavior = DragStartBehavior.start,
    this.mouseCursor,
    this.focusColor,
    this.hoverColor,
    this.overlayColor,
    this.splashRadius,
    this.focusNode,
    this.autofocus = false,
  })  : assert(autofocus != null),
        assert(activeThumbImage != null || onActiveThumbImageError == null),
        assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
        _switchType = _SwitchType.adaptive,
        super(key: key);

再来个苹果风格的开关

CupertinoSwitch(
    value: _value,
    onChanged: (v) {
      setState(() {
        _value = v;
      });
    })

样式如下:
在这里插入图片描述

SwitchListTile 开关条

可以设置标题等信息的开关条,
这个可以设置多个。

SwitchListTile(
     value: _value,
     title: Text("这是标题"),
     subtitle: Text("这是副标题"),
     onChanged: (v) {
       setState(() {
         _value = v;
       });
     }),

样式如下:
在这里插入图片描述设置样式的标题

SwitchListTile(
    // 主标题
    title: Text("这是标题"),
    // 子标题
    subtitle: Text("这是副标题"),
    // 开关的状态
    value: _value,
    // 标题颜色
    tileColor: Color(0xffededed),
    // 回调方法
    onChanged: (v) {
      setState(() {
        _value = v;
      });
    })

样式如下:
在这里插入图片描述
源码如下:

  const SwitchListTile({
    Key? key,
    required this.value,
    required this.onChanged,
    this.tileColor,
    this.activeColor,
    this.activeTrackColor,
    this.inactiveThumbColor,
    this.inactiveTrackColor,
    this.activeThumbImage,
    this.inactiveThumbImage,
    this.title,
    this.subtitle,
    this.isThreeLine = false,
    this.dense,
    this.contentPadding,
    this.secondary,
    this.selected = false,
    this.autofocus = false,
    this.controlAffinity = ListTileControlAffinity.platform,
    this.shape,
    this.selectedTileColor,
  }) : _switchListTileType = _SwitchListTileType.material,
       assert(value != null),
       assert(isThreeLine != null),
       assert(!isThreeLine || subtitle != null),
       assert(selected != null),
       assert(autofocus != null),
       super(key: key);

进度条

可以用于显示网络的加载状况和文件下载等进度,主要用于显示进度。

线性进度条
默认样式如下:

LinearProgressIndicator(),

样式如下:
在这里插入图片描述
可以设置他的样式,但是一旦设置了他的value之后就不能动了,可以借助计时器(Timer)改变value的值,从而达到动起来的效果。

LinearProgressIndicator(
    value: 0.8,
    color: Colors.blueGrey,
    backgroundColor: Colors.redAccent,
  )

样式如下:
在这里插入图片描述
源码如下:

  const LinearProgressIndicator({
    Key? key,
    double? value,
    Color? backgroundColor,
    Color? color,
    Animation<Color?>? valueColor,
    this.minHeight,
    String? semanticsLabel,
    String? semanticsValue,
  }) : assert(minHeight == null || minHeight > 0),
       super(
         key: key,
         value: value,
         backgroundColor: backgroundColor,
         color: color,
         valueColor: valueColor,
         semanticsLabel: semanticsLabel,
         semanticsValue: semanticsValue,
       );

圆形进度指示器
和上面的线性进度指示器的用法一样。
默认样式如下:

// 默认圆形进度指示器
CircularProgressIndicator(),

// 自适应圆形进度指示器
CircularProgressIndicator.adaptive(value: 0.5,)

样式如下:
在这里插入图片描述刷新进度指示器
和上面的线性进度指示器的用法一样。
默认样式如下:

RefreshProgressIndicator(),

在这里插入图片描述来个苹果的进度指示器:

CupertinoActivityIndicator()

在这里插入图片描述

Image 图片组件

可以放置图片的组件
在这之前,如果使用的是静态图片,则需要在pubspec.yaml文件中进行配置,并且需要这个项目的根目录下创建一个放置图片的文件夹。

像这个样子:
在这里插入图片描述pubspec.yaml文件中进行配置:

// 这是直接添加整个文件夹下面的图片
assets:
 - images/

// 添加单张图片
assets:
 - images/1.png
 - images/2.png

注意:在添加之后,需要点击Android studio右上角的Pub get进行添加依赖:在这里插入图片描述图片组件

使用本地图片资源,即使用asset方法:

Image.asset("images/1.png")

在这里插入图片描述可以设置他的样式:

Wrap(
        children: [
          Container(
            alignment: Alignment.center,
            width: 100,
            height: 100,
            child: Image.asset("images/1.png"),
          ),
          Container(
            alignment: Alignment.center,
            width: 100,
            height: 100,
            child: Image.asset(
              "images/1.png",
              fit: BoxFit.cover,
            ),
          ),
          Container(
            alignment: Alignment.center,
            width: 100,
            height: 100,
            child: Image.asset(
              "images/1.png",
              // 设置填充方式
              fit: BoxFit.fitWidth,
              // 设置宽、高
              width: 50,
              height: 50,
              // 填充方式
              repeat: ImageRepeat.repeat,
            ),
          ),
        ],
      )

在这里插入图片描述
使用网络图片
使用网络图片的前提是要有网络,不然一切都是扯淡,
用法和上面的静态图片的用法相同。

Container(
  alignment: Alignment.center,
  width: 100,
  height: 100,
  child: Image.network(
    "https://pic.netbian.com/uploads/allimg/220131/012219-16435633391d32.jpg",
    // 设置填充方式
    fit: BoxFit.fitWidth,
    // 设置宽、高

    // 填充方式
    repeat: ImageRepeat.repeat,
  ),
),
Container(
	alignment: Alignment.center,
	width: 100,
	height: 100,
	child: Image.network(
	  "https://pic.netbian.com/uploads/allimg/220205/002942-1643992182534d.jpg",
	  // 设置填充方式
	  fit: BoxFit.fitWidth,
	  // 设置宽、高
	
	  // 填充方式
	  repeat: ImageRepeat.repeat,
	),
)

样式如下:
在这里插入图片描述
还有其他加载图片的方式,我就只举这两种加载方式!
源码如下:

  const Image({
    Key? key,
    required this.image,
    this.frameBuilder,
    this.loadingBuilder,
    this.errorBuilder,
    this.semanticLabel,
    this.excludeFromSemantics = false,
    this.width,
    this.height,
    this.color,
    this.opacity,
    this.colorBlendMode,
    this.fit,
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
    this.centerSlice,
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
    this.isAntiAlias = false,
    this.filterQuality = FilterQuality.low,
  }) : assert(image != null),
       assert(alignment != null),
       assert(repeat != null),
       assert(filterQuality != null),
       assert(matchTextDirection != null),
       assert(isAntiAlias != null),
       super(key: key);

图标

使用Icon组件进行实现:

// 图标
Icon(Icons.add)

样式如下:
在这里插入图片描述
可以设置他的样式:

Icon(Icons.add,size: 35,color: Colors.blueGrey,),

样式如下:
在这里插入图片描述

Row和Column组件

Row
Row组件是多孩子组件,放置在其中的组件沿水平方向进行分布。
他只是布局组件,里面没有其他组件相当于无用,懂我意思吧!

Container(
  decoration: BoxDecoration(border: Border.all(width: 1)),
  child: Row(
    children: [
      Container(
        width: 50,
        height: 50,
        color: Colors.primaries[0],
      ),
      Container(
        width: 50,
        height: 50,
        color: Colors.primaries[1],
      ),
      Container(
        width: 50,
        height: 50,
        color: Colors.primaries[2],
      ),
    ],
  ),
)

在这里插入图片描述

设置他的可以设置他的对其方式,对于水平布局组件来说,它只有沿main轴进行分布,就是X轴。
他又6种对齐方式,如下所示:

// 默认
mainAxisAlignment: MainAxisAlignment.start,

// 末尾对齐
mainAxisAlignment: MainAxisAlignment.end,

// 居中对其
mainAxisAlignment: MainAxisAlignment.center,

// 周围有空间
mainAxisAlignment: MainAxisAlignment.spaceAround,

// 将剩余的空间均匀分布
mainAxisAlignment: MainAxisAlignment.spaceBetween,

// 周围的空间均匀
mainAxisAlignment: MainAxisAlignment.spaceEvenly,

最后就是这个样子:
在这里插入图片描述他的源码如下:

  Row({
    Key? key,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    MainAxisSize mainAxisSize = MainAxisSize.max,
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
    TextDirection? textDirection,
    VerticalDirection verticalDirection = VerticalDirection.down,
    TextBaseline? textBaseline, // NO DEFAULT: we don't know what the text's baseline should be
    List<Widget> children = const <Widget>[],
  }) : super(
    children: children,
    key: key,
    direction: Axis.horizontal,
    mainAxisAlignment: mainAxisAlignment,
    mainAxisSize: mainAxisSize,
    crossAxisAlignment: crossAxisAlignment,
    textDirection: textDirection,
    verticalDirection: verticalDirection,
    textBaseline: textBaseline,
  );

Column
垂直方向分布的组件,放置在它里面的组件可以垂直分布。

Container(
  child: Column(
    children: [
      Container(
        width: 50,
        height: 50,
        color: Colors.primaries[0],
      ),
      Container(
        width: 50,
        height: 50,
        color: Colors.primaries[1],
      ),
      Container(
        width: 50,
        height: 50,
        color: Colors.primaries[2],
      ),
    ],
  ),
)

在这里插入图片描述

Stack 层叠组件

放置在该组件中的组件呈现叠加效果。


LayoutBuilder 适配屏幕

共两个参数,constrains可以获得屏幕的宽(高)最大值、最小值…

LayoutBuilder(builder: (context, constrains) {
  if (constrains.maxWidth <600) {
    return Container(
      margin: EdgeInsets.only(top: 20),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          colorBlock(Colors.primaries[1], "优雅红"),
          colorBlock(Colors.primaries[2], "葡萄紫"),
        ],
      ),
    );
  } else {
    return Container(
      margin: EdgeInsets.only(top: 20),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          colorBlock(Colors.primaries[1], "优雅红"),
          colorBlock(Colors.primaries[2], "葡萄紫"),
          colorBlock(Colors.primaries[3], "靛蓝紫"),
          colorBlock(Colors.primaries[4], "笔墨蓝"),
        ],
      ),
    );
  }
})

在这里插入图片描述

shared_preferences 简单数据持久化

添加依赖

flutter pub add shared_preferences

存储、取出数据

分别使用setStringsetIntsetDoublesetBoolsetStringList进行设置需要存储的数据。
取数据则用getIntgetDoublegetBoolgetBoolgetStringgetStringListgetKeys,以及用get去任意键的值,它的是Object类型。
使用clear可以清空所用键的值。
使用containsKey查找是否存在该键。

// 设置值
	// 用来存放得到对应键之后的值,并在组件中进行显示
  String aaa = "";
  
  _setMyData() async {
    SharedPreferences preferences = await SharedPreferences.getInstance();
    // 下面是设置各个键名和对应的值
    await preferences.setInt("mySetInt", 1);
    await preferences.setDouble("mySetDouble", 1.1);
    await preferences.setBool("mySetBool", false);
    await preferences.setString("mySetString", "string");
    await preferences.setStringList("mySetStringList", ["string"]);
  }
  
// 得到值
  _getMyData() async {
    SharedPreferences preferences = await SharedPreferences.getInstance();
    // 得到int值
    int myGetInt = preferences.getInt("mySetInt") ?? 2;
    // 得到Double值
    double myGetDouble = preferences.getDouble("mySetDouble") ?? 2.2;
    // 得到bool值
    bool myGetBool = preferences.getBool("mySetBool") ?? true;
    // 得到String值
    String myGetString = preferences.getString("mySetString") ?? "空值";
    // 得到list<String>值
    List<String> myGetListString =
        preferences.getStringList("mySetStringList") ?? ["空值"];
    // 得到全部键名
    Set<String> myGetKey = preferences.getKeys();
    // 得到任意值键的值
    Object object = preferences.get("mySetStringList");

    print("得到int数据:${myGetInt}");
    print("得到Double数据:${myGetDouble}");
    print("得到bool数据:${myGetBool}");
    print("得到String数据:${myGetString}");
    print("得到ListString数据:${myGetListString}");
    print("得到Key数据:${myGetKey}");
    print(object);
    
    // 得到数据之后重绘页面
    setState(() {
      aaa = myGetString;
    });
    print(aaa);
		
    // 清空键对应的 值
    preferences.clear();
   	print("清空数据之后显示的String值、int值:${myGetString}${myGetInt}");
   	
    // 查找是否存在该 键
    bool flag = preferences.containsKey("aaa");
    print("是否找到:${flag}");
  }

效果如下:
在这里插入图片描述

path_provider 文件路径

命令行中添加path_provider:

flutter pub add path_provider

获取临时文件目录

首先,获取文件路径是个异步操作,需要使用异步方法,

创建一个方法:

getTemporaryDirectory()

void getTDir() {
    // 放置目录的返回值
    String tDir = '';
    
    // 获取临时目录
    Future<Directory> tempDir = getTemporaryDirectory();
    
    // 所用them 来得到路径
    tempDir.then((value) {
      tDir = value.path;
      print(tDir);
    });
  }

结果如下:

I/flutter ( 6737): /data/user/0/com.example.testdemo/cache

获取文档目录

getApplicationDocumentsDirectory()

  // 获取文档目录
  void getDDir() {
    // 放置目录的返回值
    String tDir = '';
    
    // 获取应用文档目录
    Future<Directory> temp = getApplicationDocumentsDirectory();
    temp.then((value) {
      tDir = value.path;
      print(tDir);
    });
  }

结果如下:

I/flutter ( 6737): /data/user/0/com.example.testdemo/app_flutter

获取外存目录

getExternalStorageDirectory()

  // 获取文档目录
  void getESDir() {
    // 放置目录的返回值
    String? tDir = '';
    
    // 获取外存目录
    Future<Directory?> temp = getExternalStorageDirectory();
    temp.then((value) {
      tDir = value!.path;
      print(tDir);
    });
  }

结果如下:

I/flutter ( 6737): /storage/emulated/0/Android/data/com.example.testdemo/files

创建目录

注意,在判断目录是否存在的时候,!表示没有这个目录。
不加!表示有这个目录。

  // 创建目录
  void createDir(String dirName) {
    // 放置目录的返回值
    String? tDir = '';
    Future<Directory?> temp = getTemporaryDirectory();
    temp.then((value) {
      tDir = value!.path;
      print(tDir);
      
      // 判断目录是否存在,并得到他的值
      Directory("$tDir/$dirName").exists().then((value){
        // 如果目录不存在,则创建目录
        if(!value){
          Directory("$tDir/$dirName").create();
          print("目录创建成功!");
        }else{
          print("目录已存在!");
        }
      });
    });
  }

结果如下:
注:因为我刚刚创建了该目录,所有已存在!

I/flutter ( 6737): /data/user/0/com.example.testdemo/cache
I/flutter ( 6737): 目录已存在!

删除目录

注意,在判断目录是否存在的时候,!表示没有这个目录。
不加!表示有这个目录。

  // 删除目录
  void deleteDir(String dirName) {
    // 放置目录的返回值
    String? tDir = '';
    Future<Directory?> temp = getTemporaryDirectory();
    temp.then((value) {
      tDir = value!.path;
      print(tDir);
      
      // 判断目录是否存在,并得到他的值
      Directory("$tDir/$dirName").exists().then((value){
        // 如果目录存在,则删除目录
        if(value){
          Directory("$tDir/$dirName").delete();
          print("目录删除成功!");
        }else{
          print("删除失败!");
        }
      });
    });
  }

结果如下:

I/flutter ( 6737): /data/user/0/com.example.testdemo/cache
I/flutter ( 6737): 目录删除成功!
I/flutter ( 6737): /data/user/0/com.example.testdemo/cache
I/flutter ( 6737): 删除失败!

显示目录列表

注意:在程序运行的时候就需要调用这个方法,才能将路径放在列表之中。

  // 显示目录列表
  List lists = [];
  void getFiles () async{
    // 放置目录的返回值
    String? tDir = '';
    
    // 放置各个目录名称
    Future<Directory?> temp = getTemporaryDirectory();
    
    temp.then((value) {
      tDir = value!.path;
      // print(tDir);

      // 将获取到的目录名以迭代的方式放进列表中
      Directory("$tDir").list(recursive: true,followLinks: false).listen((event) {
        lists.add(event.path);
      });
    });
  }

  // 在应用加载的时候进行调用
  @override
  void initState() {
    getFiles();
  }

结果如下:

I/flutter ( 6737): /data/user/0/com.example.testdemo/cache
I/flutter ( 6737): /data/user/0/com.example.testdemo/cache
I/flutter ( 6737): 目录创建成功!
I/flutter ( 6737): 目录创建成功!
I/flutter ( 6737): []
I/flutter ( 6737): [/data/user/0/com.example.testdemo/cache/voice, /data/user/0/com.example.testdemo/cache/ciy]

注:创建的是两个目录,所以有两个创建成功!

创建文件

  // 创建文件
  void createFile() async{
    // 放置目录的返回值
    String? tDir = '';

    // 放置各个目录名称
    Future<Directory?> temp = getTemporaryDirectory();
    temp.then((value) {
      tDir = value!.path;
      // print(tDir);

      File file = File("$tDir/default.ini");
      file.exists().then((value) {
        // 如果没有此文件,则创建
        if(!value){
          file.create();
        }else{
          print("已有该文件");
        }
      });
    });
  }

结果如下:

I/flutter ( 6737): 创建成功!
I/flutter ( 6737): 已经创建成功了....

删除文件

注意,当没有这个文件的时候才进行创建。

  // 删除文件
  void deleteFile() async{
    // 放置目录的返回值
    String? tDir = '';

    // 放置各个目录名称
    Future<Directory?> temp = getTemporaryDirectory();
    temp.then((value) {
      tDir = value!.path;
      // print(tDir);

      File file = File("$tDir/default.ini");
      file.exists().then((value) {
        // 如果没有此文件,则创建
        if(value){
          file.delete();
          print("删除成功");
        }else{
          print("删除失败,可能已经被删除了....");
        }
      });
    });
  }

结果如下:

I/flutter ( 6737): 删除成功
I/flutter ( 6737): 删除失败,可能已经被删除了....

写文件

  // 向文件写入内容
  void saveFile(String contents){
    // 放置目录的返回值
    String tDir = "";

    // 放置各个目录的名称
    Future<Directory> temp = getTemporaryDirectory();
    temp.then((value) {
      tDir = value.path;

      // 打开文件
      File file = File("$tDir/default.ini");
      // 向文件中写入内容
      file.writeAsString(contents,mode: FileMode.append);
      print("写入成功了!");
    });
  }

结果如下:

I/flutter ( 6737): 写入成功了!

注:写入有五种模式:

  1. read:只读模式
  2. write:可读可写模式,如果文件存在,则覆盖
  3. append:追加模式,可读可写,如果文件存在,则在末尾增加
  4. writeOnly:只写模式
  5. writeOnlyAppend:致谢追加模式,不可读

不可读意味着,写进去的数据就不能读取出来!

如果想将文件内容全部删除,可以将写入改为write模式,然后输入一个空字符串即可!
但需要注意的是,全部为空之后的话,就不能按照行方式进行读取数据!

读文件

  // 读取文件内容
  void readFile(String fileName){
    String tDir = "";

    // 放置目录名称
    Future<Directory> temp = getTemporaryDirectory();
    temp.then((value){
      tDir = value.path;
      
      // 打开文件
      File file = File("$tDir/$fileName");
      
      // 全部读出
      file.readAsString().then((value)=> print(value));

      // 按行方式读
      file.readAsLines().then((value) => print("行方式:1=》"+value[0]));
    });
  }

结果如下:

I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 行方式:第1行=》龙姐的大宝贝

小结:

  1. 文件操作基本上都是基于目录操作, 先要找到需要的目录,才能在该目录下操作文件,接着才能进行文件的写入等操作。
  2. 这些操作都是异步执行,所以都是Future<Directory>对象。
  3. 在获取目录列表的时候,需要对它在应用加载之前进行初始化数据。

最后附上此次代码:

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';

class Path_Provider_Demo extends StatefulWidget {
  const Path_Provider_Demo({Key? key}) : super(key: key);

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

class _Path_Provider_DemoState extends State<Path_Provider_Demo> {
  void getTDir() {
    // 放置目录的返回值
    String tDir = '';
    // 获取临时目录
    Future<Directory> tempDir = getTemporaryDirectory();
    // 所用them 来得到路径
    tempDir.then((value) {
      tDir = value.path;
      print(tDir);
    });
  }

  // 获取文档目录
  void getDDir() {
    // 放置目录的返回值
    String tDir = '';
    Future<Directory> temp = getApplicationDocumentsDirectory();
    temp.then((value) {
      tDir = value.path;
      print(tDir);
    });
  }

  // 获取文档目录
  void getESDir() {
    // 放置目录的返回值
    String? tDir = '';
    Future<Directory?> temp = getExternalStorageDirectory();
    temp.then((value) {
      tDir = value!.path;
      print(tDir);
    });
  }

  // 创建目录
  void createDir(String dirName) {
    // 放置目录的返回值
    String? tDir = '';
    Future<Directory?> temp = getTemporaryDirectory();
    temp.then((value) {
      tDir = value!.path;
      print(tDir);
      // 判断目录是否存在,并得到他的值
      Directory("$tDir/$dirName").exists().then((value){
        // 如果目录不存在,则创建目录
        if(!value){
          Directory("$tDir/$dirName").create();
          print("目录创建成功!");
        }else{
          print("目录已存在!");
        }
      });
    });
  }

  // 删除目录
  void deleteDir(String dirName) {
    // 放置目录的返回值
    String? tDir = '';
    Future<Directory?> temp = getTemporaryDirectory();
    temp.then((value) {
      tDir = value!.path;
      print(tDir);
      // 判断目录是否存在,并得到他的值
      Directory("$tDir/$dirName").exists().then((value){
        // 如果目录存在,则删除目录
        if(value){
          Directory("$tDir/$dirName").delete();
          print("目录删除成功!");
        }else{
          print("删除失败!");
        }
      });
    });
  }

  // 显示目录列表
  List lists = [];
  void getFiles () async{
    // 放置目录的返回值
    String? tDir = '';

    // 放置各个目录名称
    Future<Directory?> temp = getTemporaryDirectory();
    temp.then((value) {
      tDir = value!.path;
      // print(tDir);

      // 将获取到的目录名以迭代的方式放进列表中
      Directory("$tDir").list(recursive: true,followLinks: false).listen((event) {
        lists.add(event.path);
      });
    });
  }

  // 在应用加载的时候进行调用
  @override
  void initState() {
    getFiles();
  }

  // 创建文件
  void createFile() async{
    // 放置目录的返回值
    String? tDir = '';

    // 放置各个目录名称
    Future<Directory?> temp = getTemporaryDirectory();
    temp.then((value) {
      tDir = value!.path;
      // print(tDir);
      
      // 打开文件
      File file = File("$tDir/default.ini");
      file.exists().then((value) {
        // 如果没有此文件,则创建
        if(!value){
          file.create();
          print("创建成功!");
        }else{
          print("已经创建成功了....");
        }
      });
    });
  }

  // 删除文件
  void deleteFile() async{
    // 放置目录的返回值
    String? tDir = '';

    // 放置各个目录名称
    Future<Directory?> temp = getTemporaryDirectory();
    temp.then((value) {
      tDir = value!.path;
      // print(tDir);

      // 创建文件
      File file = File("$tDir/default.ini");
      file.exists().then((value) {
        // 如果没有此文件,则创建
        if(value){
          file.delete();
          print("删除成功");
        }else{
          print("删除失败,可能已经被删除了....");
        }
      });
    });
  }

  // 向文件写入内容
  void saveFile(String contents){
    // 放置目录的返回值
    String tDir = "";

    // 放置各个目录的名称
    Future<Directory> temp = getTemporaryDirectory();
    temp.then((value) {
      tDir = value.path;

      // 打开文件
      File file = File("$tDir/default.ini");
      // 向文件中写入内容
      file.writeAsString(contents,mode: FileMode.append);
      print("写入成功了!");
    });
  }

  // 读取文件内容
  void readFile(String fileName){
    String tDir = "";

    // 放置目录名称
    Future<Directory> temp = getTemporaryDirectory();
    temp.then((value){
      tDir = value.path;
      
      // 打开文件
      File file = File("$tDir/$fileName");

      // 全部读出
      file.readAsString().then((value)=> print(value));

      // 按行方式读
      file.readAsLines().then((value) => print("行方式:1=》"+value[0]));
    });
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("文件路径"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          children: [
            // 获取临时目录
            ElevatedButton(
              onPressed: () {
                getTDir();
              },
              child: Text("获取临时目录"),
            ),
            // 获取文档目录
            ElevatedButton(
              onPressed: () {
                getDDir();
              },
              child: Text("获取文档目录"),
            ),
            // 获取外存目录
            ElevatedButton(
              onPressed: () {
                getESDir();
              },
              child: Text("获取外存目录"),
            ),
            // 创建目录
            ElevatedButton(
              onPressed: () {
                createDir("voice");
                createDir("ciy");
              },
              child: Text("创建目录"),
            ),
            // 删除目录
            ElevatedButton(
              onPressed: () {
                deleteDir("voice");
                deleteDir("ciy");
              },
              child: Text("删除目录"),
            ),
            // 删除目录
            ElevatedButton(
              onPressed: () {
                getFiles();
                print(lists);
              },
              child: Text("获取临时目录列表"),
            ),
            // 创建文件
            ElevatedButton(
              onPressed: () {
                createFile();
              },
              child: Text("创建文件"),
            ),
            // 删除文件
            ElevatedButton(
              onPressed: () {
                deleteFile();
              },
              child: Text("删除文件"),
            ),
            // 写文件
            ElevatedButton(
              onPressed: () {
                saveFile("龙姐的大宝贝\n"*10);
              },
              child: Text("写文件"),
            ),
            // 读文件
            ElevatedButton(
              onPressed: () {
                readFile("default.ini");
              },
              child: Text("读文件"),
            ),
          ],
        ),
      ),
    );
  }
}

效果如下:
在这里插入图片描述

StatelessWidget 和 StatefulWidget

StatelessWidget 是无状态组件,改变内部值,但不会实时刷新。

StatefulWidget是有状态组件,改变内部值,但会实时刷新。

在这里插入图片描述

在这里插入图片描述

两种传参方式

使用StatelessWidget传参:

// 无状态
class Test4 extends StatelessWidget {
  Test4({Key? key}) : super(key: key);
  int a = 0;

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        title: const Text("无状态组件"),
      ),
      body: Center(
        child: Container(
          alignment: Alignment.center,
          width: size.width * 0.5,
          height: size.width * 0.5,
          color: Colors.amber,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 有状态
              Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  // 需要传递一个参数到另一个页面中
                  Demo04(text: a),
                  ElevatedButton(
                      onPressed: () {
                        a++;
                        print(a);
                      },
                      child: const Text("点击")),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class Demo04 extends StatelessWidget {
  const Demo04({Key? key, required this.text}) : super(key: key);

  // 传递一个必须的参数
  final int text;

  @override
  Widget build(BuildContext context) {
    return Text(text.toString());
  }
}

使用StatefulWidget 传参:

// 有状态
class Test3 extends StatefulWidget {
  const Test3({Key? key}) : super(key: key);

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

class _Test3State extends State<Test3> {
  //
  int a = 0;

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        title: const Text("有状态组件"),
      ),
      body: Center(
        child: Container(
          alignment: Alignment.center,
          width: size.width * 0.5,
          height: size.width * 0.5,
          color: Colors.amber,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 有状态
              Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                
                  // 需要传递一个参数到另一个页面中
                  Demo02(text: a),
                  
                  ElevatedButton(
                      onPressed: () {
                        setState(() {
                          a++;
                          print(a);
                        });
                      },
                      child: const Text("点击")),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class Demo02 extends StatefulWidget {
  const Demo02({Key? key, required this.text}) : super(key: key);
  final int text;

  @override
  State<Demo02> createState() => _Demo02State();
}

class _Demo02State extends State<Demo02> {
  @override
  Widget build(BuildContext context) {
    return Text(
    
    // 使用 widget 获得传递的值
      widget.text.toString(),
      
      style: const TextStyle(fontSize: 28),
    );
  }
}

注意:

  1. 获取StatefulWidget 传递过来的参数,需要使用widget.参数名进行获取。
  2. 如果当前页面是无状态页面,而上一个页面是一个有状态组件,而传递过去的参数就也会随之改变。

url_launcher 打开第三方软件、网站

使用命令加载 第三方包

flutter pub add url_launcher

打开网页

            ElevatedButton(
                onPressed: () async {
                  String url = "https://www.baidu.com";
                  await launch(url, forceSafariVC: false);
                },
                child: const Text("打开链接"))

打开电话本

ElevatedButton(onPressed: () async{
              String phone = "2611525";
              String url = "tel:$phone";
              await launch(url, forceSafariVC: false);
            }, child: Text("打开拨号页面")),
            

打开短信页面


ElevatedButton(onPressed: () async{
   String phone = "2611525";
   String url = "sms:$phone";
   await launch(url, forceSafariVC: false);
 }, 
 child: Text("打开短信页面")
),

在这里插入图片描述

WillPopScope 两次点击之后才能退出

WillPopScope 有回调函数,该回调需要返回一个Future对象,如果返回的Future最终值为false时,则当前路由不出栈(不会返回);最终值为true时,当前路由出栈退出。我们需要提供这个回调来决定是否退出。
点击两次之后才能退出,需要定义一个变量来存放上一次所点击的时间。
判断时间是否为空 或者 当前时间是否和上次点击时间相隔1秒,如果符合,则不能返回,否则就可以退出。

class WillPopScopeDemoState extends State<WillPopScopeDemo> {

  // 上次点击时间
  DateTime? _lastPressedAt;

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        if (_lastPressedAt == null ||
            DateTime.now().difference(_lastPressedAt!) > Duration(seconds: 1)) {
          // 点击两次之后超过1秒从新计时
          _lastPressedAt = DateTime.now();
          return false;
        }
        return true;
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text("WillPopScope"),
        ),
        body: Container(
          alignment: Alignment.center,
          child: Text("1秒内连续按两次返回键退出"),
        ),
      ),
    );
  }
}

在这里插入图片描述

InheritedWidget 数据共享

从根到叶实现数据共享,通常共享ThemeLocal的数据,功能就是共享数据。

  1. 创建实体类,分别存储的是人的数据。
class User {
  String name;
  int age;
  String info;
  
  // 构造函数
  User(this.name, this.age, this.info) {}
}
  1. 创建一个数据共享类,在Android studio中输入inh,回车,即可创建一个InheritedWidget 模板。我们
class SharedDateWidegetDemo extends InheritedWidget {
  const SharedDateWidegetDemo({
    Key? key,
    // 设置为必须参数
    required this.data,
    required Widget child,
  }) : super(key: key, child: child);

  // 所需的数据,可以是 String、int、bool、List、实体类....
  final User data;

	// 定义一个便捷方法,方便子树中的widget获取共享数据
  static SharedDateWidegetDemo of(BuildContext context) {
    final SharedDateWidegetDemo? result =
        context.dependOnInheritedWidgetOfExactType<SharedDateWidegetDemo>();
    assert(result != null, 'No SharedDateWidegetDemo found in context');
    return result!;
  }

  // 该回调决定当data发生变化时,是否通知子树中依赖data的Widget重新build
  @override
  bool updateShouldNotify(SharedDateWidegetDemo old) {
    return old.data != data;
  }
}
  1. 创建一个(多个)放在父类下面的子类,这个子类可以接收父组件传递下来的数据,使用父组件传递下来的数据需要通过上面创建的数据共享类SharedDateWidegetDemo.of(context).data进行获取数据,由于我们创建的是一个实体类,所以可以在build方法中创建实例,进行获取数据。
// 创建一个子组件,用于显示SharedDateWidegetDemo 中定义的 Data
class ChildWidgetDemo extends StatefulWidget {
  const ChildWidgetDemo({Key? key}) : super(key: key);

  @override
  State<ChildWidgetDemo> createState() => _ChildWidgetDemoState();
}

class _ChildWidgetDemoState extends State<ChildWidgetDemo> {
  @override
  Widget build(BuildContext context) {
  
  // 创建一个实例进行获取父组件传递下来的数据
    User a = SharedDateWidegetDemo.of(context).data;
    return Text(
      a.info,
      style: TextStyle(fontSize: 24),
    );
  }

	//父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
	//如果build中没有依赖InheritedWidget,则此回调不会被调用。
  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    print("依赖被改变");
  }
}
  1. 创建一个测试StatefulWidget作为父组件,
// 创建一个页面进行测试
class TestDemo extends StatefulWidget {
  const TestDemo({Key? key}) : super(key: key);

  @override
  State<TestDemo> createState() => _TestDemoState();
}

class _TestDemoState extends State<TestDemo> {
  // 默认年龄
  int age = 12;
  @override
  Widget build(BuildContext context) {
    // 作为数据共享类的子组件就可以进行数据的传递
    return SharedDateWidegetDemo(
      // 传入一个类
      data: User("张三", age, "劳改犯"),
      child: Scaffold(
        appBar: AppBar(),
        body: Center(
          child: Container(
            width: 100,
            // height: 100,
            child: Column(
              children: [
                // 调用子组件1
                const ChildWidgetDemo(),
                // 调用子组件2
                ChildWidgetDemo2(),
                ElevatedButton(onPressed: (){
                // 通过按钮可以动态的改变年龄
                  setState(() {
                    age++;
                  });
                }, child: Text("点击"))
              ],
            ),
          ),
        ),
      ),
    );
  }
}

效果如下:
在这里插入图片描述

小结:

  1. 在刚开始的是我觉得这个很难,其实将它分为这几步进行实现,就显得特别简单,其实就是inh创建一个共享数据类,在子组件中使用数据共享类.of(context).参数就可以将父组件传递的参数获取进行显示,然后创建一个父组件作为数据共享组件的子组件,就可以开始访问。
  2. 像其他大佬所说的依赖,我个人更觉得数据共享类是一个媒介,父组件通过媒介将数据传给子组件。

最后看看我这个灵魂画手画的图(如有错误,请指出):
在这里插入图片描述参考:

Flutter 页面间数据传递(共享)的几种经常使用方式
https://www.shangmayuan.com/a/501467f3dc8d48538c9d1093.html

provider 状态状态管理

在使用之前需要知道,provider包是基于InheritedWidget 进行实现的,大致流程都是换汤不换药的,所以在用起来的时候多注意一下就行了。
下面是它的官方介绍:

https://github.com/rrousselGit/provider/blob/master/resources/translations/zh-CN/README.md

基本使用( 更换Container的颜色并实现计数器效果)

  1. 创建一个改变数据的类,用于获取传入的数据进行传递数据
// 用于统计数字
class Counter with ChangeNotifier, DiagnosticableTreeMixin {
  // 定义一个统计变量
  int _count = 0;

  // 定义一个 get 方法
  int get count => _count;

  // 定义一个增加方法
  void increment() {
    // _count  自增
    _count++;
    // 通知监听器
    notifyListeners();
  }

  // 调用调试填充属性
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    // TODO: implement debugFillProperties
    super.debugFillProperties(properties);
    // 添加一个整形属性为 count
    properties.add(IntProperty('count', count));
  }
}

// 用于改变颜色
class ChangeColor with ChangeNotifier, DiagnosticableTreeMixin {
  int _num = 0;

  int get num => _num;
  Color _color = Colors.red;

  Color get color => _color;

  void change_color() {
    int a = _num++;
    _color = Colors.primaries[a % 18];
    // 调用通知者
    notifyListeners();
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    // TODO: implement debugFillProperties
    super.debugFillProperties(properties);
    properties.add(ColorProperty('color', color));
    // properties.add(IntProperty('num', num));
  }
}

  1. 将根组件继承MultiProvider组件,而providers存放所需的介质
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counter()),
        ChangeNotifierProvider(create: (_) => ChangeColor()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<ColorThemeProvider>(
      create: (context) => ColorThemeProvider(),
      child: Consumer<ColorThemeProvider>(
        builder: (context, colorThemeProvider, child) => MaterialApp(
          home: Provider_Demo(),
          theme: ThemeData(
              primarySwatch: colorThemeProvider.color,
              visualDensity: VisualDensity.adaptivePlatformDensity),
          // home: url_launcher_Demo(),
        ),
      ),
    );
  }
}

  1. 创建一个组件进行获取值,使用context.watch<ChangeColor>().num可获得在ChangeColor中所定义的变量值。
    context.read<Counter>().increment()则获取Counter类中的对应的方法,也可以理解为: 通过context 读取Counter类 中的increment 方法。
    context.watch<Counter>().count.toString()可以理解为:通过context 看到Counter 中的 count 的值
// 父组件
class Provider_Demo extends StatefulWidget {
  const Provider_Demo({Key? key}) : super(key: key);

  @override
  State<Provider_Demo> createState() => _Provider_DemoState();
}

class _Provider_DemoState extends State<Provider_Demo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Provider"),
      ),
      body: Center(
        child: Container(
          width: 300,
          height: 300,
          color: context.watch<ChangeColor>().color,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Builder(builder: (context) {
                String length = Colors
                    .primaries[context.watch<ChangeColor>().num % 18]
                    .toString();
                return Text(
                  "当前颜色为:${length.substring(length.length - 12, length.length - 2)}",
                  style: TextStyle(fontSize: 18),
                );
              }),
              
              // 子组件
              Count(),
              ElevatedButton(
                  onPressed: () {
                    /// 点击调用 Counter 中的 increment 方法
                    /// 也可以理解为: 通过context 读取Counter类 中的increment 方法
                    context.read<Counter>().increment();
                    context.read<ChangeColor>().change_color();
                  },
                  child: Text("点击"))
            ],
          ),
        ),
      ),
    );
  }
}


// 创建一个子组件
class Count extends StatefulWidget {
  const Count({Key? key}) : super(key: key);

  @override
  State<Count> createState() => _CountState();
}

class _CountState extends State<Count> {
  @override
  Widget build(BuildContext context) {
    return Text(
      // 通过context 看到Counter 中的 count 的值
      context.watch<Counter>().count.toString(),
      style: Theme.of(context).textTheme.headline4,
    );
  }
}

效果如下:
在这里插入图片描述
注:如果需要实现不同颜色的切换,在这里我使用的是Colors.primaries[context.watch<ChangeColor>().num % 18] .toString();,因为primaries是一个颜色列表,里面有18MaterialColor颜色,所以我用%18就可实现循环效果。

小结:

  1. flutter的根部使用MultiProvider加载所需的介质类,然后将根继承该组件。
  2. 创建介质类的时候,需要用with去混合ChangeNotifier、 DiagnosticableTreeMixin这两个类,(with起到可以直接调用它们两个类里面的任意东西的作用!),然后创建所需要的暴露的属性,以及它的get方法,并且需要创建一个可以实现通过按钮调用的context.read<Counter>().increment();方法,进行该方法对应的操作,并在该方法中调用notifyListeners();(通知者)告诉盒子需要重新绘制,最后通过context.watch<ChangeColor>().color就能得到改变之后的值并赋予给所需的组件
  3. 其实provider并不难,实现的方式和上面介绍的InheritedWidget原理一致,都是通过将数据通过介质进行更新,然后又将数据通过根拿给各个小组件使用。

参考:

《Flutter实战·第二版》
https://book.flutterchina.club/chapter7/provider.html (有点版本问题,需要自己把握)

Flutter之Widget层级介绍
https://blog.csdn.net/lmh_19941113/article/details/97566811

美团技术站
https://tech.meituan.com/2018/08/09/waimai-flutter-practice.html

Flutter’s Rendering Pipeline
https://www.youtube.com/watch?v=UUfXWzp0-DU

Flutter(Dart)中extends 、 implements 、 with的用法与区别
https://www.jianshu.com/p/04b896764f6e

官方列子
https://pub.flutter-io.cn/packages/provider/example

切换主题颜色

// main文件
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
  // 监听
    return ChangeNotifierProvider<ColorThemeProvider>(
      create: (context) => ColorThemeProvider(),
      child: Consumer<ColorThemeProvider>(
        builder: (context, colorThemeProvider, child) => MaterialApp(
        
          home: Color_Theme(),
          theme: ThemeData(
          // 得到改变的颜色,默认为 yellow 
            primarySwatch: colorThemeProvider.color,
            visualDensity: VisualDensity.adaptivePlatformDensity
          ),
        ),
      ),
    );
  }
}

// 颜色选择页面
class Color_Theme extends StatefulWidget {
  const Color_Theme({Key? key}) : super(key: key);

  @override
  State<Color_Theme> createState() => _Color_ThemeState();
}

class _Color_ThemeState extends State<Color_Theme> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("颜色主题"),
      ),
      body: ExpansionTile(
        title: Text("选择颜色"),
        leading: Icon(Icons.color_lens),
        children: [
          Padding(
            padding: const EdgeInsets.all(10.0),
            child: Wrap(
              runSpacing: 5,
              spacing: 5,
              children: [
                ...Colors.primaries
                    .map((color) => Material(
                          color: color,
                          child: InkWell(
                            onTap: () {
                              context.read<ColorThemeProvider>().changeColor(color: color);
                            },
                            child: Container(
                              height: 40,
                              width: 40,
                            ),
                          ),
                        ))
                    .toList()
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// 设置颜色改变
class ColorThemeProvider extends ChangeNotifier {
  late MaterialColor _color;

  MaterialColor get color => _color;

  ColorThemeProvider() {
    _color = Colors.yellow;
  }

  // 改变颜色
  void changeColor({MaterialColor color = Colors.yellow}) {
    _color = color;
    notifyListeners();
  }
}

样式如下:
在这里插入图片描述

ValueListenableBuilder

Flutter 提供了一个 ValueListenableBuilder 组件,它的功能是监听一个数据源,如果数据源发生变化,则会重新执行其 builder。

  1. valueListenable:类型为 ValueListenable,表示一个可监听的数据源。
  2. builder:数据源发生变化通知时,会重新调用 builder 重新 build 子组件树。
  3. child: builder 中每次都会重新构建整个子组件树,如果子组件树中有一些不变的部分,可以传递给child,child 会作为builder的第三个参数传递给 builder,通过这种方式就可以实现组件缓存,原理和AnimatedBuilder 第三个 child 相同。
class ValueListenableBuilder_Demo extends StatefulWidget {
  const ValueListenableBuilder_Demo({Key? key}) : super(key: key);

  @override
  State<ValueListenableBuilder_Demo> createState() =>
      _ValueListenableBuilder_DemoState();
}

class _ValueListenableBuilder_DemoState
    extends State<ValueListenableBuilder_Demo> {
  ValueNotifier<int> valueNotifier = ValueNotifier<int>(1);
  ValueNotifier<String> valueNotifier2 = ValueNotifier<String>("当前颜色为:");

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ValueListenableBuilder"),
      ),
      body: Center(
          child: ValueListenableBuilder(
        valueListenable: valueNotifier,
        builder: (BuildContext context, int value1, Widget? child) {
          return Container(
              width: 300,
              height: 300,
              color: Colors.primaries[value1.toInt() % 18],
              child: Test1(
                valueNotifier2: valueNotifier2,
                valueNotifier: valueNotifier,
                value1: value1,
              ));
        },
      )),
    );
  }
}

// 将文本提出为单独的组件
class Test1 extends StatefulWidget {
  const Test1({
    Key? key,
    required this.valueNotifier2,
    required this.valueNotifier,
    required this.value1,
  }) : super(key: key);

  final ValueNotifier<String> valueNotifier2;
  final ValueNotifier<int> valueNotifier;
  final int value1;

  @override
  State<Test1> createState() => _Test1State();
}

class _Test1State extends State<Test1> {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ValueListenableBuilder(
          builder: (BuildContext context, String value2, Widget? child) {
            return Text(
              value2 + widget.value1.toString(),
              style: TextStyle(fontSize: 28),
            );
          },
          valueListenable: widget.valueNotifier2,
        ),
        ElevatedButton(
            style: ButtonStyle(
                backgroundColor: MaterialStateProperty.all(
                    Colors.primaries[Random().nextInt(18)])),
            onPressed: () {
              widget.valueNotifier.value++;
            },
            child: Text("点一点"))
      ],
    );
  }
}

效果如下:
在这里插入图片描述

FutureBuilder和StreamBuilder 异步获取数据

在开发过程中,我们需要从 网络上或者文件中获得一些数据,但是为了避免在还没有获得数据的时候,UI组件以及将一些功能实现,导致出现一些错误,所以我们就可以使用FutureBuilder组件和StreamBuilder组件进行数据的加载处理,而它俩之间有着一些相同的关系,也有这不同的联系,就比如FutureBuilder是在done的时候才将数据返回,而StreamBuilder 是在active的时候将数据实时返回。

FutureBuilder

比如在打开一个页面时我们需要先从互联网上获取数据,在获取数据的过程中我们显示一个加载框,等获取到数据时我们再渲染页面。
又比如我们想展示Stream(比如文件流、互联网数据接收流)的进度。

class _FutureBuilderDemoState extends State<FutureBuilderDemo> {
  // 网络请求数据
  _getData() async {
    String url =
        "http://ws.webxml.com.cn/WebServices/WeatherWS.asmx/getRegionCountry?";
    var request = await HttpClient().getUrl(Uri.parse(url));
    var response = await request.close();
    var body = response.transform(utf8.decoder).join();
    return body;
  }

  // 通过FutureBuilder得到数据
  getFuture() {
    return FutureBuilder(
    // 调用 _getData方法得到数据
        future: Future.delayed(Duration(seconds: 1), () => _getData()),
        builder: (context, snapshot) {
        // 判断如果有值的话,就将数据取出
          if (snapshot.hasData) {
            return Column(
              children: [Text("${snapshot.data}")],
            );
          }
          // 如果没有值,显示一个错误图标
          if (snapshot.hasError) {
            return Icon(Icons.error);
          }
          // 在加载数据的时候,展示进度条,告诉用户正在加载
          return CircularProgressIndicator();
        });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("FutureBuilder"),
      ),
      body: SingleChildScrollView(
        child: Center(
            child: ConstrainedBox(
          constraints: const BoxConstraints(minHeight: 300),
          child: Container(
            width: 300,
            // height: 300,
            color: Colors.amber,
            child: getFuture(),
          ),
        )),
      ),
    );
  }
}

效果如下:
在这里插入图片描述

StreamBuilder 数据流

在Dart中Stream 也是用于接收异步事件数据,和Future 不同的是,它可以接收多个异步操作的结果,它常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。

StreamBuilder正是用于配合Stream来展示流上事件(数据)变化的UI组件。

class _StreamBuilderDemoState extends State<StreamBuilderDemo> {
  final stream = Stream.periodic(Duration(seconds: 3), (_) => 42);
  final future = Future.delayed(Duration(seconds: 2), () => "这是数据");

  // 监听数据流和FutureBuilder
  @override
  void initState() {
    super.initState();
    // 监听数据流,他会源源不断的打印 stream的值,但是只能监听一次,不能和下面的 StreamBuilder同时使用
    // stream.listen((event) {
    //   print(event);
    // });
    future.then((value) => print(value));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("FutureBuilder"),
      ),
      body: Center(
        child: ConstrainedBox(
          constraints: const BoxConstraints(minHeight: 300),
          child: DefaultTextStyle(
            style: const TextStyle(fontSize: 24),
            child: Container(
              width: 300,
              color: Colors.amber,
              child: StreamBuilder(
                stream: stream,
                builder:
                    (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
                  switch (snapshot.connectionState) {
                    case ConnectionState.none:
                      return const Text("数据为空");
                    case ConnectionState.waiting:
                      return const Text("等待数据流");
                    case ConnectionState.active:
                      if (snapshot.hasError) {
                        return Text("错误:${snapshot.error}");
                      } else {
                        return Text("内容为:${snapshot.data}");
                      }
                    case ConnectionState.done:
                      return const Text("数据流已关闭");
                  }
                  return Container();
                },
              ),
            ),
          ),
        ),
      ),
    );
  }
}

效果如下:
在这里插入图片描述Sream中还可以通过final controller = StreamController.broadcast();来控制数据的传入,它还提供了一个sink(水池)存放数据,使用controller.sink.add(10);可以手动将数据添加到水池当中,并在builder中使用controller.stream将数据存到stream中,通过snapshot.data可以将其取出。
注意:每次build得到的值都不同,所以可以用它去实现相应的业务逻辑!

final controller = StreamController.broadcast();

getStreamDate() {
  return StreamBuilder(
  //
    stream: controller.stream,
    builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
      switch (snapshot.connectionState) {
        case ConnectionState.none:
          return const Text("数据为空");
        case ConnectionState.waiting:
          return const Text("等待数据流");
        case ConnectionState.active:
          if (snapshot.hasError) {
            return Text("错误:${snapshot.error}");
          } else {
            return Text("内容为:${snapshot.data}");
          }
        case ConnectionState.done:
          return const Text("数据流已关闭");
      }
      return Container();
    },
  );
}

  // 关闭数据流
  @override
  void dispose() {
    super.dispose();
    controller.close();
  }

点击获得网络数据

实现点击按钮即可更新数据,必须将其分为两个组件进行加载,才能进行达到这个效果。

// 得到数据
getFuture() {
  return FutureBuilder(
      future: Future.delayed(Duration(seconds: 1), () => _getData()),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Column(
            children: [Text("${snapshot.data}")],
          );
        }
        if (snapshot.hasError) {
          return Icon(Icons.error);
        }
        return CircularProgressIndicator();
      });
}

// 网络请求数据
_getData() async {
  String url =
      "http://ws.webxml.com.cn/WebServices/WeatherWS.asmx/getRegionCountry?";
  var request = await HttpClient().getUrl(Uri.parse(url));
  var response = await request.close();
  var body = response.transform(utf8.decoder).join();
  return body;
}

class FutureBuilderDemo extends StatefulWidget {
  const FutureBuilderDemo({Key? key}) : super(key: key);

  @override
  State<FutureBuilderDemo> createState() => _FutureBuilderDemoState();
}

// 父组件
class _FutureBuilderDemoState extends State<FutureBuilderDemo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("FutureBuilder"),
      ),
      body: SingleChildScrollView(
        child: Center(
            child: Container(
              width: 300,
              // height: 300,
              color: Colors.amber,
              child: Test(),
            )),
      ),
    );
  }
}

//子组件
class Test extends StatefulWidget {
  const Test({Key? key}) : super(key: key);

  @override
  State<Test> createState() => _TestState();
}

class _TestState extends State<Test> {
  bool flag = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        ElevatedButton(
          onPressed: () {
            setState(() {
              flag = !flag;
            });
          },
          child: Text("点击获得数据"),
        ),
        flag == false ? Text("") : getFuture(),
      ],
    );
  }
}

效果如下:
在这里插入图片描述

StreamBilder实现时钟效果

// 定义一个死循环,当while每次为true,就build一次
getTime() async* {
  while (true) {
    await Future.delayed(Duration(seconds: 1));
    yield DateTime.now();
  }
}
class _TimeDemoState extends State<TimeDemo> {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: getTime(),
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
        print('build');
        switch (snapshot.connectionState) {
          case ConnectionState.none:
            return const Text("没有值");
          case ConnectionState.waiting:
            return const Text("没有值");
          case ConnectionState.active:
            if (snapshot.hasError) {
              return const Text("出错了");
            } else {
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    snapshot.data.toString().substring(0, 10),
                    style: const TextStyle(fontSize: 28),
                  ),
                  Text(snapshot.data.toString().substring(10, 19)),
                ],
              );
            }
          case ConnectionState.done:
            return Text("数据流已关闭");
        }
      },
    );
  }
}

效果如下:
在这里插入图片描述
参考:

王叔的StreamBuilder和FutureBilder
https://www.bilibili.com/video/BV1Di4y1V78M?spm_id_from=333.999.0.0
https://www.bilibili.com/video/BV165411V7PS/?spm_id_from=333.788

《Flutter实战·第二版》
https://book.flutterchina.club/chapter7/futurebuilder_and_streambuilder.html

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值