版權(quán)聲明:本文為作者原創(chuàng)書籍。轉(zhuǎn)載請注明作者和出處,未經(jīng)授權(quán),嚴(yán)禁私自轉(zhuǎn)載,侵權(quán)必究!?。?/a>
情感語錄: 請你相信,歲月會成就最好的自己,時光也必將打磨出你獨一無二的美麗。
哈嘍!大家好,上一章節(jié)介紹了自定義Widget、Center組件、Text組件、MaterialApp組件、Scaffold組件的簡單應(yīng)用你都掌握了嗎?上章知識回顧 戳 Flutter基礎(chǔ)第一章相信你在上一章節(jié)的練習(xí)中已經(jīng)感受到了 ' Hot Reload 的魅力,Flutter舍棄xml使用代碼描述布局,布局的變動能立刻反映出變化,告別了原生中的重新編譯安裝的過程,你覺得怎樣呢?反正我是覺得很爽 O(∩_∩)O
本章簡要:
本章主要講解圖片組件(Image)、列表組件(ListView)、網(wǎng)格列表組件(GridView)。做過原生開發(fā)的同學(xué)對這三個組件感覺應(yīng)該是親切至極。像極了原生中的ImageView 和ListView以及 GridView控件,在Flutter中用法也是極其相似。
一、圖片組件(Image)
Image組件是顯示圖像的組件,用法和原生ImageView大相徑庭,但在原生中需要借助Glide或者其他框架才能很方便的加載圖片,在Flutter中自身就能實現(xiàn)加載。Image 組件有很多構(gòu)造函數(shù):
Image.asset:用來加載當(dāng)前應(yīng)用資源圖片
Image.network:用來加載網(wǎng)絡(luò)圖片
Image.file:用來加載SD卡(File文件)圖片
Image.memory:用來加載 byte[] 字節(jié)數(shù)組圖片
Image:通過ImageProvider來加載圖片
1、Image.asset
加載一個本地資源圖片同 IOS 一樣,分為 1x,2x,3x ...,具體做法是在項目的根目錄下創(chuàng)建倍圖文件夾,一倍圖直接放入Images目錄下,如下圖所示:

實例:在屏幕上加載一個寬高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,
)
);
}
}
本篇文章篇幅可能會很長,這里就不貼效果圖了,本章的實戰(zhàn)效果會在最后貼出,喜歡嘗試的同學(xué)可以復(fù)制體驗下,需要注意的是記得修改你的圖片名稱喲?。。?!
2、Image.network
Flutter中加載的無論是普通網(wǎng)絡(luò)圖片還是Gif使用都是如加載本地圖片一樣,沒有原生中那么顯得復(fù)雜:
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'),
//加載網(wǎng)絡(luò)圖片
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
加載本地磁盤圖片文件,相比加載網(wǎng)絡(luò)圖片要復(fù)雜一些,首先在Android目錄下的 AndroidManifest.xml 配置讀寫權(quán)限 如下:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
讀文件當(dāng)然需要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'),
//加載網(wǎng)絡(luò)圖片
//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會優(yōu)化吧,如果還是不顯示,可能就是android版本是6.0以上的問題,需要動態(tài)申請運行權(quán)限。還有需要注意的是你需要替換成你本地圖片文件的地址喲。
4、Image.memory
用來將一個 byte 數(shù)組加載成圖片,這個使用場景相對較少,后面章節(jié)中會有應(yīng)用到。這里就不做詳細介紹了:
new Image.memory(bytes)
5、ImageProvider 加載占位圖
有的時候我們需要像Android那樣使用一個占位圖或者圖片加載出錯時顯示某張?zhí)囟ǖ膱D片,這時候需要用到 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', //目標(biāo)圖片沒顯示或者加載失敗 顯示 該圖片
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 設(shè)置圖片的背景顏色,通常和 colorBlendMode 配合一起
使用,這樣可以是圖片顏色和背景色混合。
fit fit 屬性用來控制圖片的拉伸和擠壓,這都是根據(jù)父容器來的。
BoxFit.fill:全圖顯示,圖片會被拉伸,并充滿父容器。
BoxFit.contain:全圖顯示,顯示原比例,可能會有空隙。
BoxFit.cover:顯示可能拉伸,可能裁切,充滿(圖片要
充滿整個容器,還不變形)。
BoxFit.fitWidth:寬度充滿(橫向充滿),顯示可能拉伸,
可能裁切。
BoxFit.fitHeight :高度充滿(豎向充滿),顯示可能拉
伸,可能裁切。
BoxFit.scaleDown:效果和 contain 差不多,但是此屬
性不允許顯示超過源圖片大小,可小不可大。
repeat 平鋪 ImageRepeat.repeat : 橫向和縱向都進行重復(fù),直到鋪滿整
個畫布。
ImageRepeat.repeatX: 橫向重復(fù),縱向不重復(fù)。
ImageRepeat.repeatY:縱向重復(fù),橫向不重復(fù)。
width 寬度 一般結(jié)合 ClipOval 才能看到效果
height 高度 一般結(jié)合 ClipOval 才能看到效果
三、ListView組件
Flutter中的列表組件一樣具有Android原生中的ListView控件或者RecyclerView的功效,在寫法上我覺的比原生使用更加簡單,下面來看ListView組件中常用的一些屬性:
名稱 類型 說明
scrollDirection Axis Axis.horizontal 水平列表
Axis.vertical 垂直列表
padding EdgeInsetsGeometry 內(nèi)邊距
resolve bool 組件反向排序
children List<Widget> 列表元素
1、水平布局
在Flutter中水平布局相比Android 原生控件簡單太多,尤其是在Android 發(fā)布RecyclerView 控件之前,你可能要重寫一個橫向的自定義ListView控件去實現(xiàn)橫向菜單,這對于剛?cè)胧值耐瑢W(xué)來說就很難實現(xiàn)了。下面我們利用Flutter ListView組件來實現(xiàn)一個簡易的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個單位的內(nèi)邊距,離屏幕四周分隔開一點
scrollDirection: Axis.horizontal, // 指定為水平樣式
children: <Widget>[
Container(
width: 120.0,
height: 120.0,
decoration: BoxDecoration( //設(shè)置成圓角卡片樣式
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組件都能滿足我們的需求。效果如下:

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個單位的內(nèi)邊距,離屏幕四周分隔開一點
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( //設(shè)置成圓角卡片樣式
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( //設(shè)置成圓角卡片樣式
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( //設(shè)置成圓角卡片樣式
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( //設(shè)置成圓角卡片樣式
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( //設(shè)置成圓角卡片樣式
color: Colors.deepPurpleAccent,
borderRadius: BorderRadius.circular(10),
),
),
],
),
);
}
}
呆萌效果,也是分分鐘見高逼格菜單,O(∩_∩)O

細心的你肯定發(fā)現(xiàn)了一個問題,上面的數(shù)據(jù)都是提前寫死在程序里的,對于常規(guī)的固定菜單自然是沒問題,但假如我們的數(shù)據(jù)是動態(tài)從服務(wù)器請求獲取的呢?
3、ListView組件之ListView.builder
在Android原生中ListView、RecyclerView是通常是繼承BaseAdapter、RecyclerView.Adapter 去實列表布局顯示的,在Flutter中也能很輕松去實現(xiàn)同樣的效果。
3.1 列表 item 之 ListTile
在講解ListView.builder的用法之前,先看下常配合ListView使用的ListTile,查看源碼有如下這么多屬性可以配置。
const ListTile({
Key key,
this.leading, // item 前置圖標(biāo)
this.title, // item 標(biāo)題
this.subtitle, // item 副標(biāo)題
this.trailing, // item 后置圖標(biāo)
this.isThreeLine = false, // item 是否三行顯示
this.dense, // item 直觀感受是整體大小
this.contentPadding, // item 內(nèi)容內(nèi)邊距
this.enabled = true, // itme 狀態(tài)是否啟用
this.onTap, // item onTap 點擊事件
this.onLongPress, // item onLongPress 長按事件
this.selected = false, // item 是否選中狀態(tài)
})
下面我們來看下適配器之 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"]), //設(shè)置標(biāo)題文本內(nèi)容
leading:Image.network(musicData[index]["url"]), //在文本前顯示網(wǎng)絡(luò)圖片
subtitle:Text(musicData[index]["author"]), // 設(shè)置二級標(biāo)題
),
//添加一條分割線
Divider(
color: Colors.grey,
height: 1,
)
],
),
);
}
@override
Widget build(BuildContext context) {
return ListView.builder(
//返回數(shù)據(jù)長度
itemCount:musicData.length,
//將_getListData 的結(jié)果綁定到當(dāng)前Item上,注意這里沒有加(),加()表示執(zhí)行該方法
itemBuilder:this._getListData
);
}
}
// 假設(shè)這是從服務(wù)器端請求下來的數(shù)據(jù)
List musicData=[
{
"music": '愛情轉(zhuǎn)移',
"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": '數(shù)天數(shù)',
"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 組件,這個組件會在后面的章節(jié)中講到,這里看不懂沒關(guān)系,你可以理解成原生中的 LinearLayout 垂直布局,添加到該容器中的View以此往下排版。Divider組件就是用來顯示分割線的,你可以很輕松的配置分割線高度,以及顏色值,當(dāng)然你也可以不用它,或者使用ListView.separated 帶分隔符的列表甚至是Container組件來代替它的使用。
由于模擬器有點垃圾,一些分割線顯示不出來,這里使用手機截屏,效果大致如下:

你可能會問可不可以不使用 ListView.builde 這玩意去實現(xiàn)嗎?查看源碼 發(fā)現(xiàn) ListView 的children 接收的是一個 List<Widget>。這個List<Widget>其實就是我們每個Item視圖,下面我們通過循環(huán)去組裝一個列表視圖,實現(xiàn)上面一樣的效果。
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 {
//通過循環(huán)添加
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"]), //設(shè)置標(biāo)題文本內(nèi)容
leading:Image.network(musicData[i]["url"]), //在文本前顯示網(wǎng)絡(luò)圖片
subtitle:Text(musicData[i]["author"]), // 設(shè)置二級標(biāo)題
),
//添加一條分割線
new Container(
color: Colors.yellow,
height: 1,
)
],
),
);
}
return list;
}
@override
Widget build(BuildContext context) {
return ListView(
children: this._getData(),
);
}
}
// 假設(shè)這是從服務(wù)器端請求下來的數(shù)據(jù)
List musicData=[
{
"music": '愛情轉(zhuǎn)移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": '數(shù)天數(shù)',
"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',
}
];
運行后你發(fā)現(xiàn)效果是和上面一模一樣的,這里就不貼效果圖了,當(dāng)然在開發(fā)中我們還是建議使用 ListView.builde 因為它在內(nèi)部做了些優(yōu)化,這就像Android 原生中 ListView控件 使用ViewHolder是一樣的,顯示條目復(fù)用,能節(jié)約很大一部分內(nèi)存和性能。
四、GridView 組件
在Flutter中網(wǎng)格布局和Android原生同名依然是采用的GridView命名,用法也是極其簡單,這和我們上面講到的ListView極為相似,下面我們先來看下它的常用屬性:
名稱 類型 說明
scrollDirection Axis 滾動方法
padding EdgeInsetsGeometry 內(nèi)邊距
resolve bool 組件反向排序
crossAxisSpacing double 水平子 Widget 之間間距
mainAxisSpacing double 垂直子 Widget 之間間距
crossAxisCount int 一行的 Widget 數(shù)量
childAspectRatio double 子 Widget 寬高比例
children <Widget>[ ]
gridDelegate SliverGridDelegateWithFixedCrossAxisCount 控制布局主要用在GridView.builder 里面
SliverGridDelegateWithMaxCrossAxisExtent
4.1 GridView 創(chuàng)建網(wǎng)格列表有多種方式,其中常用的構(gòu)造函數(shù)
1、GridView.builder
2、GridView.count
3、GridView.custom
4、GridView.extent
下面我們主要介紹兩種:GridView.builder 和 GridView.count
嘿哈,看到?jīng)]?GridView.builder也同樣具有ListView.builder一樣的適配器,我們來將上面的音樂列表換成網(wǎng)格列表來顯示看看 代碼如下:
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總數(shù)量
itemCount: musicData.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//橫軸元素個數(shù)
crossAxisCount: 2,
//縱軸間距
mainAxisSpacing: 10.0,
//橫軸間距
crossAxisSpacing: 10.0,
//子組件寬高長度比例
childAspectRatio: 1.0
),
itemBuilder: this._getListData,
);
}
}
// 假設(shè)這是從服務(wù)器端請求下來的數(shù)據(jù)
List musicData=[
{
"music": '愛情轉(zhuǎn)移',
"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": '數(shù)天數(shù)',
"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',
}
];

上面通過 GridView.builder 實現(xiàn)網(wǎng)格布局,接下來通過使用 GridView.count 實現(xiàn)上面一樣的效果:
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 數(shù)量
crossAxisCount: 2,
//寬度和高度的比例
// childAspectRatio:0.7,
children: this._getListData(),
);
}
}
// 假設(shè)這是從服務(wù)器端請求下來的數(shù)據(jù)
List musicData=[
{
"music": '愛情轉(zhuǎn)移',
"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": '數(shù)天數(shù)',
"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 中需要指定條目的數(shù)量,itemBuilder 接收是一個方法,至于該方法何時被調(diào)用,我們無需關(guān)心,因為這一切都在內(nèi)部幫你實現(xiàn)了,所以再寫的時候一定得注意。
2. GridView.count 雖然無需要指定item數(shù)量,因為Item的數(shù)量完全取決于 children返回的List<Widget>的長度,所以要顯示網(wǎng)格列表,需要提前組裝好 children 包裹的內(nèi)容。
本章實戰(zhàn)
通過對上面Image組件、ListView組件、GridView組件的大致了解,本章實戰(zhàn)內(nèi)容就是利用上面學(xué)到的組件實現(xiàn)一個圖文列表,點擊按鈕可以從單列表變成網(wǎng)格列表,網(wǎng)格列表又能變成單列表的效果。
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 實現(xiàn)有狀態(tài)StatefulWidget
class ViewStatefulWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return HomeContent();
}
}
class HomeContent extends State<ViewStatefulWidget> {
void changeView() {
// 通過 setState() 更新數(shù)據(jù),組件樹自動刷新
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();
}
//通過循環(huán)添加
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"]), //設(shè)置標(biāo)題文本內(nèi)容
leading: Image.network(musicData[i]["url"]), //在文本前顯示網(wǎng)絡(luò)圖片
subtitle: Text(musicData[i]["author"]), // 設(shè)置二級標(biāo)題
),
//添加一條分割線
new Container(
color: Colors.black12,
height: 1,
)
],
),
);
}
return list;
}
// 根據(jù)狀態(tài)返回不同的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 數(shù)量
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, //標(biāo)題居中顯示
actions: <Widget>[
IconButton(
icon: Icon(Icons.menu),
onPressed: () { //添加按下事件
changeView();
},
)
],
),
body: new Container(
child: getWidget(),
)),
);
}
}
這里只是演示只簡單的寫了下樣式,你也可以仔細的調(diào)整下頁面這也是知識點的鞏固嘛,最終效果:

在本章實戰(zhàn)中使用到了有狀態(tài)StatefulWidget 以及點擊事件onPressed,點擊事件很好理解,至于StatefulWidget 現(xiàn)在看不懂沒關(guān)系,先嘗試用嘛,這會在后面的章節(jié)中詳細講到。
實戰(zhàn)源碼地址: https://github.com/zhengzaihong/flutter_learn
好了本章節(jié)就此結(jié)束,又到了說再見的時候了,如果你喜歡請留下你的小紅星,你們的支持才是創(chuàng)作的動力。謝謝大家觀看,下章再會 O(∩_∩)O