1.文本框

///Flutter SDK
const Text(
//文本內容
this.data, {
Key key,
//TextStyle
this.style,
//文本對齊方式
this.textAlign,
//文字方向
this.textDirection,
//true 文字自動換行顯示,false文字不自動換行顯示
this.softWrap,
//文本溢出的時候,怎麼展示
//TextOverflow.clip:裁剪溢出的文本內容
//TextOverflow.fade:溢出的文本淡入透明
//TextOverflow.ellipsis:溢出的文本用省略號代替
this.overflow,
//縮放大小,默認是1.0,如果設置1.5則比之前的文本大50%
this.textScaleFactor,
//最多展示幾行文本
this.maxLines,
})

上面是普通的Text文本,下面我們看看「富文本」:

const RichText({
Key key,
//TextSpan,可以想像下android中的spannableString或者html.fromhtml之類的用法
@required this.text,
this.textAlign: TextAlign.start,
this.textDirection,
this.softWrap: true,
this.overflow: TextOverflow.clip,
this.textScaleFactor: 1.0,
this.maxLines,
})
const TextSpan({
//TextStyle
this.style,
//文本內容
this.text,
//List<TextSpan>
this.children,
//手勢識別器
this.recognizer,
})

我們再看看TextStyle有哪些特性?

const TextStyle({
this.inherit: true,
//字體顏色
this.color,
//字體大小
this.fontSize,
this.fontWeight,
this.fontStyle,
//每個單詞之間的間距
this.letterSpacing,
//單詞內間距,看下面效果圖:」world!「黃色背景的那個效果圖
this.wordSpacing,
this.textBaseline,
//文本高度,修飾了Text文本框之後,文本框容器高度就會變高,默認是1.0
//高度為字體大小的倍數
this.height,
//語言
this.locale,
//背景色,用法如:new Paint();paint.color=Colors.amber;
this.background,
//文本內容裝飾器,如下劃線,刪除線等
this.decoration,
//文本裝飾器顏色
this.decorationColor,
//文本裝飾樣式,如虛線,實線,水波紋等
this.decorationStyle,
this.debugLabel,
String fontFamily,
String package,
})

RichText

關鍵代碼如下:

new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new RichText(
text: new TextSpan(
text: Hello ,
style: DefaultTextStyle
.of(context)
.style,
children: <TextSpan>[
new TextSpan(text: bold,
style: new TextStyle(
color: Colors.green, fontWeight: FontWeight.bold)),
new TextSpan(text: world!,
style: new TextStyle(
wordSpacing: 20.0, background: textPaintColor,)),
],
),
),
new RichText(
text: new TextSpan(
text: 刪除線 ,
style: new TextStyle(color: Colors.black,
decorationColor: Colors.red,
decoration: TextDecoration.lineThrough,
decorationStyle: TextDecorationStyle.solid),
children: <TextSpan>[
new TextSpan(text: 下劃線,
style: new TextStyle(color: Colors.red,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.solid)),
new TextSpan(text: 兩個下劃線,
style: new TextStyle(color: Colors.brown,
decorationColor: Colors.lightGreen,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.double)),
],
),
),
new Padding(padding: EdgeInsets.all(30.0),
child: new RichText(
text: new TextSpan(
text: 水波紋 ,
style: new TextStyle(color: Colors.lightGreen,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.wavy),
children: <TextSpan>[
new TextSpan(text: 上劃線,
style: new TextStyle(color: Colors.red,
decoration: TextDecoration.overline,
decorationStyle: TextDecorationStyle.solid)),
new TextSpan(text: 虛線下劃線,
style: new TextStyle(color: Colors.brown,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.dashed)),
],
),
),)
],
)

二、可擴展的列表

android中用的是android.widget.ExpandableListView。

我們先講默認提供的ExpansionPanelList(這個效果真醜),

然後再介紹如何實現android中的ExpandableListView效果。

1.首先我們先來說說"ExpansionPanelList":

Flutter提供的ExpansionPanelList真醜,醜到想吐,只能點擊最右邊的箭頭才能展開和關閉

視頻封面

需要注意的一點是ExpansionPanelList他的父節點的主軸必須擁有無限空間,不受約束!!!

performLayout() 斷言會去檢測mainAxis,不符合條件的拋出異常:

throw new FlutterError(
RenderListBody must have unlimited space along its main axis.

RenderListBody does not clip or resize its children, so it must be
placed in a parent that does not constrain the main
axis. You probably want to put the RenderListBody inside a
RenderViewport with a matching main axis.
);

Flex小部件都可以包裹ExpansionPanelList,鑒於是可擴展的列表,我們需要用一個可滑動的小部件包裹他,便於滑動數據;ExpansionPanelList本身不支持滑動。

我們可以使用ListView包裹ExpansionPanelList也可以使用SingleChildScrollView來包裹ExpansionPanelList。

這篇文章我們使用SingleChildScrollView來包裹,代碼如下:

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appTitle = ExpansionPanelListDemo;
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(appTitle),
),
body: new SingleChildScrollView(
child: MyCustomExpandList(),
),
),
);
}
}

// Create a ExpandList Widget
class MyCustomExpandList extends StatefulWidget {
@override
MyCustomExpandListState createState() {
return MyCustomExpandListState();
}
}

class MyCustomExpandListState extends State<MyCustomExpandList> {
var currentPanelIndex=-1;
List<int> items;
MyCustomExpandListState(){
items=new List();
for(int i=0;i<16;i++){
items.add(i);
}
}

@override
Widget build(BuildContext context) {
return new ExpansionPanelList(
expansionCallback:(int panelIndex, bool isExpanded){
print(panelIndex:$panelIndex);
setState(() {
currentPanelIndex=(currentPanelIndex!=panelIndex?panelIndex:-1);
});
},
children:items.map((i){
return new ExpansionPanel(
headerBuilder: (context,isExpanded){
return new ListTile(
title: new Text(女逃犯在五月天演唱會上落網:$i),
);
},
body: new Padding(padding: const EdgeInsets.only(left: 10.0,right: 10.0,bottom: 10.0),
child: new ListBody(
children: <Widget>[
new Text($i個標題下的內容:),
new Text(6月30日17時許,在「五月天世界巡迴演唱會」廣東佛山站抓獲一可疑女子,最終確認其是全國在逃人員黎某,早前因扒竊被廣西警方追逃。

現場執勤的派出所民警覈查進場觀眾身份信息時,發現一名女子與廣西在逃人員的身份信息吻合。面對民警的詢問,女子神色慌張,眼神閃爍,甚至聲稱連演唱會都不想看了,想要離開。

民警隨後將其帶回覈查,最終確認其是全國在逃人員黎某,早前因扒竊被廣西警方追逃。目前,黎某正移交廣西警方處理),
],
),),
isExpanded: currentPanelIndex==i
);
}).toList(),
);
}
}

2.下面我們來實現下android和IOS中的擴展列表效果

「展開/摺疊」列表效果需要使用:ExpansionTiles,使用ListView.builder()創建列表相當於android中RecycleView,可復用列表。

同時ExpansionTile還可以套ExpansionTile,可以展開多級,想展開多少級就多少級。

下面進入正題,先創建一個UserInfo.dart:

class UserInfo {
///這裡我們創建構造方法的時候使用可選的命名參數 {param1, param2, …}
///調用函數時,可以使用指定命名參數。
const UserInfo({
@required this.title,
this.subTitle:這傢伙很懶,什麼都沒有寫,
this.children = const <UserInfo>[]
}) : assert(title !=null);
//assert: debug模式下會斷言檢查,release模式下會自動忽略

final String title;
final String subTitle;
final List<UserInfo> children;
}

有了上面的實體類,我們接下來就可以定義成像QQ好友列表那樣的數據了,不過我們這個實體類,比qq那個列表更加多樣化,可以在分組下建分組。

下面定義一個聯繫人列表集合:

final List<UserInfo> data = <UserInfo>[
const UserInfo(
title:我的好友,
children:<UserInfo>[
const UserInfo(title:張三,subTitle:xxxxx),
const UserInfo(title:李四,subTitle:xxxxxxxxxx),
],
),
//省略,讀者自己補上..........
];

先聲明一個回調方法:

typedef void OnItemClickListener(UserInfo info);

接下來創建EntryItem無狀態小部件,定義兩個成員變數userInfo和onItemClickListener:

///根據UserInfo構建一個條目內容:
Widget _buildTiles(UserInfo root) {
if (root.children.isEmpty)

///展開的內容item
return new Container(
padding: const EdgeInsets.all(8.0),
child: new ListTile(
onTap: () {
if (null != onItemClickListener) {
this.onItemClickListener(root);
}
},
leading: new CircleAvatar(
backgroundImage: new AssetImage(images/zhubo01.jpg),
),
title: new Text(root.title),
subtitle: new Text(root.subTitle),
),
);
///內容父標題
return new ExpansionTile(
//記錄滾動位置
key: new PageStorageKey<UserInfo>(root),
title: new Text(root.title),
//遍歷下面的childEntry
children: root.children.map(_buildTiles).toList(),
);
}

在EntryItem的build(BuildContext context)內直接return _buildTiles(userInfo);即可,字數限制,部分代碼就不貼了。

最終使用如下:

///使用ListView.builder創建列表,復用列表
new ListView.builder(
itemBuilder: (BuildContext context, int index) =>
new EntryItem(data[index], (info) {
//這裡省略SnackBar顯示內容代碼,讀者自己補上
//new Text(info.title + "
" + info.subTitle)

}),
itemCount: data.length,
)

結尾來個效果:

視頻封面

三、Switch按鈕

Switch

Switch按鈕代碼如下:

class MySwitchState extends State<MySwitch> {
var isOpen=false;
@override
Widget build(BuildContext context) {
return new Switch(value: isOpen, onChanged: (bool){
setState(() {
isOpen=bool;
});
});
}
}

下面我們看看SwitchListTile效果以及怎麼使用:

SwitchListTile

class MySwitchListTileState extends State<MySwitchListTile> {
var isOpen=false;
@override
Widget build(BuildContext context) {
return new SwitchListTile(value: isOpen, onChanged: (bool){
setState(() {
isOpen=bool;
});
},
title:new Text(title),
subtitle: new Text("subTitle"),
activeColor:Colors.amber,
activeThumbImage:new AssetImage(images/zhubo01.jpg),
inactiveThumbImage: new AssetImage(images/zhubo02.jpeg),
secondary:new Container(
decoration: new BoxDecoration(
shape: BoxShape.circle,
border: new Border.all(color: Colors.amber,width: 1.5)
),
child: new CircleAvatar(
backgroundImage: new AssetImage(images/zhubo01.jpg),
),
),
);
}
}

四、RatingBar 評分控制項

Flutter沒有提供給我們這樣的控制項,就目前而言,我沒有找到,下面我們自己定義一個。

定義一個能夠滑動選擇評分,點擊星星評分的兩個操作,同時支持配置多個星星。

同時我們提供一個flag用作「列表」或「圖文」展示時候顯示的評分控制項,不走供手勢操作回調。如果你想實現自己特殊需求的評分控制項,我建議你自己弄吧。

先給懶人貼上github上評分控制項代碼鏈接地址:

TheMelody/FlutterRatingbar?

github.com
圖標

再看看實現的最終效果:

視頻封面

我們需要定義以下幾個參數來控制評分控制項:

1.默認展示的評分數值rating;2.星星數量starCount;

3.星星大小starSize;

4.星星選中的顏色color;5.添加一個回調方法,回調當前評分的數值;

進入正題,有了上面的介紹,我們先聲明一個回調方法:

///評分數值變化回調
typedef void RatingChangeCallback(double rating);

接下來定義一個StarRatingBarWidget有狀態的小部件:

///創建一個StarRatingBar Widget
class StarRatingBarWidget extends StatefulWidget {
final double rating;
final int starCount;
final double starSize;
final Color color;
final RatingChangeCallback onRatingCallback;

StarRatingBarWidget(
{Key key,
this.onRatingCallback,
this.rating = 0.0,
this.starCount,
this.starSize,
this.color});

@override
StarRatingBarState createState() {
return StarRatingBarState(
onRatingCallback: this.onRatingCallback,
rating: this.rating,
starCount: this.starCount,
starSize: this.starSize,
color: this.color);
}
}

再接下來,我們創建StarRatingBarState:

///創建一個 StarRatingBar State
class StarRatingBarState extends State<StarRatingBarWidget> {
double rating;
final int starCount;
final double starSize;
final Color color;
final RatingChangeCallback onRatingCallback;

StarRatingBarState(
{this.onRatingCallback,
this.rating,
this.starCount,
this.starSize,
this.color});

///通過滑動的距離來計算出當前評分數值
double getRatingValue(double dragDx) {
if (dragDx <= 0) return 0.0;
//評分控制項寬度
double totalWidth = starSize * starCount + 4;
//單個星星佔據的空間距離
double singleDistance = totalWidth / starCount + 2;
for (int i = 1; i <= starCount; i++) {
if (dragDx < singleDistance * i) {
if (dragDx < singleDistance * (i * 2 - 1) / 2) {
return (i * 2 - 1) / 2;
}
return i * 1.0;
}
}
return starCount * 1.0;
}

@override
Widget build(BuildContext context) {
void postInvalidateRatingValue(double rating) {
this.onRatingCallback(rating);
setState(() {
this.rating = rating;
});
}

return new GestureDetector(
//水平拖拽位置更新,注意:onRatingCallback此處做非空判斷,如果為空,則不處理水平滑動手勢
onHorizontalDragUpdate: onRatingCallback == null? null : (DragUpdateDetails details) {
RenderBox getBox = context.findRenderObject();
//details.globalPosition更新時顯示的全局位置Offset
//globalToLocal將全局坐標轉換為當前盒子內部的坐標
double dragDx = getBox.globalToLocal(details.globalPosition).dx;
print("當前控制項內部滑動距離:$dragDx");
postInvalidateRatingValue(getRatingValue(dragDx));
},
child: new StarRating(
rating: this.rating,
starCount: this.starCount,
starSize: this.starSize,
color: this.color,
//注意:此處也做onRatingCallback非空判斷,如果為空不做點擊操作響應
onRatingChanged: onRatingCallback == null ? null: (rating) => postInvalidateRatingValue(rating),
),
);
}
}

最後我們定義水平的星星列表,它是無狀態的,所以我們來實現下StartRating小部件:

///構建星星列表
class StarRating extends StatelessWidget {
final int starCount;
final double rating;
final double starSize;
final Color color;
final RatingChangeCallback onRatingChanged;

StarRating(
{@required this.starCount,
@required this.starSize,
this.rating,
this.onRatingChanged,
this.color});

Widget buildStar(BuildContext context, int index) {
Icon icon;
if (index >= rating) {
icon = new Icon(
Icons.star_border,
color: Theme.of(context).buttonColor,
size: starSize,
);
} else if (index > rating - 1 && index < rating) {
icon = new Icon(
Icons.star_half,
color: color ?? Theme.of(context).primaryColor,
size: starSize,
);
} else {
icon = new Icon(
Icons.star,
color: color ?? Theme.of(context).primaryColor,
size: starSize,
);
}
///如果你對水波紋有迷之熱愛,你可以用InkResponse/InkWell/IconButton等包裝即可
return new GestureDetector(
onTap:
onRatingChanged == null ? null : () => onRatingChanged(index + 1.0),
child: icon,
);
}

@override
Widget build(BuildContext context) {
//生成水平展示的星星列表
return new Row(
children:
new List.generate(starCount, (index) => buildStar(context, index)));
}
}

上面代碼全部貼完,我們看看如何調用?

//星星數量,大小,顏色,默認展示的評分數都可以指定
//如果onRatingCallback不寫,那麼評分控制項不響應滑動和點擊事件
new Container(
width: 200.0,
height: 50.0,
child: StarRatingBarWidget(
rating: 3.5,
starSize: 36.0,
starCount: 5,
color: Colors.amber,
onRatingCallback: (ratingValue) {
print(當前選了:$ratingValue);
},
),
)

自定義評分控制項小結:

在做評分控制項的時候,手勢GestureDetector的onHorizontalDragUpdate回調參數DragUpdateDetails的globalPosition更新時顯示的全局位置Offset,我們不能直接使用它,而且評分的時候,滑動也是針對評分控制項內部做移動距離處理,所以我們需要轉換下我們的坐標位置。

我們先通過buildContext獲取RenderBox:

RenderBox getBox = buildContext.findRenderObject();

大家點擊BuildContext的源碼進去看看RenderObject注釋:

It should only be called from interaction event handlers (e.g.

gesture callbacks) or layout or paint callbacks.

我們需要使用RenderBox中的globalToLocal來轉換下坐標:

///將全局坐標的給定點轉換為此框的局部坐標系
Offset globalToLocal(Offset point, { RenderObject ancestor })

五、ProgressBar

在Flutter中進度條父類:ProgressIndicator,它有兩個子類

LinearProgressIndicator和CircularProgressIndicator 使用起來非常簡單

new LinearProgressIndicator()//水平進度載入圈
new CircularProgressIndicator()//圓形載入圈

我貼出來圓形圈圈效果,和android效果一致:

視頻封面

六、ScrollView

1.CustomScrollView

允許您直接提供[slivers]來創建各種滾動效果,例如列表、網格和展開頭。例如,要創建一個滾動視圖,其中包含一個擴展的應用程序欄,後面跟著一個列表和一個網格,可以使用一個由三個條組成的列表:[SliverAppBar]、[SliverList]和[SliverGrid]。

視頻封面

我直接貼出代碼,注釋基本上100%說明如何使用:

new CustomScrollView(
///slivers內:放置多個碎片,需要注意的是我們只能使用下面這三種:
///[SliverAppBar], [SliverList]或[SliverFixedExtentList],[SliverGrid]
///SliverAppBar:用於actionbar展示伸展縮放動畫
///SliverList或SliverFixedExtentList:展示列表數據的
///SliverGrid展示網格列表數據的
///
///SliverList和SliverFixedExtentList比較:
///如果子類在主軸上有固定的範圍,則考慮使用[SliverFixedExtentList]而不是[SliverList]
///因為[SliverFixedExtentList]不需要對其子類執行佈局就能在主軸上獲得它們的範圍區間大小,
///因此更有效率.
slivers: <Widget>[
///AppBar
new SliverAppBar(
//列表在滾動的時候appbar是否一直保持可見
pinned: true,
expandedHeight: 150.0,
//flexibleSpace高度和appbar高度一致
//FlexibleSpaceBar:一個靈活空間控制appbar擴展和伸縮
flexibleSpace: const FlexibleSpaceBar(
title: const Text(SliverAppBar),
//centerTitle值表示:appbar縮回去之後是否居中展示title
centerTitle: true,
//背景,final Widget background;
//我們要使用的Image對象必須是const聲明的常量對象,對象不可變
background: const Image(
image: const AssetImage("images/lake.jpg"),
fit: BoxFit.cover,
),
),
actions: <Widget>[
new IconButton(
icon: const Icon(
Icons.search,
color: Colors.white,
),
tooltip: Search,
onPressed: () {
/* search content */
},
),
]),

///SliverList或SliverFixedExtentList
new SliverFixedExtentList(
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//構建一個測試列表
return new Container(
alignment: Alignment.center,
color: Colors.blueGrey,
child: new Text(
SliverFixedExtentList,
textAlign: TextAlign.center,
),
);
},
childCount: 1,
),

///強制孩子在滾動方向上有指定的範圍
///指定itemExtent比讓孩子確定自己的範圍更有效率
itemExtent: 200.0),

///SliverGrid
new SliverGrid(
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
///這裡實現自己的gridItem即可
return new Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: new Text(grid item $index),
);
},
childCount: 18,
),

///委託item都是最大範圍
gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
//均勻劃分網格範圍,範圍最多為maxCrossAxisExtent的值
//example:如果網格是垂直的,網格為500.0像素寬,
//並且[maxCrossAxisExtent]為150.0(將要劃分的網格範圍最多不超過150)
//此委託將創建一個網格,其中4列寬為125.0像素。
maxCrossAxisExtent: 180.0,
//主軸,橫向,item之間的間距
mainAxisSpacing: 1.0,
//縱軸,item之間的間距
crossAxisSpacing: 4.0,
//寬/高的比例
childAspectRatio: 1.0,
)),
],
)

代碼光看是不行的,你們得跟著我的代碼「敲」!然後在敲的過程中進行自己的理解。

2.SingleChildScrollView 可以讓單個小部件進行滾動,和android中的ScrollView功能一樣

3.NestedScrollView

NestedScrollView和CustomScrollView相同點都是可以創建滾動視圖;

不同點是它可以創建一個[SliverAppBar]+[TabBar]組合的頭部,body體中通常使用[TabBarView],body體中默認使用[PrimaryScrollController]控制器。

先實現一個不含Tab欄的(盡在注釋中解釋):

視頻封面

WTF,文章內容字數太多,提示我字數限制,那麼我們轉戰下一篇文章繼續!

推薦閱讀:

相關文章