用Flutter实现小Q聊天机器人(三)

134 阅读4分钟

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

GitHub:https://github.com/baiyuliang/Qrobot_Flutter

上一篇,我们实现了一个文本+输入框的垂直排列布局,那么本篇,我们来学习如何实现一个Listview;

在这里插入图片描述 依然用最简单的代码实现:

class _MyHomePageState extends State<MyHomePage> {
  var imageModellList = List<ImageModel>();

  @override
  void initState() {
    super.initState();
    for (var i = 0; i < 20; i++) {
      imageModellList.add(ImageModel("标题$i",
          "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2602558426,100251765&fm=27&gp=0.jpg"));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: ListView.builder(
          itemBuilder: (context, index) {
            return Column(children: <Widget>[
              Text(imageModellList[index].title),
              Image.network(imageModellList[index].url)
            ]);
          },
          itemCount: imageModellList.length,
        ));
  }

}

首先在_MyHomePageState 类中声明一个变量:imageModellList,用于存放列表Item的对象,然后初始化方法中去添加一些用于测试的数据,初始化方法initState,顾名思义,在进行一些初始化操作时可以在该方法中进行,我们这里手动的给list添加了20条测试对象;

如何定义类及引用类: ImageModel:

class ImageModel{
  var title;
  var url;

  ImageModel(title,url){
    this.title=title;
    this.url=url;
  }
}

跟java太像了,像的我都不想再做过多说明了,需要了解的是,java中文件名必须与类名像同,但dart却不需要,

在这里插入图片描述

使用该类的时候只需要在需要引用的文件中import即可:

import 'image_model.dart';

然后我们来说ListView,创建ListView组件时要用到builder方法,属性itemBuilder两个参数(context,index)上下文和item的position,其返回的就是item的布局view,itemCount即ListView的item的数量,这里需要注意的是dart中的list的item不能像java中通过list.get()获取,而是通过list[i]获取,长度用list.length表示而不是list.size(),声明完数据,再布局完itemView并完成绑定,listview就可以显示出来了,是不是很简单?

对于如何加载网络图片,只需要用Image的network方法就可以了,Image还有其它更多方法提供:

在这里插入图片描述

根据不同需求选择不同方法!

我们继续来看,这个ListView虽然实现了,但是界面太丑了,我想控制Text的大小、颜色、边距,图片的宽高,裁剪方式等,该如何做呢?别急我们一点一点来实现!

首先Text的大小颜色等属性需要用到TextStyle类,具体写法如下:

style: TextStyle(fontSize: 15,color: Colors.blue),

但由于Text组件本身不具备设置margin及padding属性,那么我们就需要在Text外层套一个Container来设置margin或padding,Container上面说过,可以理解为一个ViewGroup或者h5中的div,代码如下:

  itemBuilder: (context, index) {
            return Column(
                children: <Widget>[
                  Container(
                    padding: const EdgeInsets.all(10),
                    child: Text(
                      imageModellList[index].title,
                      style: TextStyle(fontSize: 15, color: Colors.blue),
                    ),
                  ),
                  Image.network(imageModellList[index].url)
                ]);
          }

运行效果: 在这里插入图片描述 EdgeInsets边距设置的一个类,margin和padding都可以使用,EdgeInsets.all(10)表示上下左右边距全部为10,EdgeInsets.only()则可以单独设置某一方向的边距,例如:EdgeInsets.only(left:10,right:10);

那么我现在还是不满意,想把标题左对齐,然后给图片也加个边距,该如何做呢?

首先,Column和Row默认子组件都是居中的,所以如果想设置子组件的对齐方式,则需要用到两个类和属性:MainAxisAlignmentCrossAxisAlignment,对应的属性为:mainAxisAlignmentcrossAxisAlignmentc

  1. mainAxisAlignment:主轴布局方式,column主轴方向是垂直的方向
    • start ,沿着主轴方向(垂直方向)顶部对齐;
    • end,沿着主轴方向(垂直方向)底部对齐;
    • center,沿着主轴方向(垂直方向)居中对齐;
    • spaceBetween ,沿着主轴方向(垂直方向)平分剩余空间;
    • spaceAround,把剩余空间平分成n份,n是子widget的数量,然后把其中一份空间分成2份,放在第一个child的前面,和最后一个child的后面;
    • spaceEvenly,把剩余空间平分n+1份,然后平分所有的空间,请注意和spaceAround的区别;
  2. crossAxisAlignment: 交叉轴的布局方式,对于column来说就是水平方向的布局方式
    • start ,垂直主轴方向(水平方向)左侧对齐;
    • end,垂直主轴方向(水平方向)右侧对齐;
    • center,垂直主轴方向(水平方向)居中对齐;
    • stretch ,垂直主轴方向(水平方向)拉伸子child;
    • baseline,这个要和textBaseline一起使用;

因此,对于当前页面,想让标题左对齐,那么我们就需要用到属性:

crossAxisAlignment: CrossAxisAlignment.start,

意思为水平方向,从左边开始布局!

itemBuilder: (context, index) {
            return Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.max,
                children: <Widget>[
                  Container(
                    padding: const EdgeInsets.all(10),
                    child: Text(
                      imageModellList[index].title,
                      style: TextStyle(fontSize: 15, color: Colors.blue),
                    ),
                  ),
                  Container(
                    margin: const EdgeInsets.only(left: 10, right: 10),
                    child: Image.network(imageModellList[index].url),
                  )
                ]);
          }

实现效果:

在这里插入图片描述

当我们不知道某个组件具体有哪些属性时,我们只需要Crtl+鼠标点击去这个组件就可以看到对应的属性了,比如Image:

在这里插入图片描述

另外,如果itemBuilder的布局足够复杂,那么这部分代码将足够多,可读性也就越来越差,有什么办法呢?当然!和java一样,提取为一个方法就可以了:

@override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: ListView.builder(
          itemBuilder: (context, index) => buildItem(imageModellList[index]),
          itemCount: imageModellList.length,
        ));
  }

  buildItem(ImageModel imageModel) {
    return Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.max,
        children: <Widget>[
          buildText(imageModel.title),
          buildImage(imageModel.url)
        ]);
  }

  buildText(title) {
    return Container(
      padding: const EdgeInsets.all(10),
      child: Text(
        title,
        style: TextStyle(fontSize: 15, color: Colors.blue),
      ),
    );
  }

  buildImage(url) {
    return Container(
      margin: const EdgeInsets.only(left: 10, right: 10),
      child: Image.network(url),
    );
  }

好了,关于类的创建和引用、listview的使用、基础布局了解和熟悉后,离我们的目标就越来越近了!