Flutter学习笔记(五)聊天界面:PopupMenu,ListView的使用以及简单聊天布局

Flutter学习笔记(五)聊天界面:PopupMenu,ListView的使用以及简单聊天布局

    • PopupMenu的使用
    • ListView的使用
    • 简单的聊天布局

距离上一次更新也过了一段时间了,期间忙着学车去了,现在有空再来写一篇。

PopupMenu的使用

为什么要用这个PopupMenu呢,因为大多数消息界面的右上角,都会有一个设置的选项,而这个选项最常用的就是通过PopupMenu来实现。首先来看一下效果图:
Flutter学习笔记(五)聊天界面:PopupMenu,ListView的使用以及简单聊天布局_第1张图片
类似于这样的情况,然后来看代码:

  new PopupMenuButton(
       
       icon: Icon(Icons.settings),
       
       itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
       new PopupMenuItem<String>(
            value: "2",child: new Text("全部已读",style: new TextStyle(fontSize: 14)),),
       new PopupMenuItem(
            value:"1",child: new Text("接受但不提醒",style: new TextStyle(fontSize: 14))),
       new PopupMenuItem(
            value:"0",child: new Text("接受并提醒",style: new TextStyle(fontSize: 14),)),
            ],
       onSelected: (String value){
         setState(() {
            _model = value;
          });
        },
    ),

这个按钮的图标是可以自定义的,然后弹出的选项也可以有很多自定义的样式。注意在选中之后要通过setState()来改变值。

ListView的使用

从上面的图种可以看出,这个消息界面的主题是一个ListView,较为常用的除了普通的ListView,还有ListView.builder和ListView.separated,这几个的区别在于,ListView这种方式适合只有少量的子组件的情况,因为这种方式需要将所有children都提前创建好;而ListView.builder适合列表项比较多(或者无限)的情况,因为只有当子组件真正显示的时候才会被创建;最后一个ListView.separated可以在生成的列表项之间添加一个分割组件,它比ListView.builder多了一个separatorBuilder参数,该参数是一个分割组件生成器。
但是,仅仅是一个简单的列表还是不够的,我们需要给每个item绑定上一个点击事件,跳转到聊天的界面,具体的做法也很简单,就是将整个item包在一个GestureDetector里,然后就可以调用它的onTap,onDoubleTap,onLongPress等一系列跟手势有关的方法。
这还不够,flutter还提供了一个滑动删除item的方法,就是让整个ItemBuilder返回一个Dismissible,然后根据传递的参数index,删除所选的这一行。
下面是实现的代码:

//分割线
Widget divider = Divider(color: Colors.black12, height: 1.0, indent: 18,);

......

//构造带有分割线的ListView
ListView.separated(
   itemCount: _message.length,
   separatorBuilder: (BuildContext context, int index) {
    return divider;
   },
   itemBuilder: (context, index) => EachItem(_message[index],_message,index),)
   
//构造每一个Item的类
class EachItem extends StatelessWidget{

  final Message message;
  final List<Message> _message;
  final int index;
  const EachItem(this.message, this._message, this.index);

  @override
  Widget build(BuildContext context) {
    //滑动删除功能
    return Dismissible(
    //key是必须的,而且是String类型
      key: new Key(message.userName),
      onDismissed: (direction){
        _message.removeAt(index);
      },
      child:Container(
          margin: const EdgeInsets.symmetric(vertical: 10.0),
          //获取手势功能
          child:  GestureDetector(
            child:Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Container(
                    margin: const EdgeInsets.only(left: 14.0,right: 14.0),
                    child: new CircleAvatar(
                      backgroundImage: NetworkImage(message.imageUrl),
                    ),
                  ),
                  Flexible(
                    child:  Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          new Text(message.userName,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                  fontSize: 17.0
                              )
                          ),
                          new Container(
                            margin: const EdgeInsets.only(top: 5.0),
                            child: new Text(message.firstMessage,
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                color: Colors.grey,
                              ),
                            ),
                          )
                        ]
                    ),
                  ),
                ]
            ) ,
            onTap: (){
            //跳转到聊天界面
              Navigator.pushNamed(context,
                  "Chat",
                  arguments: new Chatter(message.userName,message.imageUrl)
              );
            },
          )
      ) ,
    );
  }

这里还有一个小点,就是用命名路由进行传参时,还可以直接传递一个对象作为argument。这里跳转到聊天页面的时候,可以直接传递一个Chatter对象,在chat页面接收参数时,只需要使用

 Chatter chatter = ModalRoute.of(context).settings.arguments;

就可以接收到传递的这个Chatter对象,并且使用其变量参数,比较方便。

但是我在写这段代码时遇到了一个问题,就是在使用滑动删除item的时候,那一行的消息的确删掉了,但是如果删掉的不是首尾而是中间行的话,上下的分割线divider就会重叠在一起,只有点击搜索框或其他触发ListView重新渲染的操作之后才恢复正常,我没有想到什么好的解决办法,麻烦看到的大佬帮助解决以下QAQ

简单的聊天布局

先来看聊天布局的大体效果:
Flutter学习笔记(五)聊天界面:PopupMenu,ListView的使用以及简单聊天布局_第2张图片
主体的代码就是一个ListView加上一个TextField。难度也不是很大,下面看代码:




///发送信息
void _handleSubmitted(String text) {
    _textController.clear();//清空文本框
    Message message = new Message(
      text,true
    );

    setState((){
      _messages.insert(0, message);
    });
  }

//构造输入框
Widget _buildTextComposer(){
    return new Container(
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
        child: new Row(
            children: <Widget> [
              new Flexible(
                  child: new TextField(
                    controller: _textController,
                    onSubmitted: _handleSubmitted,
                    decoration: new InputDecoration.collapsed(hintText: '发送消息'),
                  )
              ),
              new Container(
                margin: new EdgeInsets.symmetric(horizontal: 4.0),
                child: new IconButton(
                    icon: new Icon(Icons.send,color: Colors.blue,),
                    onPressed: () => _handleSubmitted(_textController.text)
                ),
              )
            ]
        )
    );
  }
  
  ......
  
  @override
  Widget build(BuildContext context) {
    
    Widget divider = Divider(color: Colors.white, height: 18.0, indent: 18,);
    ///获得聊天的对象
    Chatter chatter = ModalRoute.of(context).settings.arguments;
    //print(chatter.userName);

    return new Scaffold(
      appBar: AppBar(
        //backgroundColor: Colors.white,
              title: new Text("Chat with "+chatter.userName),
        centerTitle: true,
      ),
      body: new Column(
        children: <Widget>[
          new Flexible(
              child:new ListView.separated(
                separatorBuilder: (BuildContext context, int index) {
                  return divider;
                },
                padding: new EdgeInsets.all(8.0),
                reverse: true,
                itemBuilder: (context, int index) => EntryItem(_messages[index],chatter),
                itemCount: _messages.length,
              ),
          ),
          new Divider(height: 1.0),
          new Container(
            decoration: new BoxDecoration(
              color: Theme.of(context).cardColor,
            ),
            child: _buildTextComposer(),
          )
        ],
      ),
    );
  }
  
......

///发送的信息类
class Message {
  String text;//内容
  bool isSender;//是否由自己发送
  Message(this.text, this.isSender,);
}

///构造发送的信息
class EntryItem extends StatelessWidget{
  final Chatter chatter;
  final Message message;
  const EntryItem(this.message,this.chatter);

  Widget row(){
  	///由自己发送,在右边显示
    if(message.isSender){
      return new Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Flexible(
            child:  Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  new Container(
                    margin: const EdgeInsets.only(top: 5.0),
                    child: new Text(message.text,
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      style: TextStyle(
                        color: Colors.black,
                        fontSize: 16
                      ),
                    ),
                  )
                ]
            ),
          ),
          new Container(
            margin: const EdgeInsets.only(left: 12.0,right: 12.0),
            child: new CircleAvatar(
              backgroundImage: AssetImage("assets/images/user.png"),
              radius: 24.0,
            ) ,
          ),
        ],
      );
    }
    else{
    ///对方发送,左边显示
      return new Row(
        mainAxisAlignment: MainAxisAlignment.start,
        children: <Widget>[
          new Container(
            margin: const EdgeInsets.only(left: 12.0,right: 12.0),
            child: new CircleAvatar(
              backgroundImage: NetworkImage(chatter.imageUrl),
              radius: 24.0,
            ) ,
          ),
          Flexible(
            child:  Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  new Container(
                    margin: const EdgeInsets.only(top: 5.0),
                    child: new Text(message.text,
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      style: TextStyle(
                          color: Colors.black,
                          fontSize: 16
                      ),
                    ),
                  )
                ]
            ),
          ),
        ],
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      child: row(),
    );
  }
}

crossAxisAlignment参数可以决定一个Column的布局方式,以后有空会补充聊天气泡的部分。

参考资料:

  • 《Flutter实战》
  • 实现滑动关闭
  • Flutter实战一Flutter聊天应用
  • Flutter 22: 图解 PopupMenu 那些事儿

你可能感兴趣的:(flutter学习笔记)