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,文章內容字數太多,提示我字數限制,那麼我們轉戰下一篇文章繼續!
推薦閱讀: