Flutter布局入門
一、Widget簡介
描述
Fullter的核心思想是用widget 來構(gòu)建你的 UI 界面。 Widget 描述了在當(dāng)前的配置和狀態(tài)下視圖所應(yīng)該呈現(xiàn)的樣子。當(dāng) widget 的狀態(tài)改變時,它會重新構(gòu)建其描述(展示的 UI),框架則會對比前后變化的不同,以確定底層渲染樹從一個狀態(tài)轉(zhuǎn)換到下一個狀態(tài)所需的最小更改。
以上是Widget官方解釋,所以Widget和我們通常所說的View不一樣,widget不是一個控件,它是對控件的描述(我們在入門時并不需要太過糾結(jié)Widget是什么,只需知道它是構(gòu)建UI即可)。
下面我們官方demo,神圣的“Hello World”
import 'package:flutter/material.dart';
void main() {
runApp(
Center(
child: Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
這個demo官方文檔有相關(guān)解釋有興趣的可以查看,就不具體贅述了,具體的Center Text會在后面詳解。
1.2、Widget分類
Widget的種類有很多,官方做了如下分類
- Accessibility
- Animation and Motion 給你的應(yīng)用程序添加動畫。
- Assets, Images, and Icons
- Async Flutter應(yīng)用程序的異步Widget。
- Basics 在構(gòu)建第一個Flutter應(yīng)用程序之前,需要知道的Basics Widget。
- Cupertino (iOS-style widgets) iOS風(fēng)格的Widget。
- Input 除了在Material Components和Cupertino中的輸入Widget外,還可以接受用戶輸入的Widget。
- Interaction Models 響應(yīng)觸摸事件并將用戶路由到不同的視圖中。
- Layout 用于布局的Widget。
- Material Components Material Design風(fēng)格的Widget。
- Painting and effects 不改變布局、大小、位置的情況下為子Widget應(yīng)用視覺效果。
- Scrolling 滾動相關(guān)的Widget。
- Styling 主題、填充相關(guān)Widget。
- Text 顯示文本和文本樣式。
Widget詳解
Widget類
在Widget的構(gòu)造方法中有一個參數(shù)key
/// Initializes [key] for subclasses.
const Widget({ this.key });
這個key的作用是用來控制在widget樹中替換widget的時候使用的,key作為唯一標(biāo)識。
State
我們常用的Widget有兩種,StatelessWidget、StatefulWidget,在了解State之前,看下二者之間的區(qū)別,其實(shí)可以根據(jù)字面意思大致猜到
- StatelessWidget:無中間狀態(tài)變化的 widget,需要更新展示內(nèi)容的話,就得通過重新 new。
- StatefulWidget:具有可變狀態(tài)(State)的Widget,在狀態(tài)改變是調(diào)用State.setState()通知狀態(tài)改變,刷新狀態(tài)
State生命周期
State生命周期可以分為三個階段:
- 初始化:
- 狀態(tài)改變:
-
銷毀:
state生命周期
完整的生命周期
initState:widget創(chuàng)建執(zhí)行的第一個方法,可以再里面初始化一些數(shù)據(jù),以及綁定控制器
didChangeDependencies :當(dāng)State對象的依賴發(fā)生變化時會被調(diào)用;例如:在之前build()中包含了一個InheritedWidget,然后在之后的build() 中InheritedWidget發(fā)生了變化,那么此時InheritedWidget的子widget的didChangeDependencies()回調(diào)都會被調(diào)用。InheritedWidget這個widget可以由父控件向子控件共享數(shù)據(jù)。
reassemble:此回調(diào)是專門為了開發(fā)調(diào)試而提供的,在熱重載(hot reload)時會被調(diào)用,此回調(diào)在Release模式下永遠(yuǎn)不會被調(diào)用。
didUpdateWidget:當(dāng)樹rebuid的時候會調(diào)用該方法。
deactivate:當(dāng)State對象從樹中被移除時,會調(diào)用此回調(diào)。
dispose():當(dāng)State對象從樹中被永久移除時調(diào)用;通常在此回調(diào)中釋放資源。
StatelessWidget
class MyStatelessWidget extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Container(color: const Color(0xFF2DBD3A));
}
}
StatelessWidget的build方法通常會在以下三種情況被調(diào)用:
- 將widget插入樹中時
- 當(dāng)widget的父級更改其配置時
- 當(dāng)它依賴的InheritedWidget發(fā)生變化時
StatefulWidget
StatefulWidget是有可變狀態(tài)的Widget。
- 在initState中創(chuàng)建資源,在dispose中銷毀,但是不依賴于InheritedWidget或者調(diào)用setState方法,這類widget基本上用在一個應(yīng)用或者頁面的root;
- 使用setState或者依賴于InheritedWidget,這種在營業(yè)生命周期中會被重建(rebuild)很多次。
class MyHomePage extends StatefulWidget {
const YellowBird({ Key key }) : super(key: key);
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<YellowBird> {
@override
Widget build(BuildContext context) {
return new Container(color: const Color(0xFFFFE306));
}
}
二 基本組件
Container
Container在Flutter很常見。官方給出的簡介,是一個結(jié)合了繪制(painting)、定位(positioning)以及尺寸(sizing)widget的widget。
組成
Container的組成如下:
- 最里層的是child元素;
- child元素首先會被padding包著;
- 然后添加額外的constraints限制;
- 最后添加margin。
Container的繪制的過程如下:
- 首先會繪制transform效果;
- 接著繪制decoration;
- 然后繪制child;
- 最后繪制foregroundDecoration。
Container自身尺寸的調(diào)節(jié)分兩種情況:
- Container在沒有子節(jié)點(diǎn)(children)的時候,會試圖去變得足夠大。除非constraints是unbounded限制,在這種情況下,Container會試圖去變得足夠小。
- 帶子節(jié)點(diǎn)的Container,會根據(jù)子節(jié)點(diǎn)尺寸調(diào)節(jié)自身尺寸,但是Container構(gòu)造器中如果包含了width、height以及constraints,則會按照構(gòu)造器中的參數(shù)來進(jìn)行尺寸的調(diào)節(jié)。
布局行為
由于Container組合了一系列的widget,這些widget都有自己的布局行為,因此Container的布局行為有時候是比較復(fù)雜的。
一般情況下,Container會遵循如下順序去嘗試布局:
- 對齊(alignment);
- 調(diào)節(jié)自身尺寸適合子節(jié)點(diǎn);
- 采用width、height以及constraints布局;
- 擴(kuò)展自身去適應(yīng)父節(jié)點(diǎn);
- 調(diào)節(jié)自身到足夠小。
進(jìn)一步說:
- 如果沒有子節(jié)點(diǎn)、沒有設(shè)置width、height以及constraints,并且父節(jié)點(diǎn)沒有設(shè)置unbounded的限制,Container會將自身調(diào)整到足夠小。
- 如果沒有子節(jié)點(diǎn)、對齊方式(alignment),但是提供了width、height或者constraints,那么Container會根據(jù)自身以及父節(jié)點(diǎn)的限制,將自身調(diào)節(jié)到足夠小。
- 如果沒有子節(jié)點(diǎn)、width、height、constraints以及alignment,但是父節(jié)點(diǎn)提供了bounded限制,那么Container會按照父節(jié)點(diǎn)的限制,將自身調(diào)整到足夠大。
- 如果有alignment,父節(jié)點(diǎn)提供了unbounded限制,那么Container將會調(diào)節(jié)自身尺寸來包住child;
- 如果有alignment,并且父節(jié)點(diǎn)提供了bounded限制,那么Container會將自身調(diào)整的足夠大(在父節(jié)點(diǎn)的范圍內(nèi)),然后將child根據(jù)alignment調(diào)整位置;
- 含有child,但是沒有width、height、constraints以及alignment,Container會將父節(jié)點(diǎn)的constraints傳遞給child,并且根據(jù)child調(diào)整自身。
另外,margin以及padding屬性也會影響到布局。
構(gòu)造方法
Container({
Key key,
this.alignment,
this.padding,
Color color,
Decoration decoration,
this.foregroundDecoration,
double width,
double height,
BoxConstraints constraints,
this.margin,
this.transform,
this.child,
})
平時使用最多的,也就是padding、color、width、height、margin屬性。
屬性解析
alignment:控制child的對齊方式,如果container或者container父節(jié)點(diǎn)尺寸大于child的尺寸,這個屬性設(shè)置會起作用,有很多種對齊方式。
padding:decoration內(nèi)部的空白區(qū)域,如果有child的話,child位于padding內(nèi)部。padding與margin的不同之處在于,padding是包含在content內(nèi),而margin則是外部邊界,設(shè)置點(diǎn)擊事件的話,padding區(qū)域會響應(yīng),而margin區(qū)域不會響應(yīng)。
color:用來設(shè)置container背景色,如果foregroundDecoration設(shè)置的話,可能會遮蓋color效果。
decoration:繪制在child后面的裝飾,設(shè)置了decoration的話,就不能設(shè)置color屬性,否則會報錯,此時應(yīng)該在decoration中進(jìn)行顏色的設(shè)置。
foregroundDecoration:繪制在child前面的裝飾。
width:container的寬度,設(shè)置為double.infinity可以強(qiáng)制在寬度上撐滿,不設(shè)置,則根據(jù)child和父節(jié)點(diǎn)兩者一起布局。
height:container的高度,設(shè)置為double.infinity可以強(qiáng)制在高度上撐滿。
constraints:添加到child上額外的約束條件。
margin:圍繞在decoration和child之外的空白區(qū)域,不屬于內(nèi)容區(qū)域。
transform:設(shè)置container的變換矩陣,類型為Matrix4。
child:container中的內(nèi)容widget。
代碼示例
new Container(
constraints: new BoxConstraints.expand(
height:Theme.of(context).textTheme.display1.fontSize * 1.1 + 200.0,
),
decoration: new BoxDecoration(
border: new Border.all(width: 2.0, color: Colors.red),
color: Colors.grey,
borderRadius: new BorderRadius.all(new Radius.circular(20.0)),
image: new DecorationImage(
image: new NetworkImage('https://hosjoy-iot.oss-cn-hangzhou.aliyuncs.com/images/public/LKM.png'),
centerSlice: new Rect.fromLTRB(270.0, 180.0, 1360.0, 730.0),
),
),
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: new Text('Hello World',
style: Theme.of(context).textTheme.display1.copyWith(color: Colors.black)),
transform: new Matrix4.rotationZ(0.3),
)
其中decoration可以設(shè)置邊框、背景色、背景圖片、圓角等屬性。對于transform這個屬性,一般不是變換的實(shí)際位置,而是變換的繪制效果,也就是說它的點(diǎn)擊以及尺寸、間距等都是按照未變換前的。
Row、Column
Row

Row({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
| header 1 | header 2 |
|---|---|
| MainAxisAlignment | 主軸方向上的對齊方式,會對child的位置起作用,默認(rèn)是start。 |
| MainAxisSize | 在主軸方向占有空間的值,默認(rèn)是max。 |
| CrossAxisAlignment | children在交叉軸方向的對齊方式,與MainAxisAlignment略有不同 |
| VerticalDirection | children擺放順序,默認(rèn)是down。 |
| TextBaseline | 使用的TextBaseline的方式 |
MainAxisAlignment值:
- center:將children放置在主軸的中心;
- end:將children放置在主軸的末尾;
- spaceAround:將主軸方向上的空白區(qū)域均分,使得children之間的空白區(qū)域相等,但是首尾child的空白區(qū)域?yàn)?/2;
- spaceBetween:將主軸方向上的空白區(qū)域均分,使得children之間的空白區(qū)域相等,首尾child都靠近首尾,沒有間隙;
- spaceEvenly:將主軸方向上的空白區(qū)域均分,使得children之間的空白區(qū)域相等,包括首尾child;
- start:將children放置在主軸的起點(diǎn);
MainAxisSize的取值:
- max:根據(jù)傳入的布局約束條件,最大化主軸方向的可用空間;
- min:與max相反,是最小化主軸方向的可用空間;
CrossAxisAlignment的值:
- baseline:在交叉軸方向,使得children的baseline對齊;
- center:children在交叉軸上居中展示;
- end:children在交叉軸上末尾展示;
- start:children在交叉軸上起點(diǎn)處展示;
- stretch:讓children填滿交叉軸方向;
VerticalDirection的值:
- down:從top到bottom進(jìn)行布局;
- up:從bottom到top進(jìn)行布局。
Column

Column與Row類似只是排列方式不同,不再贅述
文本組件Text
構(gòu)造函數(shù)
const Text(
this.data, {
Key key,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.locale,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
})
const Text.rich(
this.textSpan, {
Key key,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.locale,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
})
text有兩個構(gòu)造函數(shù),第一個是默認(rèn),第二個是實(shí)現(xiàn)Text.rich樣式的,和 Android 中的 SpannableString 一樣,具體API屬性如下:
| 屬性 | 含義 |
|---|---|
| textAlign | 文本對齊方式(center居中,left左對齊,right右對齊,justfy兩端對齊) |
| textDirection | 文本方向(ltr從左至右,rtl從右至左) |
| softWare | 是否自動換行(true自動換行,false單行顯示,超出屏幕部分默認(rèn)截斷處理) |
| overflow | 文字超出屏幕之后的處理方式(clip裁剪,fade漸隱,ellipsis省略號) |
| textScaleFactor | 字體顯示倍率 |
| maxLines | 文字顯示最大行數(shù) |
| style | 字體的樣式設(shè)置 |
代碼示例
child: new Column(
children: <Widget>[
new Text(
"Unit 1 Lesson 3 About animal",
style: new TextStyle(
fontFamily: "Round", fontSize: 20, color: Colors.white),
),
Text('默認(rèn)text'),
Padding(
padding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 0.0),
child: Text(
'Flutter擁有豐富的工具和庫,可以幫助您輕松地同時在iOS和Android系統(tǒng)中實(shí)現(xiàn)您的想法和創(chuàng)意。',
//是否自動換行 false文字不考慮容器大小,單行顯示,超出屏幕部分將默認(rèn)截斷處理
softWrap: true,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 0.0),
child: Text(
'Flutter擁有豐富的工具和庫,可以幫助您輕松地同時在iOS和Android系統(tǒng)中實(shí)現(xiàn)您的想法和創(chuàng)意。',
//文字超出屏幕之后的處理方式 TextOverflow.clip剪裁 TextOverflow.fade 漸隱 TextOverflow.ellipsis省略號
overflow: TextOverflow.ellipsis,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 0.0),
child: Text(
'Flutter擁有豐富的工具和庫,可以幫助您輕松地同時在iOS和Android系統(tǒng)中實(shí)現(xiàn)您的想法和創(chuàng)意,可以幫助您輕松地同時在iOS和Android系統(tǒng)中實(shí)現(xiàn)您的想法和創(chuàng)意。',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 0.0),
child: Text(
'文本方向 Flutter擁有豐富的工具和庫,可以幫助您輕松地同時在iOS和Android系統(tǒng)中實(shí)現(xiàn)您的想法和創(chuàng)意。',
//TextDirection.ltr從左至右,TextDirection.rtl從右至左
textDirection: TextDirection.rtl,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 0.0),
child: Text(
'文本對齊方式 Flutter擁有豐富的工具和庫,可以幫助您輕松地同時在iOS和Android系統(tǒng)中實(shí)現(xiàn)您的想法和創(chuàng)意。',
//TextAlign.left左對齊,TextAlign.right右對齊,TextAlign.center居中對齊,TextAlign.justfy兩端對齊
textAlign: TextAlign.center,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
child: Text(
'設(shè)置顏色和大小',
style: TextStyle(
color: const Color(0xfff2c222),
fontSize: 20,
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
child: Text(
'設(shè)置粗細(xì)和斜體',
style: TextStyle(
//字體粗細(xì),粗體和正常
fontWeight: FontWeight.bold,
//文字樣式,斜體和正常
fontStyle: FontStyle.italic,
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
child: Text(
'設(shè)置文字裝飾',
style: TextStyle(
//none無文字裝飾,lineThrough刪除線,overline文字上面顯示線,underline文字下面顯示線
decoration: TextDecoration.underline,
decorationColor: Colors.blue,
decorationStyle: TextDecorationStyle.wavy
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 0.0),
child: Text(
'單詞間隙 hello world',
style: TextStyle(
wordSpacing: 10.0,
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 0.0),
child: Text(
'字母間隙 hello world',
style: TextStyle(
letterSpacing: 10.0,
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
child: GestureDetector(
onTap: () {
print("點(diǎn)擊了按鈕");
},
child: Text(
'設(shè)置文字點(diǎn)擊事件',
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
),
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
child: Text.rich(
new TextSpan(
text: 'Text.rich',
style: new TextStyle(
color: Colors.blueAccent,
fontSize: 10,
decoration: TextDecoration.none),
children: <TextSpan>[
new TextSpan(
text: '拼接測試1',
style: new TextStyle(
color: Colors.blue,
fontSize: 14,
decoration: TextDecoration.none)),
new TextSpan(
text: '拼接測試2',
style: new TextStyle(
color: Colors.black,
fontSize: 18,
decoration: TextDecoration.none)),
new TextSpan(
text: '拼接測試3',
style: new TextStyle(
color: Colors.red,
fontSize: 22,
decoration: TextDecoration.none)),
new TextSpan(
text: '拼接測試4',
style: new TextStyle(
color: Colors.grey,
fontSize: 26,
decoration: TextDecoration.none)),
]),
),
)
],
),

圖片組件Image
構(gòu)造方法
Image({
Key key,
@required this.image,
this.semanticLabel,
this.excludeFromSemantics = false,
this.width,
this.height,
this.color,
this.colorBlendMode,
this.fit,
this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat,
this.centerSlice,
this.matchTextDirection = false,
this.gaplessPlayback = false,
this.filterQuality = FilterQuality.low,
})
| API | 屬性 |
|---|---|
| BoxFit.contain | 顯示整張圖片,按照原始比例縮放顯示 |
| BoxFit.fill | 顯示整張圖片,拉伸填充全部可顯示區(qū)域 |
| BoxFit.cover | 按照原始比例縮放,可能裁剪,填滿可顯示區(qū)域 |
| BoxFit.fitHeight | 按照原始比例縮放,可能裁剪,高度優(yōu)先填滿 |
| BoxFit.fitWidth | 按照原始比例縮放,可能裁剪寬度優(yōu)先填滿 |
| BoxFit.none | 圖片居中顯示,不縮放原圖,可能被裁剪 |
| BoxFit.scaleDown | 顯示整張圖片,只能縮小或者原圖顯示 |
colorBlendMode
圖片顏色混合處理,篇幅有限,有興趣的可以點(diǎn)擊鏈接查看
按鈕類組件(IconButton、RaisedButton)
構(gòu)造方法
const IconButton({
Key key,
this.iconSize = 24.0,
this.padding = const EdgeInsets.all(8.0),
this.alignment = Alignment.center,
@required this.icon,
this.color,
this.highlightColor,
this.splashColor,
this.disabledColor,
@required this.onPressed,
this.tooltip
}) : assert(iconSize != null),
assert(padding != null),
assert(alignment != null),
assert(icon != null),
super(key: key);
| 屬性 | 含義 |
|---|---|
| highlightColor | 按鈕處于按下狀態(tài)時按鈕的輔助顏色。 |
| splashColor | 按鈕處于按下狀態(tài)時按鈕的主要顏色。 |
| disabledColor | 圖標(biāo)組件禁用的顏色,默認(rèn)為主題里的禁用顏色 |
| onPressed | 點(diǎn)擊或以其他方式激活按鈕時調(diào)用的回調(diào)。如果將其設(shè)置為null,則將禁用該按鈕。 |
| tooltip | 描述按下按鈕時將發(fā)生的操作的文本。 |
代碼示例
new IconButton(
padding: EdgeInsets.zero,
iconSize: 60.0,
icon: new Image.asset("assets/images/share_wechat.png"),
onPressed: () {
print("share to wechat");
}),

列表類組件ListView
ListView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
})
| 屬性 | 含義 |
|---|---|
| reverse | 是否反向滾動 |
| controller | 滾動控制器 |
| primary | 是否是與父級PrimaryScrollController關(guān)聯(lián)的主滾動視圖。如果primary為true,controller必須設(shè)置 |
| shrinkWrap | 滾動方向上的滾動視圖的范圍是否應(yīng)由所查看的內(nèi)容決定。 |
| itemExtent | 子元素長度 |
| physics | 列表滾動至邊緣后繼續(xù)拖動的物理效果 |
| cacheExtent | 預(yù)渲染區(qū)域長度 |
代碼示例
child: new ListView.builder(
padding: new EdgeInsets.all(30.0),
itemExtent: 50.0,
itemBuilder: (BuildContext context, int index) {
return new Text("Hosjoy $index");
},
),

網(wǎng)各類組件GridView
構(gòu)造方法
GridView.count({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required int crossAxisCount,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
})
gridView的屬性可以參考ListView,下面列舉GrideView特有的屬性
| 屬性 | 含義 |
|---|---|
| mainAxisSpacing | itme的水平距離 |
| crossAxisSpacing | item的垂直距離 |
| childAspectRatio | item的寬高比 |
| crossAxisCount | 每行的item個數(shù) |
代碼示例
GridView.count(padding: const EdgeInsets.all(20.0),
crossAxisSpacing: 10.0,
crossAxisCount: 3,
children: <Widget>[
const Text('Hosjoy'),
new Image.network('https://hosjoy-iot.oss-cn-hangzhou.aliyuncs.com/images/public/LKM.png',fit: BoxFit.cover),
const Text('Hosjoy'),
const Text('Hosjoy'),
new Image.network('https://hosjoy-iot.oss-cn-hangzhou.aliyuncs.com/images/public/LKM.png',fit: BoxFit.cover),
const Text('Hosjoy'),
const Text('Hosjoy'),
new Image.network('https://hosjoy-iot.oss-cn-hangzhou.aliyuncs.com/images/public/LKM.png',fit: BoxFit.cover),
new Image.network('https://hosjoy-iot.oss-cn-hangzhou.aliyuncs.com/images/public/LKM.png',fit: BoxFit.cover),
],
),

