本人主要在知乎上釋出相關Flutter文章,知乎瞭解下:
https://www.zhihu.com/people/qiang-fu-5-67/activities
我們來實戰剖析下“企鵝電競”直播欄下怎麼實現:
程式碼我儘量寫的大家淺顯易懂,不裝逼,不多嗶嗶,點關注不迷路。
企鵝電競app截圖:
下面看下最終實現的效果圖:
佈局解析成下面一個簡單的圖例:
整體佈局結構如下:
我們把內容部分拆分成了幾個方法體:
_buildReminder()實現提醒內容區域==>預定按鈕那一行
_buildRecommedList()實現橫向的推薦列表==>吸金榜,周禮榜,真愛榜
_buildContentImageText()實現直播推薦下方的網格列表中的一個單元格內容
複製程式碼
在實踐過程中用了好幾個實現方式,
其中一種是ScrollView方式,在Flutter中通過CustomScrollView實現,小部件使用SliveFixedExtendList和SliverGrid。
這種實現方式寫完才發現,尼瑪grid不支援自定義title,不像Android中recycleView那麼好,這尼瑪就和android中的GridView大差不差,另外Fluterr中也有GridView,也是網格佈局。
然後推翻了這個實現方式。
另一箇中實現方式也差不多,也是gridView無法新增水平title,我實現的是ListView巢狀GridView,但是這裡要注意的是,你需要禁用GridView滑動,否則會和ListView滑動衝突。
所有滾動元件都有一個叫physics的屬性,我們增加一個
physics: new NeverScrollableScrollPhysics(),//禁用滾動
複製程式碼
這個是一開始實現的方案。
最後還是使用ListView實現吧,圖片效果網格部分列表怎麼實現呢?使用一個標題加4個網格單元當成一個item實現就行了。
針對本章節運用到的新控制元件和方法進行一個講解:
一、Flutter中shape的使用:
BoxDecoration:描述如何繪製容器,Container與BoxDecoration配合來裝飾 background, border, or shadow。
new Center(
child: new Container(
width: 50.0,
height: 50.0,
decoration: new BoxDecoration(
//背景色
color: const Color(0xff7c94b6),
//沒有圖片的小夥,註釋掉image這個,用color背景也是可以看效果的
image: new DecorationImage(
image: new ExactAssetImage('images/mozi.jpeg'),
fit: BoxFit.cover,
),
//shape型別:rectangle|circle
shape: BoxShape.rectangle,
//邊框顏色
border: new Border.all(
color: Colors.red,//邊框顏色
width: 2.0,//邊框寬度
),
),
),
)
複製程式碼
主要是在容器中使用BoxDecoration進行繪製,如果不指定borderRadius 那麼容器就是一個矩形。
如果設定shape引數,BoxShape.rectangle:矩形,BoxShape.circle:圓形
我們通過改變borderRadius值來變化shape的弧度。
我們可以BoxDecoration的屬性borderRadius中配置一個邊界半徑:
borderRadius: new BorderRadius.all(new Radius.circular(15.0))
複製程式碼
Radius.circular構造一個圓的半徑。
下面我們實現下“預定按鈕區域”:
///預訂按鈕區域
new Container(
//設定容器邊距
padding: const EdgeInsets.only(top: 21.0, left: 20.0),
child: new Container(
//容器中小部件居中
alignment: Alignment.center,
//設定小部件距離容器的邊距
padding: const EdgeInsets.fromLTRB(14.0, 7.0, 14.0, 7.0),
//設定裝飾器
decoration: new BoxDecoration(
//設定邊界
border: new Border.all(
color: Colors.black38,
width: 1.0,
),
//設定邊界半徑
borderRadius: new BorderRadius.all(new Radius.circular(50.0)),
),
child: new Row(
//按鈕和預訂文字水平排列顯示
children: <Widget>[
new Icon(Icons.timer, color: Colors.black38, size: 12.0),
new Text(
"預訂",
style: new TextStyle(fontSize: 13.0),
)
],
),
),
)
複製程式碼
同樣的,推薦列表中使用者頭像也是同樣的道理:
new Container(
//外邊距,如果用padding的話頭像會變形
margin: new EdgeInsets.symmetric(horizontal: 10.0),
//需要定容器寬高,否則CircleAvatar裁剪出來的圖片很小
width: 40.6,
height: 40.6,
//新增一個邊框
decoration: new BoxDecoration(
shape: BoxShape.circle,
//設定邊框顏色
border: new Border.all(
width: 1.0,
color: Colors.yellow,
)),
child: new CircleAvatar(//圓角頭像小部件
radius: 5.0,
//AssetBundleImageProvider
backgroundImage: new AssetImage(
assetName,//動態傳入進來,如'images/chenhe.jpg'
),
),
)
複製程式碼
二、Flutter中Stack使用,類似Android中FrameLayout控制元件:
new Center(
child: new Stack(
children: <Widget>[
widget1,
widget2,
widget3,
......
],
),
)
複製程式碼
上面這段程式碼顯示出來的小部件都疊加在一起,預設對齊方式是左上角,Stack控制元件本身包含所有不定位的子控制元件,
我們可以通過Stack的alignment讓內部所有的子控制元件對齊方式改變。
在做上面企鵝電競效果的時候,Expanded啊,Container啊,等等控制元件,內部各種屬性用了遍,無法讓其他小部件在Stack內部進行定位到某個位置。就一個滑鼠點選的操作,Stack原始碼中有告訴我們哪個控制元件可以對Stack內部的小部件進行定位。
Positioned程式碼的註釋:
A widget that controls where a child of a [Stack] is positioned.
通過子控制元件的top、right、bottom和left屬性將它們定位在Stack控制元件不同位置處。
程式碼樣例如下:
new Container(
width: double.infinity,
height: double.infinity,
child: new Stack(
children: <Widget>[
new Container(
width: 100.0,
height: 80.0,
color: Colors.green,
),
new Container(
width: 50.0,
height: 50.0,
color: Colors.orangeAccent,
),
new Positioned(
right: 150.0,
bottom: 280.0,
child: new Container(
width: 100.0,
height: 100.0,
color: Colors.lime,
)),
new Positioned(
right: 50.0,
bottom: 100.0,
child: new Container(
width: 60.0,
height: 60.0,
color: Colors.deepPurpleAccent,
)),
],
),
)
複製程式碼
三、旋轉控制元件RotatedBox:
截圖效果中有一個抽獎中的tag背景,我拿到企鵝電競app內的小圖示,它是反過來的,我懶得轉方向,考慮程式怎麼旋轉,順便讓大家瞭解下怎麼旋轉這個圖片。
RotatedBox:旋轉內部小部件,上面的圖片我們需要順時針旋轉2次即可。
const RotatedBox({
Key key,
@required this.quarterTurns,
Widget child,
}) : assert(quarterTurns != null),
super(key: key, child: child);
複製程式碼
quarterTurns這個屬性值代表的是:旋轉的次數;每旋轉一次走順時針方向的四分之一;
我們通過如下程式碼實現了,抽獎中的tag效果:
new Stack(
alignment: Alignment.center,
children: <Widget>[
//RotatedBox:旋轉內部小部件;
//quarterTurns:旋轉的次數;每旋轉一次走順時針方向的四分之一;
new RotatedBox(quarterTurns: 2,child: new Image.asset('images/battle_status_bg_yellow.9.png',width: 45.0,height: 20.0,fit: BoxFit.fill,),),
new Text('抽獎中',style: new TextStyle(fontSize: 9.0,color: Colors.black),),
],
)
複製程式碼
下面來實現企鵝電競直播欄下面的佈局效果:
1.入口無狀態小部件來一波,瞭解下:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text('企鵝電競佈局實戰篇一'),
),
body: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return index == 0 ? buildHeader() : buildContent(index);
},
itemCount: 3,
),
),
);
}
}
複製程式碼
2.來構建下列表頭部內容:
Widget buildHeader() {
return new Container(
alignment: Alignment.topLeft,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
//佈局輪播圖區域,本篇只講簡單的佈局,不做輪播圖介紹
//設定寬度最大,高度150畫素,裁剪方式:居中裁剪
new Image.asset(
'images/lake.jpg',
width: double.infinity,
height: 126.6,
fit: BoxFit.cover,
),
_buildReminder(),
_buildRecommendList(),
],
),
);
}
複製程式碼
_buildReminder()和_buildRecommendList()這兩個有了上面的講解自己就可以實現了,最下面我會附上github程式碼地址。
3.構建網格列表body體,瞭解下:
Widget buildContent(int index) {
return new Column(
children: <Widget>[
new Container(
margin: const EdgeInsets.all(15.0),
child: new Row(
children: <Widget>[
//強制子類填充可用空間==match_parent
new Expanded(
child: new Text(
'直播推薦',
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.w500,
fontFamily: 'Roboto',
),
)),
new Expanded(
child: new Text(
'重新整理',
textAlign: TextAlign.right,
style: new TextStyle(
fontSize: 12.0,
color: const Color.fromARGB(255, 136, 136, 153)),
))
],
),
),
new Row(
children: <Widget>[
//強制填充剩餘空間
new Expanded(
child: new Container(
margin: const EdgeInsets.only(right: 1.5),
child: _buildContentImageText(
'images/zhubo01.jpg', '新進主播,多多關注', 'Dae-安格', 16.6),
)),
new Expanded(
child: new Container(
margin: const EdgeInsets.only(left: 1.5),
child: _buildContentImageText(
'images/zhubo02.jpeg', '國服李白,瞭解一下', 'EL-溜神', 52.1),
),
),
],
),
new Row(
children: <Widget>[
new Expanded(
child: new Container(
margin: const EdgeInsets.only(right: 1.5),
child: _buildContentImageText(
'images/zhubo03.jpeg', '貂蟬帶你五殺', '呂布別走\(^o^)/~', 5.9),
)),
new Expanded(
child: new Container(
margin: const EdgeInsets.only(left: 1.5),
child: _buildContentImageText(
'images/zhubo04.jpeg', '國服最騷香香', '國服最騷香香', 11.1),
))
],
)
],
);
}
複製程式碼
上述程式碼中Expanded作用是:填充剩餘可用空間==Match_parent
網格列表body體,佈局結構:
Column{
Container子widget1,<外邊距,child{
Row{
Expanded包裹一個Text文字(直播),
Expanded包裹一個Text文字(重新整理)
}
}>
Row子widget2,------>_buildContentImageText()
Row子widget3 ------->_buildContentImageText()
}
複製程式碼
_buildContentImageText()方法主要針對Stack的使用
Widget _buildContentImageText(
String asserPath, String desc, String username, double onlinePopulation) {
return new Container(
alignment: Alignment.center,
child: new Column(
children: <Widget>[
new Stack(
children: <Widget>[
//封面圖
new Image.asset(
asserPath,
fit: BoxFit.cover,
),
//抽獎標識
new Container(
alignment: Alignment.topRight,
padding: const EdgeInsets.only(top: 5.0),
child: new Stack(
alignment: Alignment.center,
children: <Widget>[
//RotatedBox:旋轉內部小部件;
//quarterTurns:旋轉的次數;每旋轉一次走順時針方向的四分之一;
new RotatedBox(
quarterTurns: 2,
child: new Image.asset(
'images/battle_status_bg_yellow.9.png',//Tag背景圖片
width: 45.0,
height: 20.0,
fit: BoxFit.fill,
),
),
new Text(
'抽獎中',
style: new TextStyle(fontSize: 9.0, color: Colors.black),
),
],
),
),
//使用者名稱和人氣值
new Positioned(
//控制[Stack]子部件位置的小部件
left: 15.0,
right: 11.0,
bottom: 7.0,
child: new Row(
children: <Widget>[
//填充剩餘空間
new Expanded(
child: new Text(
username,
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 8.0,
color: Colors.white,
),
)),
new Expanded(
child: new Text(
'$onlinePopulation 萬人氣',
textAlign: TextAlign.right,
style: new TextStyle(fontSize: 8.0, color: Colors.white),
))
],
),
),
],
),
//網格圖片下面的文字介紹
new Container(
margin: const EdgeInsets.only(top: 7.0, bottom: 16.0),
child: new Text(
desc,
style: new TextStyle(
color: Colors.black,
fontSize: 12.0,
fontWeight: FontWeight.w500,
),
),
)
],
),
);
}
複製程式碼