Flutter第二章(Image,ListView,GridView)

版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!

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

哈喽!大家好,上一章节介绍了自定义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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容