美文网首页flutterFlutter
Flutter 布局聊天列表页及网络数据处理

Flutter 布局聊天列表页及网络数据处理

作者: 晨曦的简书 | 来源:发表于2021-11-24 13:55 被阅读0次


    下拉菜单栏实现

    Widget _buildPopupMenuItem(String imgAss, String title) {
        return Row(
          children: [
            Image(image: AssetImage(imgAss), width: 20,),
            SizedBox(width: 20,),
            Text(title, style: TextStyle(color: Colors.white),),
          ],
        );
      }
    
    Container(
                margin: EdgeInsets.only(right: 10),
                child: PopupMenuButton(
                  offset: Offset(0, 50.0),
                  color: Color.fromRGBO(1, 1, 1, 0.65),
                  child: Image(image: AssetImage('images/圆加.png'), width: 25,),
                  itemBuilder: (BuildContext context) {
                    return <PopupMenuItem<String>>[
                      PopupMenuItem(child: _buildPopupMenuItem('images/发起群聊.png', '发起群聊')),
                      PopupMenuItem(child: _buildPopupMenuItem('images/添加朋友.png', '添加朋友')),
                      PopupMenuItem(child: _buildPopupMenuItem('images/扫一扫1.png', '扫一扫')),
                      PopupMenuItem(child: _buildPopupMenuItem('images/收付款.png', '收付款')),
                    ];
                  },
                ),
              )
    

    如图所示,实现这种菜单栏我们可以使用 Flutter 提供的部件 PopupMenuButton 来实现,itemBuilder 属性是一个 PopupMenuItem 类型的数组,这里我们抽取了 _buildPopupMenuItem 方法来创建 item,最后用 PopupMenuItem 部件包装 _buildPopupMenuItem 方法的返回值。

    json 转模型

    class ChatModel {
      final String? name;
      final String? message;
      final String? imageUrl;
      ChatModel({this.name, this.message, this.imageUrl});
    
      //工厂构造方法
      factory ChatModel.fromMap(Map map) {
        return ChatModel(
          name: map['name'],
          message: map['message'],
          imageUrl: map['imageUrl'],
        );
      }
    }
    
    final chatMap = {
          'name' : 'ChenXi',
          'message' : 'Hello!',
        };
        //Map 转 json
        final chatJson = json.encode(chatMap);
        print(chatJson);
    
        // json 转 Map
        final newChatMap = json.decode(chatJson);
        print(chatJson);
    
        final chatModel = ChatModel.fromMap(newChatMap as Map);
        print(chatModel);
    

    这里我们简单定义了一个 map 对象,代码示例中给出里 jsonMap 的相互转换,及 Map 转模型。我们定义了一个 ChatModel 的模型,添加了 fromMap 方法,由外部传入一个 Map 类型的对象。开源的也有一些转模型的框架,这里我们先自己实现。

    Future 使用

    void initState() {
        super.initState();
        //获取网络数据
        _getDatas().then((value) {
          print('$value');
        });
      }
      Future<List<ChatModel>> _getDatas() async {
        //url 链接
        final url = Uri.parse('http://rap2api.taobao.org/app/mock/294394/api/chat/list');
        //发送请求
        final response = await http.get(url);
        if (response.statusCode == 200) {
          //获取响应数据,并且把 json 转成 Map
          final bodyMap = json.decode(response.body);
          // 取出 bodyMap 中的 chat_list 数组,通过 map 方法进行遍历并转为模型,通过 toList 返回一个模型数组
          final chatList = (bodyMap['chat_list'] as List).map((item) => ChatModel.fromMap(item)).toList();
          return chatList;
        } else {
          throw Exception('statusCode:${response.statusCode}');
        }
    

    这里 Future 代表未来的数据,我们对返回的数据通过 Future 包装成 Future<List<ChatModel>>Future 有一个 then 方法,then 有一个外部传入一个闭包属性,当数据请求完成会调用闭包,这里我们可以拿到 value 的值,也就是模型数组。

    FutureBuilder 异步渲染

    body: Container(
            child: FutureBuilder(
              future: _getDatas(),
              builder: (BuildContext context, AsyncSnapshot snapshot) {
                //正在加载
                if (snapshot.connectionState == ConnectionState.waiting) {
                  return Container(
                    child: Text('正在加载!'),
                  );
                }
                //加载完成
                return ListView(
                  children: snapshot.data.map<Widget>((ChatModel item) {
                    return ListTile(
                      title: Text(item.name as String),
                      subtitle: Container(
                        alignment: Alignment.bottomCenter,
                        height: 25,
                        child: Text(item.message as String, overflow: TextOverflow.ellipsis,),
                      ),
                      leading: Container(
                        width: 44,
                        height: 44,
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(6.0),
                          image: DecorationImage(image: NetworkImage(item.imageUrl as String)),
                        ),
                      ),
                    );
                  }).toList(),
                );
              },
            )
          )
    

    这里我们通过 FutureBuilder 部件实现网络数据的加载,FutureBuilder 部件支持异步渲染,future 属性是 Future 类型的数据。在每次进入微信页面的时候 builder 方法最少会被调用两次,没有数据的时候会调用一次,数据来了之后又会调用一次。ConnectionState.waiting 代表数据正在加载,在这里我们可以做一些空页面展示的处理。ConnectionState.done 代表数据加载完成,snapshot.data 就是 _getDatas 方法返回的列表数据,这里可以进行相关逻辑的处理, 这里我们展示聊天列表数据。ListView 中我们用 ListTile 部件来作为 cellListTile 包含主标题 title、副标题 subtitle、头像 leading 等属性,用起来很方便。

    网络请求数据处理

    //模型数组
    List<ChatModel> _datas = [];
    
    void initState() {
        super.initState();
        //获取网络数据
        _getDatas().then((value) {
          if (!_cancelConnect) {
            setState(() {
              _datas = value;
            });
          }
        }).catchError((e) {
          _cancelConnect = true;
          //获取数据失败
          print(e);
        }).whenComplete(() {
          print('数据请求结束');
        }).timeout(Duration(seconds: 5)).catchError((timeout) {
          _cancelConnect = true;
          print('请求超时 ! $timeout');
        });
      }
    

    这里我们创建了一个外部成员变量 _datas,用来保存网络请求的数据,网络请求成功之后调用 setState 方法,定义了一个属性 _cancelConnect 标识网络请求是否取消。catchError 代表请求失败,whenComplete 代表请求结束,timeout 可以设置超时时间,这里我们可以用来做一些 loading 页面的展示及错误页面的展示。

    Container(
              child: _datas.length == 0 ? Center(child: Text('Loading...')) :
              ListView.builder(itemCount: _datas.length ,itemBuilder: (BuildContext context, int index) {
                return ListTile(
                  title: Text(_datas[index].name as String),
                  subtitle: Container(
                    alignment: Alignment.bottomCenter,
                    height: 25,
                    child: Text(_datas[index].message as String, overflow: TextOverflow.ellipsis,),
                  ),
                  leading: Container(
                    width: 44,
                    height: 44,
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(6.0),
                      image: DecorationImage(image: NetworkImage(_datas[index].imageUrl as String)),
                    ),
                  ),
                );
              }),
            )
    

    对于列表的展示我们这里换回了 ListView,使用 FutureBuilder 的话,当数据很多的话需要进行数据的保存,会专门放入一个数组,例如数据的分页加载等,这时候使用 FutureBuilder 就不太好,但是数据量不大的话用 FutureBuilder 就会很方便。

    页面保持状态

    class _ChatPageState extends State<ChatPage> with AutomaticKeepAliveClientMixin<ChatPage>
    
    Widget build(BuildContext context) {
        super.build(context);
    }
    
    class _RootPageState extends State<RootPage> {
      int _currentIndex = 0;
      List <Widget>_pages = [ChatPage(), FriendsPage(), DiscoverPage(), MinePage()];
      final PageController _controller = PageController();
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Scaffold(
            body: PageView(
              //禁止页面拖拽
              physics: NeverScrollableScrollPhysics(),
              onPageChanged: (int index) {
                setState(() {
                  _currentIndex = index;
                });
              },
              controller: _controller,
              children: _pages,
            ),
    

    当我们切换底部 tabBar 的时候,每次进入页面都会重新加载,这里我们采用 AutomaticKeepAliveClientMixin 来保持状态,让页面只会被加载一次,以聊天页面为例,_ChatPageState 后面加上 with AutomaticKeepAliveClientMixin<ChatPage>,并在 build 方法中调用 super.build(context)。在 RootPage 中,用 _pages 数组来保存底部子页面,body 使用 PageView 部件,controller 赋值为我们定义的 _controllerchildren 赋值为 _pages

    Dart 中的异步编程

    通过这个案例我们可以看到 任务 3 会被 for 循环给阻塞,那么我们怎么能把循环给放到异步而不影响其他任务继续执行呢?

    这里我们可以通过 Future 进行包装,把耗时任务放到 Future 中,而不会阻塞 任务 3 的执行。

    如果 任务 4 需要依赖异步耗时任务完成后再执行的话,可以使用 asyncawait 结合的方式。

    当我们需要在耗时任务之后需要执行 任务 4,并且耗时操作不阻塞 任务 4 的执行,这里我们可以定义一个变量 future,调用 then 方法,then 中的闭包会在耗时操作完成之后执行。

    相关文章

      网友评论

        本文标题:Flutter 布局聊天列表页及网络数据处理

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