美文网首页
Flutter第二章(Image,ListView,GridVi

Flutter第二章(Image,ListView,GridVi

作者: 一巴掌拍出两坨脂肪 | 来源:发表于2021-04-09 16:35 被阅读0次
    版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!

    情感语录: 请你相信,岁月会成就最好的自己,时光也必将打磨出你独一无二的美丽。

    哈喽!大家好,上一章节介绍了自定义Widget、Center组件、Text组件、MaterialApp组件、Scaffold组件的简单应用你都掌握了吗?上章知识回顾 戳 Flutter基础第一章相信你在上一章节的练习中已经感受到了 ' Hot Reload 的魅力,Flutter舍弃xml使用代码描述布局,布局的变动能立刻反映出变化,告别了原生中的重新编译安装的过程,你觉得怎样呢?反正我是觉得很爽 O(∩_∩)O

    本章简要:

    本章主要讲解图片组件(Image)、列表组件(ListView)、网格列表组件(GridView)。做过原生开发的同学对这三个组件感觉应该是亲切至极。像极了原生中的ImageView 和ListView以及 GridView控件,在Flutter中用法也是极其相似。

    一、图片组件(Image)

    Image组件是显示图像的组件,用法和原生ImageView大相径庭,但在原生中需要借助Glide或者其他框架才能很方便的加载图片,在Flutter中自身就能实现加载。Image 组件有很多构造函数:

     Image.asset:用来加载当前应用资源图片
    
     Image.network:用来加载网络图片
    
     Image.file:用来加载SD卡(File文件)图片
    
     Image.memory:用来加载 byte[] 字节数组图片
    
     Image:通过ImageProvider来加载图片
    

    1、Image.asset

    加载一个本地资源图片同 IOS 一样,分为 1x,2x,3x ...,具体做法是在项目的根目录下创建倍图文件夹,一倍图直接放入Images目录下,如下图所示:

    image.png

    实例:在屏幕上加载一个宽高300的本地资源图片,代码如下:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(title: Text('呆萌')),
              body: HomeContent(),
            ));
      }
    }
    class HomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Center(
            child: Container(
              child:Image.asset('images/mm.jpg',
              ),
              height: 300,
              width: 300,
            )
        );
      }
    }
    

    本篇文章篇幅可能会很长,这里就不贴效果图了,本章的实战效果会在最后贴出,喜欢尝试的同学可以复制体验下,需要注意的是记得修改你的图片名称哟!!!!

    2、Image.network

    Flutter中加载的无论是普通网络图片还是Gif使用都是如加载本地图片一样,没有原生中那么显得复杂:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(title: Text('呆萌')),
              body: HomeContent(),
            ));
      }
    }
    class HomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Center(
            child: Container(
              //加载本地资源
              //child:Image.asset('images/mm.jpg'),
              //加载网络图片
              child:Image.network('https://upload.jianshu.io/users/upload_avatars/3030564/2789e9ea-9856-456f-be2a-e00ed5992c26.png?imageMogr2/auto-orient/strip|imageView2/1/w/300/h/300/format/webp'),
              height: 300,
              width: 300,
            )
        );
      }
    }
    

    3、Image.file

    加载本地磁盘图片文件,相比加载网络图片要复杂一些,首先在Android目录下的 AndroidManifest.xml 配置读写权限 如下:

     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
    

    读文件当然需要IO包 ,引入 import 'dart:io';

    import 'dart:io';
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(title: Text('呆萌')),
              body: HomeContent(),
            ));
      }
    }
    class HomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Center(
            child: Container(
              //加载本地资源
              //child:Image.asset('images/mm.jpg'),
              //加载网络图片
              //child:Image.network('https://upload.jianshu.io/users/upload_avatars/3030564/2789e9ea-9856-456f-be2a-e00ed5992c26.png?imageMogr2/auto-orient/strip|imageView2/1/w/300/h/300/format/webp'),
              //加载磁盘图片文件
              child:Image.file(File('/storage/emulated/0/Pictures/mm.jpg')),
            //  child:Image.memory(byteList),
              height: 300,
              width: 300,
            )
        );
      }
    }
    

    Duang???磁盘图片文件加载是不是遇到问题了?热加载起来怎么一片空白,图片也不显示,客官别急,先将程序卸载掉,然后重新运行安装便可显示了。这个问题希望后期Flutter会优化吧,如果还是不显示,可能就是android版本是6.0以上的问题,需要动态申请运行权限。还有需要注意的是你需要替换成你本地图片文件的地址哟。

    4、Image.memory

    用来将一个 byte 数组加载成图片,这个使用场景相对较少,后面章节中会有应用到。这里就不做详细介绍了:

    new Image.memory(bytes)
    

    5、ImageProvider 加载占位图

    有的时候我们需要像Android那样使用一个占位图或者图片加载出错时显示某张特定的图片,这时候需要用到 FadeInImage 这个组件:

    import 'dart:io';
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
    
    
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(title: Text('呆萌')),
              body: HomeContent(),
            ));
      }
    }
    class HomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Center(
            child: Container(
               child: new FadeInImage.assetNetwork(
               placeholder: 'images/mm.jpg', //目标图片没显示或者加载失败 显示 该图片
               image: "https://upload.jianshu.io/users/upload_avatars/3030564/2789e9ea-9856-456f-be2a-e00ed5992c26.png?imageMogr2/auto-orient/strip|imageView2/1/w/300/h/300/format/webp",
               width: 300,
               fit: BoxFit.fitWidth,
            ),
              height: 300,
              width: 300,
            )
        );
      }
    }
    

    二、Image组件中的常用属性

    名称                              说明
                                       
    alignment                         图片的对齐方式
    
    color和colorBlendMode             设置图片的背景颜色,通常和 colorBlendMode 配合一起
                                      使用,这样可以是图片颜色和背景色混合。
                                      
    fit                               fit 属性用来控制图片的拉伸和挤压,这都是根据父容器来的。
                                      BoxFit.fill:全图显示,图片会被拉伸,并充满父容器。
                                      BoxFit.contain:全图显示,显示原比例,可能会有空隙。
                                      BoxFit.cover:显示可能拉伸,可能裁切,充满(图片要
                                      充满整个容器,还不变形)。
                                      BoxFit.fitWidth:宽度充满(横向充满),显示可能拉伸,
                                      可能裁切。
                                      BoxFit.fitHeight :高度充满(竖向充满),显示可能拉
                                      伸,可能裁切。
                                      BoxFit.scaleDown:效果和 contain 差不多,但是此属
                                      性不允许显示超过源图片大小,可小不可大。
                                      
    repeat                            平铺 ImageRepeat.repeat : 横向和纵向都进行重复,直到铺满整
                                      个画布。
                                      ImageRepeat.repeatX: 横向重复,纵向不重复。
                                      ImageRepeat.repeatY:纵向重复,横向不重复。
                                      
    width                             宽度 一般结合 ClipOval 才能看到效果
    
    height                            高度 一般结合 ClipOval 才能看到效果
    

    三、ListView组件

    Flutter中的列表组件一样具有Android原生中的ListView控件或者RecyclerView的功效,在写法上我觉的比原生使用更加简单,下面来看ListView组件中常用的一些属性:

    名称                        类型                       说明
    
    scrollDirection             Axis                       Axis.horizontal 水平列表
                                                           Axis.vertical 垂直列表
                                                           
    padding                     EdgeInsetsGeometry         内边距
    
    resolve                     bool                       组件反向排序
    
    children                    List<Widget>               列表元素
    

    1、水平布局

    在Flutter中水平布局相比Android 原生控件简单太多,尤其是在Android 发布RecyclerView 控件之前,你可能要重写一个横向的自定义ListView控件去实现横向菜单,这对于刚入手的同学来说就很难实现了。下面我们利用Flutter ListView组件来实现一个简易的banner

    实例代码:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(title: Text('呆萌')),
              body: BannerView(),
            ));
      }
    }
    
    class BannerView extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: EdgeInsets.all(3),
          height: 120, // 限制Container 的高度,不让子元素listView填充整个屏幕
          child: ListView(
            padding: EdgeInsets.all(10), //10个单位的内边距,离屏幕四周分隔开一点
    
            scrollDirection: Axis.horizontal, // 指定为水平样式
    
            children: <Widget>[
              Container(
                width: 120.0,
                height: 120.0,
                decoration: BoxDecoration(  //设置成圆角卡片样式
                  color: Colors.red,
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              SizedBox(   // 分割线 其实就是给点里右边的距离
                width: 10,
                height: 10,
              ),
              Container(
                width: 120.0,
                height: 120.0,
                decoration: BoxDecoration(
                  color: Colors.orange,
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              SizedBox(
                width: 10,
                height: 10,
              ),
              Container(
                width: 120.0,
                height: 120.0,
                decoration: BoxDecoration(
                  color: Colors.blue,
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              SizedBox(
                width: 10,
                height: 10,
              ),
              Container(
                width: 120.0,
                height: 120.0,
                decoration: BoxDecoration(
                  color: Colors.green,
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              SizedBox(
                width: 10,
                height: 10,
              ),
              Container(
                width: 120.0,
                height: 120.0,
                decoration: BoxDecoration(
                  color: Colors.deepPurpleAccent,
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
            ],
          ),
        );
      }
    }
    

    【温馨提示】 在Flutter 列表组件中不能使用 margin属性的情况下,可以使用SizedBox 组件,或者Container组件都能满足我们的需求。效果如下:

    Banner.gif

    2、垂直布局

    延用上面横向banner这个例子,简单修改下,做成一个垂直的菜单栏,代码如下:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(title: Text('呆萌')),
              body: BannerView(),
            ));
      }
    }
    
    class BannerView extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: EdgeInsets.all(3),
          child: ListView(
            padding: EdgeInsets.all(10), //10个单位的内边距,离屏幕四周分隔开一点
            scrollDirection: Axis.vertical, // 指定为垂直样式
    
            children: <Widget>[
              Container(
    
                alignment: Alignment.center,
                child: new Text(
                  "Java专栏",
                   textAlign: TextAlign.center,
                   style: TextStyle(
                    fontSize: 30.0,
                    color: Colors.white,
                  ),
                ),
                height: 120.0,
                decoration: BoxDecoration(  //设置成圆角卡片样式
                  color: Colors.red,
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              SizedBox(   // 分割线
                width: 10,
                height: 10,
              ),
              Container(
                alignment: Alignment.center,
                child: new Text(
                  "Kotlin专栏",
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 30.0,
                    color: Colors.white,
                  ),
                ),
                height: 120.0,
                decoration: BoxDecoration(  //设置成圆角卡片样式
                  color: Colors.orange,
                  borderRadius: BorderRadius.circular(10),
                ),
    
              ),
              SizedBox(
                height: 10,
              ),
              Container(
                alignment: Alignment.center,
                child: new Text(
                  "Flutter专栏",
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 30.0,
                    color: Colors.white,
                  ),
                ),
                height: 120.0,
                decoration: BoxDecoration(  //设置成圆角卡片样式
                  color: Colors.blue,
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              SizedBox(
                height: 10,
              ),
              Container(
                alignment: Alignment.center,
                child: new Text(
                  "Swift专栏",
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 30.0,
                    color: Colors.white,
                  ),
                ),
                height: 120.0,
                decoration: BoxDecoration(  //设置成圆角卡片样式
                  color: Colors.green,
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              SizedBox(
                height: 10,
              ),
              Container(
                alignment: Alignment.center,
                child: new Text(
                  "Object C",
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 30.0,
                    color: Colors.white,
                  ),
                ),
                height: 120.0,
                decoration: BoxDecoration(  //设置成圆角卡片样式
                  color: Colors.deepPurpleAccent,
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
            ],
          ),
        );
      }
    }
    

    呆萌效果,也是分分钟见高逼格菜单,O(∩_∩)O

    menue.gif

    细心的你肯定发现了一个问题,上面的数据都是提前写死在程序里的,对于常规的固定菜单自然是没问题,但假如我们的数据是动态从服务器请求获取的呢?

    3、ListView组件之ListView.builder

    在Android原生中ListView、RecyclerView是通常是继承BaseAdapter、RecyclerView.Adapter 去实列表布局显示的,在Flutter中也能很轻松去实现同样的效果。

    3.1 列表 item 之 ListTile

    在讲解ListView.builder的用法之前,先看下常配合ListView使用的ListTile,查看源码有如下这么多属性可以配置。

    const ListTile({
        Key key,
        this.leading,              // item 前置图标
        this.title,                // item 标题
        this.subtitle,             // item 副标题
        this.trailing,             // item 后置图标
        this.isThreeLine = false,  // item 是否三行显示
        this.dense,                // item 直观感受是整体大小
        this.contentPadding,       // item 内容内边距
        this.enabled = true,       // itme 状态是否启用 
        this.onTap,                // item onTap 点击事件
        this.onLongPress,          // item onLongPress 长按事件
        this.selected = false,     // item 是否选中状态
    })
    

    下面我们来看下适配器之 ListView.builder 的简单实例:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(title: Text('呆萌')),
              body: HomeContent(),
            ));
      }
    }
    class HomeContent extends StatelessWidget {
    
      //自定义方法
      Widget _getListData(context,index){
    
        return new Container(
    
          child: Column(
            children: <Widget>[
              ListTile(
                 title: Text(musicData[index]["music"]), //设置标题文本内容
                 leading:Image.network(musicData[index]["url"]), //在文本前显示网络图片
                 subtitle:Text(musicData[index]["author"]), // 设置二级标题
                 ),
              //添加一条分割线
              Divider(
                color: Colors.grey,
                height: 1,
              )
            ],
          ),
        );
    
      }
    
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
             //返回数据长度
            itemCount:musicData.length,
    
            //将_getListData 的结果绑定到当前Item上,注意这里没有加(),加()表示执行该方法
            itemBuilder:this._getListData
        );
      }
    }
    
    
    
    // 假设这是从服务器端请求下来的数据
    List musicData=[
    
      {
        "music": '爱情转移',
        "author": '陈奕迅',
        "url": 'http://qukufile2.qianqian.com/data2/pic/af739e0109798366b9419230be5253ce/541222074/541222074.jpg',
      },
      {
        "music": '说谎',
        "author": '林宥嘉',
        "url": 'http://qukufile2.qianqian.com/data2/pic/11fd3062a07e029325e152aac593531a/533505799/533505799.jpg',
      },
      {
        "music": '后来',
        "author": '刘若英',
        "url": 'http://qukufile2.qianqian.com/data2/pic/246708144/246708144.jpg',
      },
      {
        "music": '暖暖',
        "author": '梁静茹',
        "url": 'http://qukufile2.qianqian.com/data2/pic/ad87603bb29cc5ac6ef5945582d56cdd/580337204/580337204.jpg',
      },
      {
        "music": '数天数',
        "author": '龚玥',
        "url": 'http://qukufile2.qianqian.com/data2/pic/246584942/246584942.jpg',
      },
      {
        "music": '大美青海',
        "author": '琼雪卓玛',
        "url": 'http://qukufile2.qianqian.com/data2/pic/318191ab36d37cdf1b7a8f12d7c633ed/611246006/611246006.jpg',
      },
      {
        "music": '爱在心里',
        "author": '刘思嫒',
        "url": 'http://qukufile2.qianqian.com/data2/pic/baf889dd9e0e36efdde4d8c23b1ff944/607931693/607931693.jpg',
      }
    
    ];
    

    上面代码中用到了 Column 组件,这个组件会在后面的章节中讲到,这里看不懂没关系,你可以理解成原生中的 LinearLayout 垂直布局,添加到该容器中的View以此往下排版。Divider组件就是用来显示分割线的,你可以很轻松的配置分割线高度,以及颜色值,当然你也可以不用它,或者使用ListView.separated 带分隔符的列表甚至是Container组件来代替它的使用。

    由于模拟器有点垃圾,一些分割线显示不出来,这里使用手机截屏,效果大致如下:


    红红的截屏.jpg

    你可能会问可不可以不使用 ListView.builde 这玩意去实现吗?查看源码 发现 ListView 的children 接收的是一个 List<Widget>。这个List<Widget>其实就是我们每个Item视图,下面我们通过循环去组装一个列表视图,实现上面一样的效果。

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(title: Text('呆萌')),
              body: HomeContent(),
            ));
      }
    }
    class HomeContent extends StatelessWidget {
    
      //通过循环添加
      List<Widget> _getData(){
    
        List<Widget> list=new List();
    
        for(var i=0;i<musicData.length;i++){
          list.add(Column(
            children: <Widget>[
              ListTile(
                title: Text(musicData[i]["music"]), //设置标题文本内容
                leading:Image.network(musicData[i]["url"]), //在文本前显示网络图片
                subtitle:Text(musicData[i]["author"]), // 设置二级标题
              ),
              //添加一条分割线
              new Container(
                color: Colors.yellow,
                height: 1,
              )
            ],
          ),
          );
        }
        return list;
     }
    
      @override
      Widget build(BuildContext context) {
        return ListView(
          children: this._getData(),
        );
      }
    }
    
    // 假设这是从服务器端请求下来的数据
    List musicData=[
    
      {
        "music": '爱情转移1',
        "author": '陈奕迅',
        "url": 'http://qukufile2.qianqian.com/data2/pic/af739e0109798366b9419230be5253ce/541222074/541222074.jpg',
      },
      {
        "music": '说谎',
        "author": '林宥嘉',
        "url": 'http://qukufile2.qianqian.com/data2/pic/11fd3062a07e029325e152aac593531a/533505799/533505799.jpg',
      },
      {
        "music": '后来',
        "author": '刘若英',
        "url": 'http://qukufile2.qianqian.com/data2/pic/246708144/246708144.jpg',
      },
      {
        "music": '暖暖',
        "author": '梁静茹',
        "url": 'http://qukufile2.qianqian.com/data2/pic/ad87603bb29cc5ac6ef5945582d56cdd/580337204/580337204.jpg',
      },
      {
        "music": '数天数',
        "author": '龚玥',
        "url": 'http://qukufile2.qianqian.com/data2/pic/246584942/246584942.jpg',
      },
      {
        "music": '大美青海',
        "author": '琼雪卓玛',
        "url": 'http://qukufile2.qianqian.com/data2/pic/318191ab36d37cdf1b7a8f12d7c633ed/611246006/611246006.jpg',
      },
      {
        "music": '爱在心里',
        "author": '刘思嫒',
        "url": 'http://qukufile2.qianqian.com/data2/pic/baf889dd9e0e36efdde4d8c23b1ff944/607931693/607931693.jpg',
      }
    
    ];
    

    运行后你发现效果是和上面一模一样的,这里就不贴效果图了,当然在开发中我们还是建议使用 ListView.builde 因为它在内部做了些优化,这就像Android 原生中 ListView控件 使用ViewHolder是一样的,显示条目复用,能节约很大一部分内存和性能。

    四、GridView 组件

    在Flutter中网格布局和Android原生同名依然是采用的GridView命名,用法也是极其简单,这和我们上面讲到的ListView极为相似,下面我们先来看下它的常用属性:

    名称                    类型                             说明
    
    scrollDirection          Axis                            滚动方法
    
    padding                  EdgeInsetsGeometry              内边距
    
    resolve                  bool                            组件反向排序
    
    crossAxisSpacing         double                          水平子 Widget 之间间距
    
    mainAxisSpacing          double                          垂直子 Widget 之间间距
    
    crossAxisCount           int                             一行的 Widget 数量
    
    childAspectRatio         double                          子 Widget 宽高比例
    
    children                                                   <Widget>[ ]
    
    gridDelegate     SliverGridDelegateWithFixedCrossAxisCount             控制布局主要用在GridView.builder 里面
                     SliverGridDelegateWithMaxCrossAxisExtent
    
    4.1 GridView 创建网格列表有多种方式,其中常用的构造函数
    1、GridView.builder 
    
    2、GridView.count 
    
    3、GridView.custom
    
    4、GridView.extent
    

    下面我们主要介绍两种:GridView.builderGridView.count

    嘿哈,看到没?GridView.builder也同样具有ListView.builder一样的适配器,我们来将上面的音乐列表换成网格列表来显示看看 代码如下:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(title: Text('呆萌')),
              body: HomeContent(),
            ));
      }
    }
    class HomeContent extends StatelessWidget {
    
      Widget _getListData(context, index) {
        return Container(
    
          margin: EdgeInsets.all(10),
    
          width: 110,
          height: 110,
          child: Column(
            children: <Widget>[
    
              SizedBox(height: 10),
              Image.network(musicData[index]["url"],width: 110,height: 100,fit: BoxFit.fitWidth),
    
              SizedBox(height: 10),
    
              Text(musicData[index]["author"], textAlign: TextAlign.center,
                  style: TextStyle(
                      fontSize: 20,
                      color: Colors.white
                  )),
            ],
          ),
    
          //绘制圆角背景
          decoration: BoxDecoration(
    
              //圆角10个单位
              borderRadius: BorderRadius.all(Radius.circular(10)),
               //背景色
              color: Colors.deepPurple,
    
              //绘制边框
              border: Border.all(
                  color: Colors.deepPurple,
                  width: 1.0,
    
              )
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return GridView.builder(
    
          //item总数量
          itemCount: musicData.length,
    
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              //横轴元素个数
              crossAxisCount: 2,
              //纵轴间距
              mainAxisSpacing: 10.0,
               //横轴间距
              crossAxisSpacing: 10.0,
              //子组件宽高长度比例
              childAspectRatio: 1.0
          ),
          itemBuilder: this._getListData,
        );
      }
    }
    
    // 假设这是从服务器端请求下来的数据
    List musicData=[
    
      {
        "music": '爱情转移',
        "author": '陈奕迅',
        "url": 'http://qukufile2.qianqian.com/data2/pic/af739e0109798366b9419230be5253ce/541222074/541222074.jpg',
      },
      {
        "music": '说谎11',
        "author": '林宥嘉',
        "url": 'http://qukufile2.qianqian.com/data2/pic/11fd3062a07e029325e152aac593531a/533505799/533505799.jpg',
      },
      {
        "music": '后来',
        "author": '刘若英',
        "url": 'http://qukufile2.qianqian.com/data2/pic/246708144/246708144.jpg',
      },
      {
        "music": '暖暖',
        "author": '梁静茹',
        "url": 'http://qukufile2.qianqian.com/data2/pic/ad87603bb29cc5ac6ef5945582d56cdd/580337204/580337204.jpg',
      },
      {
        "music": '数天数',
        "author": '龚玥',
        "url": 'http://qukufile2.qianqian.com/data2/pic/246584942/246584942.jpg',
      },
      {
        "music": '大美青海',
        "author": '琼雪卓玛',
        "url": 'http://qukufile2.qianqian.com/data2/pic/318191ab36d37cdf1b7a8f12d7c633ed/611246006/611246006.jpg',
      },
      {
        "music": '爱在心里',
        "author": '刘思嫒',
        "url": 'http://qukufile2.qianqian.com/data2/pic/baf889dd9e0e36efdde4d8c23b1ff944/607931693/607931693.jpg',
      }
    
    ];
    
    image.png

    上面通过 GridView.builder 实现网格布局,接下来通过使用 GridView.count 实现上面一样的效果:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(title: Text('呆萌')),
              body: HomeContent(),
            ));
      }
    }
    class HomeContent extends StatelessWidget {
    
    
      List<Widget> _getListData() {
    
        var tempList = musicData.map((value){
    
          return Container(
            
            child:Column(
              children: <Widget>[
    
                SizedBox(height: 10),
    
                Image.network(value["url"],width: 110,height: 100,fit: BoxFit.fitWidth),
    
                SizedBox(height: 10),
    
                Text(value["author"], textAlign: TextAlign.center,
                    style: TextStyle(
                        fontSize: 20,
                        color: Colors.white
                    )),
              ],
    
            ),
            //绘制圆角背景
            decoration: BoxDecoration(
    
              //圆角10个单位
                borderRadius: BorderRadius.all(Radius.circular(10)),
                //背景色
                color: Colors.deepPurple,
                //绘制边框
                border: Border.all(
                  color: Colors.deepPurple,
                  width: 1.0,
                )
            ),
          );
        });
        return tempList.toList();
      }
    
      @override
      Widget build(BuildContext context) {
        return GridView.count(
    
          //水平子 Widget 之间间距
          crossAxisSpacing:10.0 ,
          //垂直子 Widget 之间间距
          mainAxisSpacing: 10.0,   
          padding: EdgeInsets.all(10),
          //一行的 Widget 数量
          crossAxisCount: 2,
          //宽度和高度的比例
          // childAspectRatio:0.7, 
          
          children: this._getListData(),
        );
      }
    }
    
    // 假设这是从服务器端请求下来的数据
    List musicData=[
    
      {
        "music": '爱情转移',
        "author": '陈奕迅',
        "url": 'http://qukufile2.qianqian.com/data2/pic/af739e0109798366b9419230be5253ce/541222074/541222074.jpg',
      },
      {
        "music": '说谎11',
        "author": '林宥嘉',
        "url": 'http://qukufile2.qianqian.com/data2/pic/11fd3062a07e029325e152aac593531a/533505799/533505799.jpg',
      },
      {
        "music": '后来',
        "author": '刘若英',
        "url": 'http://qukufile2.qianqian.com/data2/pic/246708144/246708144.jpg',
      },
      {
        "music": '暖暖',
        "author": '梁静茹',
        "url": 'http://qukufile2.qianqian.com/data2/pic/ad87603bb29cc5ac6ef5945582d56cdd/580337204/580337204.jpg',
      },
      {
        "music": '数天数',
        "author": '龚玥',
        "url": 'http://qukufile2.qianqian.com/data2/pic/246584942/246584942.jpg',
      },
      {
        "music": '大美青海',
        "author": '琼雪卓玛',
        "url": 'http://qukufile2.qianqian.com/data2/pic/318191ab36d37cdf1b7a8f12d7c633ed/611246006/611246006.jpg',
      },
      {
        "music": '爱在心里',
        "author": '刘思嫒',
        "url": 'http://qukufile2.qianqian.com/data2/pic/baf889dd9e0e36efdde4d8c23b1ff944/607931693/607931693.jpg',
      }
    ];
    

    从代码上来看两者的使用其实都很简单,没什么难点。但需要注意的两点是:
    1. GridView.builder 中需要指定条目的数量,itemBuilder 接收是一个方法,至于该方法何时被调用,我们无需关心,因为这一切都在内部帮你实现了,所以再写的时候一定得注意。

    2. GridView.count 虽然无需要指定item数量,因为Item的数量完全取决于 children返回的List<Widget>的长度,所以要显示网格列表,需要提前组装好 children 包裹的内容。

    本章实战

    通过对上面Image组件ListView组件GridView组件的大致了解,本章实战内容就是利用上面学到的组件实现一个图文列表,点击按钮可以从单列表变成网格列表,网格列表又能变成单列表的效果。

    import 'package:flutter/material.dart';
    
    import 'res/TestData.dart';
    
    void main() => runApp(MyApp());
    
    bool isGrid = false;
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          home: new ViewStatefulWidget(),
        );
      }
    }
    
    //自定义Widget 实现有状态StatefulWidget
    class ViewStatefulWidget extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return HomeContent();
      }
    }
    
    class HomeContent extends State<ViewStatefulWidget> {
      void changeView() {
        // 通过 setState() 更新数据,组件树自动刷新
        setState(() {
          if (isGrid) {
            isGrid = false;
          } else {
            isGrid = true;
          }
        });
      }
    
      List<Widget> _getListData() {
        var tempList = musicData.map((value) {
          return Container(
            child: Column(
              children: <Widget>[
                SizedBox(height: 10),
                Image.network(value["url"],
                    width: 110, height: 100, fit: BoxFit.fitWidth),
                SizedBox(height: 10),
                Text(value["author"],
                    textAlign: TextAlign.center,
                    style: TextStyle(fontSize: 20, color: Colors.white)),
              ],
            ),
            //绘制圆角背景
            decoration: BoxDecoration(
                //圆角10个单位
                borderRadius: BorderRadius.all(Radius.circular(10)),
                //背景色
                color: Colors.deepPurple,
                //绘制边框
                border: Border.all(
                  color: Colors.deepPurple,
                  width: 1.0,
                )),
          );
        });
        return tempList.toList();
      }
    
      //通过循环添加
      List<Widget> _getData() {
        List<Widget> list = new List();
    
        for (var i = 0; i < musicData.length; i++) {
          list.add(
            Column(
              children: <Widget>[
                ListTile(
                  title: Text(musicData[i]["music"]), //设置标题文本内容
                  leading: Image.network(musicData[i]["url"]), //在文本前显示网络图片
                  subtitle: Text(musicData[i]["author"]), // 设置二级标题
                ),
                //添加一条分割线
                new Container(
                  color: Colors.black12,
                  height: 1,
                )
              ],
            ),
          );
        }
        return list;
      }
    
      // 根据状态返回不同的Widget
      Widget getWidget() {
        if (isGrid) {
          return ListView(
            children: this._getData(),
          );
        } else {
          return GridView.count(
            //水平子 Widget 之间间距
            crossAxisSpacing: 10.0,
            //垂直子 Widget 之间间距
            mainAxisSpacing: 10.0,
            padding: EdgeInsets.all(10),
            //一行的 Widget 数量
            crossAxisCount: 2,
            //宽度和高度的比例
            // childAspectRatio:0.7,
    
            children: this._getListData(),
          );
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return new Container(
          child: new Scaffold(
              appBar: AppBar(
                title: Text("呆萌"),
                 backgroundColor: Colors.red, //appBar 背景色
                 centerTitle: true, //标题居中显示
                 actions: <Widget>[
                  IconButton(
    
                    icon: Icon(Icons.menu),
                    onPressed: () { //添加按下事件
                      changeView();
                    },
                  )
                ],
              ),
              body: new Container(
                child: getWidget(),
              )),
        );
      }
    }
    

    这里只是演示只简单的写了下样式,你也可以仔细的调整下页面这也是知识点的巩固嘛,最终效果:


    shizhan.gif

    在本章实战中使用到了有状态StatefulWidget 以及点击事件onPressed,点击事件很好理解,至于StatefulWidget 现在看不懂没关系,先尝试用嘛,这会在后面的章节中详细讲到。

    实战源码地址: https://github.com/zhengzaihong/flutter_learn

    好了本章节就此结束,又到了说再见的时候了,如果你喜欢请留下你的小红星,你们的支持才是创作的动力。谢谢大家观看,下章再会 O(∩_∩)O

    相关文章

      网友评论

          本文标题:Flutter第二章(Image,ListView,GridVi

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