美文网首页
Flutter入门六:【聊天】+ 网络请求 + 状态保留

Flutter入门六:【聊天】+ 网络请求 + 状态保留

作者: markhetao | 来源:发表于2021-02-21 22:31 被阅读0次

    Flutter入门 学习大纲

    1. Mock网络数据
    2. 网络请求
    3. 聊天页 (状态保留)

    相关资源链接

    1. Mock数据地址http://rap2.taobao.org/account/login
    2. 随机头像地址https://randomuser.me/photos
    3. mockjs规则示例http://mockjs.com/examples.html
    4. dart包的公共库https://pub.dev/

    1. Mock网络数据

    • 使用阿里妈妈团队开发的RAP接口管理平台进行接口数据的创建,模拟微信好友信息

    • 点击进入 👉 Mock登录页,使用邮箱注册登录

      image.png
    • 新建Flutter入门仓库:

      image.png
    • 点击进入仓库新建接口,设置Url地址、名称、类型、状态码

      image.png
    • 编辑聊天接口

      image.png
    • 新增chat_list数组,50个元素,每个元素包含imageUrl头像name名称message消息

    生成规则,可以参考👉 mock.js实例

    @natural: 随机生成数字
    @name:随机生成英文名字
    @cname:随机生成中文名字
    @cparagraph:随机生成一段文字

    image.png

    头像链接从👉随机用户网站临时获取

    image.png
    • 拷贝地址https://randomuser.me/api/portraits/women/85.jpg,将最后的85编号修改为随机值
      https://randomuser.me/api/portraits/women/@natural(20,70).jpg
      image.png
    • 获取接口内容

      image.png
    • 接口地址为http://rap2api.taobao.org/app/mock/277621/api/chat/list,每次访问,随机生成50条数据。

      image.png

    2. 网络请求

    • 官方提供了http的公共网络请求包,在👉dart公共库可以搜索到http及其使用方式
    image.png image.png

    2.1 导入http网络请求包

    • 拷贝http版本,在项目的pubspec.yaml配置文件中,dependencies一栏新增http包引用
    image.png
    • 获取所有包:点击Pub get终端手动输入flutter pub get,都可以获取到。
    image.png

    2.2 常规网络请求

    • 创建Chat 聊天模型,使用factory创建工厂化方法,将Map对象转换为Chat对象
    class Chat {
      final String name;     // 名称
      final String message;  // 消息
      final String imageUrl; // 头像链接
    
      Chat({this.name, this.message, this.imageUrl});
    
      // 工厂方法,Map对象转Chat对象
      factory Chat.fromJson(Map json) {
        return Chat(name: json["name"],
                    message: json["message"],
                    imageUrl: json["imageUrl"]);
      }
    }
    
    • 使用http请求获取数据
      // Future表示可能存在错误,记录正在执行的状态
      // async异步请求,配合await使用
      Future<List<Chat>> getDatas() async {
        // 发起请求并等待结果
        final response = await http.get(
            'http://rap2api.taobao.org/app/mock/277621/api/chat/list');
    
        // 状态码不为200,抛出错误
        if (response.statusCode != 200) {
          throw Exception('statusCode: ${response.statusCode}');
        }
    
        // response.body是json数据,json转map再转Model
        final responseBody = json.decode(response.body); // Map结构
        List<Chat> chatList = responseBody['chat_list'].map<Chat>((item) =>
            Chat.fromJson(item)).toList(); // 将列表元素都转换为Chat类型
        // print(chatList.map((item) => print(item.name)));
        return chatList;
      }
    
    1. 使用Future类,记录执行状态返回内容错误,供外部处理
    2. 网络请求使用异步任务,用async修饰,必须配合await等待网络结果
    3. 网络请求的错误情况,使用throw抛出Exception错误信息。
    4. 接口返回的数据json格式,使用json.decode转化成Map结构,再转换成对应Model类型。

    2.3 数据的使用

    • 数据的使用,介绍两种方法:
    1. 常规方式:使用变量进行承接,通过setState()刷新部件
    2. 快捷方式:使用FutureBuilder部件,在future属性中设置异步请求函数,在builder中读取AsyncSnapshot 异步的数据结果并返回构建的部件

    2.3.1 常规方式

    • 假设我们在initState中调用getDatas请求数据,对于Future类型的数据,可以有两种方法进行处理:
    • 方法一: 使用try catch处理正常请求结果error错误

    • 方法二: 使用Future提供的各种状态链式处理:

      • then:获取正确的内容
      • catchError:捕获异常
      • timeout:设置超时时间
      • whenComplete:捕获结束状态

      常识:
      接口请求超时仅代表超出客户端的timeout请求时限,并不代表取消了请求。依旧会收到服务端的返回结果,我们一般通过Bool值记录是否需要处理返回结果(超时或手动取消,不处理结果)

      • 错误示例(已超时仍接收了返回结果):
        image.png
    • 正确做法,下面代码中使用_cancelConnect进行状态记录,觉得是否接收返回结果

     @override
      void initState() {
        super.initState();
    
        // 方法一: try catch
        try { getDatas(); } catch(error) { print(error); }
    
        // 方法二: Future状态链式处理
        bool _cancelConnect = false;
    
        getDatas()
        // then 获取正确内容
        .then((value) {
          // 已取消,不接受数据
          if (_cancelConnect) return;
          /* 此处使用【变量】接受【value】值,配合【setState】重新【构建部件】即可 */
          value.forEach((e) => print(e.name)); 
        })
        // 捕获错误
        .catchError((error) => print("错误$error"))
        // 设置超时时间,捕获超时错误
        .timeout(Duration(milliseconds: 100)).catchError((timeout) {
          _cancelConnect = true;
          print("超时${timeout}");
        })
        // 结束
        .whenComplete(() => print("完毕"));
      }
    

    2.3.2 快捷方式

    • 使用FutureBuilder异步部件,直接绑定异步数据部件
    import 'dart:convert';
    
    import 'package:flutter/material.dart';
    import 'package:wechat_demo/const.dart';
    
    import 'package:http/http.dart' as http; //导入http库,取别名为http
    
    import 'chat.dart';
    
    class ChatPage extends StatefulWidget {
      @override
      _ChatPageState createState() => _ChatPageState();
    }
    
    class _ChatPageState extends State<ChatPage> {
    
      // Future表示可能存在错误,记录正在执行的状态
      // async异步请求,配合await使用
      Future<List<Chat>> getDatas() async {
        // 发起请求并等待结果
        final response = await http
            .get('http://rap2api.taobao.org/app/mock/277621/api/chat/list');
    
        // 状态码不为200,抛出错误
        if (response.statusCode != 200) {
          throw Exception('statusCode: ${response.statusCode}');
        }
    
        // response.body是json数据,json转map再转Model
        final responseBody = json.decode(response.body); // Map结构
        List<Chat> chatList = responseBody['chat_list']
            .map<Chat>((item) => Chat.fromJson(item))
            .toList(); // 将列表元素都转换为Chat类型
        // print(chatList.map((item) => print(item.name)));
        return chatList;
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Wechat_themeColor,
            elevation: 0.0,
            centerTitle: true,
            title: Text("聊天"),
          ),
          // FutureBuilder 异步部件
          body: FutureBuilder(
            future: getDatas(),    // future: 异步请求
            builder: (BuildContext context, AsyncSnapshot snapshot) {  // builder: 获取异步数据并返回部件
              print(snapshot.data);            // 数据
              print(snapshot.connectionState); // 状态
              return Container();
            },
          ),
        );
      }
    }
    
    • 查看打印结果,可以看到数据null的情况,状态也看到了waitting等待和done完成两种:

      image.png
    • 查看ConnectionState,是枚举类型,包含4种情况

      image.png
    • 我们可以根据不同数据状态展示不同的视图信息

    body: FutureBuilder(
            future: getDatas(),
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              switch (snapshot.connectionState) {
                case ConnectionState.none:    // 无
                  return Container();
                case ConnectionState.waiting:  // 加载中
                  return Center(child:Text("加载中"));
                case ConnectionState.active:  // 连接中
                  return Center(child:Text("连接中"));
                case ConnectionState.done:  // 已完成(根据数据展示页面)
                  print("数据: ${snapshot.data}");
                  return Center(child:Text("正常显示"));
                default:
                  return Container();
              }
            },
          )
    
    • 以上,就是网络请求基本使用方式。下面进行聊天页开发

    3. 聊天页

    1. 开发导航栏气泡弹框
    2. 根据网络数据,构建聊天Cell

    3.1 导航栏Popup气泡

    • 使用Flutter提供的PopupMenuButton实现导航栏按钮气泡弹框:

      image.png
    • chat_page页面代码:

    import 'package:flutter/material.dart';
    import 'package:wechat_demo/const.dart';
    
    class ChatPage extends StatefulWidget {
      @override
      _ChatPageState createState() => _ChatPageState();
    }
    
    class _ChatPageState extends State<ChatPage> {
      // 气泡视图
      List<PopupMenuItem<String>> _buildPopupMenuItem(BuildContext context) {
        return <PopupMenuItem<String>>[
          _buildItem("发起群聊", "发起群聊"),
          _buildItem("添加朋友", "添加朋友"),
          _buildItem("扫一扫1", "扫一扫"),
          _buildItem("收付款", "收付款")
        ];
      }
    
      // 气泡元素
      PopupMenuItem<String> _buildItem(String assetImage, String name) {
        return PopupMenuItem(
            child: Row(children: [
          Image(image: AssetImage("images/${assetImage}.png"), width: 20),
          SizedBox(width: 20),
          Text(name, style: TextStyle(color: Colors.white))
        ]));
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Wechat_themeColor,
            elevation: 0.0,
            // 隐藏分割线
            centerTitle: true,
            // 安卓的导航栏标题未居中,可以设置居中
            title: Text("聊天"),
            actions: <Widget>[
              Container(
                  margin: EdgeInsets.only(right: 20),
                  child: PopupMenuButton(
                      // 偏移值 kToolbarHeight: 导航栏高度
                      offset: Offset(0, kToolbarHeight),
                      // 图标
                      child: Container(
                        margin: EdgeInsets.only(right: 10),
                        child: Image(image: AssetImage("images/圆加.png"), width: 20),
                      ),
                      // 气泡弹框
                      itemBuilder: _buildPopupMenuItem)),
            ],
          ),
          body: Center(child: Text("聊天页面")),
        );
      }
    }
    

    注意

    1. PopupMenuButtonchild设置按钮内容itemBuilder设置弹框内容,必须是内部元素PopupMenuEntry<T>List数组。(PopupMenuItem继承自PopupMenuEntry

    2. 气泡弹框背景色,需要更改主题背景色cardColor:
      main.dart中关于主题色的代码:

    import 'package:flutter/material.dart';
    import 'package:wechat_demo/pages/root_page.dart';
    
    void main() => runApp(App());
    
    class App extends StatelessWidget {
     @override
     Widget build(BuildContext context) {
       return MaterialApp(
         title: 'Wechat Demo', // 安卓需要,后台切换app时展示的名称(iOS中名称与APP名称一致)
         debugShowCheckedModeBanner: false, // 隐藏debug角标
         home: RootPage(),
         theme: ThemeData(
           primaryColor: Colors.white, // 主题色
           highlightColor: Color.fromRGBO(0, 0, 0, 0), // 去除高亮色
           splashColor: Color.fromRGBO(0, 0, 0, 0), // 去除水波纹
           cardColor: Color.fromRGBO(1, 1, 1, 0.5) // 弹出卡片背景色
         ),
       );
     }
    }
    

    3.2 请求网络,构建聊天视图

    • 当前使用FutureBuildersnapshotwaiting等待时,页面展示loading,其余状态展示MessageCell
    • ListTile类似于iOS的默认UITableViewCell,有title主标题、subtitle子标题、leading首部部件(本案例放图片)

    • 圆角头像:

    Container( width: 40, height: 40, 
               decoration: BoxDecoration( borderRadius: BorderRadius.circular(12.0), 
                                          image: DecorationImage(image: NetworkImage(item.imageUrl))))
    
    • 圆形头像:
    CircleAvatar(backgroundImage: NetworkImage(item.imageUrl))
    
    • chat_page代码:
    import 'dart:convert';
    
    import 'package:flutter/material.dart';
    import 'package:wechat_demo/const.dart';
    
    import 'package:http/http.dart' as http; //导入http库,取别名为http
    
    import 'chat.dart';
    
    class ChatPage extends StatefulWidget {
      @override
      _ChatPageState createState() => _ChatPageState();
    }
    
    class _ChatPageState extends State<ChatPage> {
      // Future表示可能存在错误,记录正在执行的状态
      // async异步请求,配合await使用
      Future<List<Chat>> getDatas() async {
        // 发起请求并等待结果
        final response = await http
            .get('http://rap2api.taobao.org/app/mock/277621/api/chat/list');
    
        // 状态码不为200,抛出错误
        if (response.statusCode != 200) {
          throw Exception('statusCode: ${response.statusCode}');
        }
    
        // response.body是json数据,json转map再转Model
        final responseBody = json.decode(response.body); // Map结构
        List<Chat> chatList = responseBody['chat_list']
            .map<Chat>((item) => Chat.fromJson(item))
            .toList(); // 将列表元素都转换为Chat类型
        // print(chatList.map((item) => print(item.name)));
        return chatList;
      }
    
      // 气泡视图
      List<PopupMenuItem<String>> _buildPopupMenuItem(BuildContext context) {
        return <PopupMenuItem<String>>[
          _buildItem("发起群聊", "发起群聊"),
          _buildItem("添加朋友", "添加朋友"),
          _buildItem("扫一扫1", "扫一扫"),
          _buildItem("收付款", "收付款")
        ];
      }
    
      // 气泡元素
      PopupMenuItem<String> _buildItem(String assetImage, String name) {
        return PopupMenuItem(
            child: Row(children: [
          Image(image: AssetImage("images/${assetImage}.png"), width: 20),
          SizedBox(width: 20),
          Text(name, style: TextStyle(color: Colors.white))
        ]));
      }
    
      // 消息Cell
      Widget _buildMessageCell(Chat item) {
        return Container(child: Column(children: [
          ListTile(
            title: Text(item.name),
            subtitle: Container(height: 20, child: Text(item.message, overflow: TextOverflow.ellipsis)),
            leading: Container( 
                width: 40,  height: 40,
                decoration: BoxDecoration( // 圆角
                    borderRadius: BorderRadius.circular(12.0),
                    image: DecorationImage(image: NetworkImage(item.imageUrl)))),
          ),
          Container(margin: EdgeInsets.only(left: 74), height: 1, color: Wechat_themeColor)
        ]));
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Wechat_themeColor,
            elevation: 0.0,// 隐藏分割线
            centerTitle: true, // 安卓导航栏居中
            title: Text("聊天"),
            actions: <Widget>[
              Container(
                  margin: EdgeInsets.only(right: 20),
                  child: PopupMenuButton(
                      offset: Offset(0, kToolbarHeight),// 偏移值 kToolbarHeight: 导航栏高度
                      child: Container( // 图标
                        margin: EdgeInsets.only(right: 10),
                        child: Image(image: AssetImage("images/圆加.png"), width: 20),
                      ),
                      itemBuilder: _buildPopupMenuItem)),// 气泡弹框
            ],
          ),
          body: FutureBuilder(
            future: getDatas(),
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return Center(child: Text("Loading..."));  // loading
              }
              return ListView(   // 正常展示
                  children: snapshot.data
                      .map<Widget>((item) => _buildMessageCell(item))
                      .toList());
            },
          ),
        );
      }
    }
    
    • 展示样式:


      image.png

    使用FutureBuilder部件构建,每次进入页面都会触发网络请求刷新页面,适用于简单页面
    当前模块,我们需要保留页面状态,不适合使用FutureBuilder,可使用变量(List<Chat>)记录返回值,将数据存储内存中,保留页面状态

    1. 使用上面介绍常规方式,在initState中调用getDatas,用变量_datas记录返回值,通过setState重新构建部件
      getDatas中使用_cancelConnect记录了请求是否取消,默认false每次请求都会重置falsetimeout超时会设置为truethen数据返回时,只有_cancelConnecttrue才会更新数据setState刷新页面。

    2. 通过_datas数据构建body。通过_datas.length区分loading正常数据部件的展示

    3. 使用Mixins(混入)保活当前页面

    Mixins(混入):

    • 类似iOS中的Category分类,用来给类增加功能, 使用【with混入一个或多个mixin(实现多继承的关系)

    • 执行方法:

    【第一步】:state类使用with继承AutomaticKeepAliveClientMixin。例如:

    class _ChatPageState extends State<ChatPage> with AutomaticKeepAliveClientMixin<ChatPage> { ... }
    

    【第二步】:重写wantKeepAlive计算属性。例如:

     @override
     bool get wantKeepAlive => true; 
    

    【第三步】:执行父类build。例如:

      Widget build(BuildContext context) {
       super.build(context); 
       ... 
      }
    
    • 修改后的chat_page.dart代码如下:
    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'package:wechat_demo/const.dart';
    import 'package:http/http.dart' as http; //导入http库,取别名为http
    import 'chat.dart';
    
    class ChatPage extends StatefulWidget {
      @override
      _ChatPageState createState() => _ChatPageState();
    }
    
    class _ChatPageState extends State<ChatPage>
    with AutomaticKeepAliveClientMixin<ChatPage> {  // 第一步,混入(多继承)
    
      @override
      bool get wantKeepAlive => true; // 第二步,重写wantKeepAlive计算属性
      // 存储模型数据
      List<Chat> _datas = [];
      // 记录连接状态
      bool _cancelConnect = false;
    
      @override
      initState() {
        super.initState();
    
        getDatas()
            .then((value) {
              if (_cancelConnect) return; // 已取消连接,不更新数据
              _datas = value;
              setState(() {});
            })
            .catchError((e) => print(e)) // 错误
            .whenComplete(() => print("完毕")) //完毕
            .timeout(Duration(seconds: 6)).catchError((timeout) {
              _cancelConnect = true; // 超时取消连接
              print("超时$timeout");
            });
      }
    
      // Future 记录结果, async异步请求,配合await使用
      Future<List<Chat>> getDatas() async {
        // 每次调用请求,设为false(保证每次主动请求都可执行)
        _cancelConnect = false;
        // 发起请求并等待结果
        final response = await http
            .get('http://rap2api.taobao.org/app/mock/277621/api/chat/list');
        // 状态码不为200,抛出错误
        if (response.statusCode != 200) {
          throw Exception('statusCode: ${response.statusCode}');
        }
    
        // response.body是json数据,json转map再转Model
        final responseBody = json.decode(response.body); // Map结构
        List<Chat> chatList = responseBody['chat_list']
            .map<Chat>((item) => Chat.fromJson(item))
            .toList(); // 将列表元素都转换为Chat类型
        return chatList;
      }
    
      // 气泡视图
      List<PopupMenuItem<String>> _buildPopupMenuItem(BuildContext context) {
        return <PopupMenuItem<String>>[
          _buildItem("发起群聊", "发起群聊"),
          _buildItem("添加朋友", "添加朋友"),
          _buildItem("扫一扫1", "扫一扫"),
          _buildItem("收付款", "收付款")
        ];
      }
    
      // 气泡元素
      PopupMenuItem<String> _buildItem(String assetImage, String name) {
        return PopupMenuItem(
            child: Row(children: [
          Image(image: AssetImage("images/${assetImage}.png"), width: 20),
          SizedBox(width: 20),
          Text(name, style: TextStyle(color: Colors.white))
        ]));
      }
    
      // 构建Body
      Widget _buildBody() {
        if (_datas.length == 0) return Center(child: Text("Loading..."));
        return ListView.builder(
            itemCount: _datas.length, itemBuilder: _buildCellOfRow);
      }
    
      // 构建Cell
      Widget _buildCellOfRow(BuildContext context, int index) {
        final item = _datas[index];
        return Container(
            child: Column(children: [
          ListTile(
            title: Text(item.name),
            subtitle: Container(
                height: 20,
                child: Text(item.message, overflow: TextOverflow.ellipsis)),
            leading: Container(
                width: 40,
                height: 40,
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(12.0),
                    image: DecorationImage(image: NetworkImage(item.imageUrl)))),
          ),
          Container(
              margin: EdgeInsets.only(left: 74),
              height: 1,
              color: Wechat_themeColor)
        ]));
      }
    
      @override
      Widget build(BuildContext context) {
        super.build(context); // 第三步,执行父类build
        return Scaffold(
            appBar: AppBar(
              backgroundColor: Wechat_themeColor,
              elevation: 0.0,
              centerTitle: true, 
              title: Text("聊天"),
              actions: <Widget>[
                Container(
                    margin: EdgeInsets.only(right: 20),
                    child: PopupMenuButton(
                        offset: Offset(0, kToolbarHeight), // 偏移值 kToolbarHeight: 导航栏高度
                        child: Container(// 图标
                          margin: EdgeInsets.only(right: 10),
                          child:
                              Image(image: AssetImage("images/圆加.png"), width: 20),
                        ),
                        itemBuilder: _buildPopupMenuItem)),// 气泡弹框
              ],
            ),
            body: _buildBody()); // 构建Body
      }
    }
    

    app运行后,发现切换tab仍然会重新请求数据刷新页面

    • 这是因为main.dart中,我们每次切换tab,都是返回的bodys[index],每次都重新生成页面,所以无法保留状态
      (如果需要保留状态,页面必须存在渲染树中)

      image.png
    • 我们将main.dartbody改为PageView部件,使用_pageController(PageController类型)记录当前PageView,设置children为四个tab页面,将physics设置为NeverScrollableScrollPhysics()进制左右滑动。

    • 点击Tab时,我们通过_pageController执行jumpToPage切换到指定tab

    import 'dart:ui';
    
    import 'package:flutter/material.dart';
    import 'package:wechat_demo/pages/chat/chat_page.dart';
    import 'package:wechat_demo/pages/discover/discover_page.dart';
    import 'package:wechat_demo/pages/friends/friends_page.dart';
    import 'package:wechat_demo/pages/mine/mine_page.dart';
    
    class RootPage extends StatefulWidget {
      @override
      _RootPageState createState() => _RootPageState();
    }
    
    class _RootPageState extends State<RootPage> {
    
      PageController _pageController = PageController(); // 记录当前PageView控制器
    
      // 点击Tabbar
      Widget onTap(int index) {
        _currentIndex = index;
        setState(() { });
        _pageController.jumpToPage(index); // 调到指定tab页面
      }
    
      // 每个栏目的主页面
      final List<Widget> bodys = [ChatPage(), FriendsPage(), DiscoverPage(), MinePage()];
    
      // 每个栏目的底部Item
      final List<BottomNavigationBarItem> items = [
        BottomNavigationBarItem(
            icon: Image(image: AssetImage('images/tabbar_chat.png'), width: 20),
            activeIcon:
                Image(image: AssetImage('images/tabbar_chat_hl.png'), width: 20),
            label: "聊天"),
        BottomNavigationBarItem(
            icon: Image(image: AssetImage('images/tabbar_friends.png'), width: 20),
            activeIcon:
                Image(image: AssetImage('images/tabbar_friends_hl.png'), width: 20),
            label: "通讯录"),
        BottomNavigationBarItem(
            icon: Image(image: AssetImage('images/tabbar_discover.png'), width: 20),
            activeIcon: Image(
                image: AssetImage('images/tabbar_discover_hl.png'), width: 20),
            label: "朋友圈"),
        BottomNavigationBarItem(
            icon: Image(image: AssetImage('images/tabbar_mine.png'), width: 20),
            activeIcon:
                Image(image: AssetImage('images/tabbar_mine_hl.png'), width: 20),
            label: "我的")
      ];
    
      // 当前选中Index
      int _currentIndex = 0;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.blue,
          body: PageView(
            controller: _pageController, // 记录当前PageView,便于点击tab时,控制跳转
            physics: NeverScrollableScrollPhysics(), // 禁止左右滑动页面 默认AlwaysScrollableScrollPhysics可滚动
            children: bodys,
            // 左右滚动页面
            // onPageChanged: (index) {
            //   _currentIndex = index;
            //   setState(() { });
            // },
          ),
          bottomNavigationBar: BottomNavigationBar(
            type: BottomNavigationBarType.fixed,
            // 固定大小,避免白色背景
            fixedColor: Colors.green,
            // 固定颜色
            currentIndex: _currentIndex,
            // 选择的默认值
            items: items,
            onTap: onTap,
            // 点击回调
            selectedFontSize: 12, // 选择字体大小设置为12(因为默认大小是12,这样可以去掉变大动画)
            // selectedLabelStyle: ,
          ),
        );
      }
    }
    
    • 至此,切换tab时,页面保持原先滚动位置,保持了原有状态

    总结
    本节,我们掌握了:

    1. 【接口Mock】借助RAP接口管理平台Mock接口数据;
    2. 【网络请求】http网络请求库的使用,通过Future获取多状态结果以及try catch链式处理的方法,asyncawait的组合使用,使用_cancelConnect隔离数据和重复请求
      2.1 使用变量内存存储异步数据setState重新构建部件
      2.2 FutureBuilder每次都会异步请求数据重构部件
    3. 【气泡弹框】气泡按钮弹框PopupMenuButton的使用
    4. 【状态保留】 state类通过with继承AutomaticKeepAliveClientMixin,重写wantKeepAlive,执行父类build
    5. 【PageView】多页视图的使用(同时保活多个页面部件)。

    下一节,完善聊天搜索框搜索页面

    相关文章

      网友评论

          本文标题:Flutter入门六:【聊天】+ 网络请求 + 状态保留

          本文链接:https://www.haomeiwen.com/subject/zymxfltx.html