經過前面的對於Flutter的介紹,我們現在已經可以開始寫我們的天氣預報APP的界面了。
項目Github地址:a1203991686/CoolWeather_Flutter
1. 大致界面
最終寫成的大致界面如圖:
我們可以把這個界面拆分成如下部分:
可以看到我們APP主要有最上面用來顯示地點和刷新時間的Title
、顯示溫度和天氣的兩個Text
、顯示3天預報的ListView
、顯示空氣質量的GridView
、以及最後顯示生活建議的ListView
。
此處會用到我們前面學過的所有知識,如果有同學沒有看過前面內容的,可以看下本系列前面的文章。
2. 創建項目
首先我們創建一個新的Flutter項目:
- 在AndroidStudio點擊
File
->New Flutter Project
; - 接着選擇
Flutter Application
->NEXT
。接着填寫你的項目名稱等一系列信息後點擊next
;
- 接着輸入你的組織/公司名稱作爲包名,最後點擊
Finish
即可,這樣就新建了一個Flutter項目,並且Flutter會自動爲你生成一個計數器Demo;
- 讓我們把
mian.dart
裏面的所有註釋以及_MyHomePageState
裏面的build
方法裏面的代碼都刪掉; - 接着在
lib
文件夾下新建一個文件夾,名叫view
,到時候我們把所有與界面有關的代碼文件都放在這個文件夾下面; - 然後我們在
view
文件夾下面新建一個dart文件,命名爲main_page
。這個就是我們的天氣詳情頁。
3. 設計好天氣詳情頁框架
首先我們需要導入material
包,我們項目主要用material風格UI來寫。
在開頭輸入import 'package:flutter/material.dart';
,這樣就導入了material
包。接着創建我們的界面類MainPage
。
由於我們在到時候寫好網絡請求後,所有的數據都得從網絡獲取,並且使用了異步的方法,也就是說我們先把頁面加載瞭然後等獲取的數據回來了在通知Flutter更新狀態,所以這塊我們得使用StatefulWidget
。
class MainPage extends StatefulWidget {
MainPage({Key key}) : super(key: key);
@override
MainPageState createState() => new MainPageState();
}
class MainPageState extends State<MainPage> {
@override
Widget build(BuildContext context) {
}
}
接着我們就得開始寫build()
裏面的內容了。由於我們需要一個全屏的背景圖片,所以我們就使用Container
作爲我們最外層的Widget,並使用BoxDecoration
裝飾容器配合DecorationImage
來放置圖片:
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage("http://blog.mrabit.com/bing/today"), //必應每日一圖背景
fit: BoxFit.cover, // 設置爲全屏
),
),
child: _weatherBody(),
);
}
這個時候我們的運行結果如圖(①你背景圖片多半和我不一樣,因爲上面那個Uri獲取的是必應的每日一圖,每天圖片都不一樣;②運行的時候記得把child: _weatherBody(),
給註釋掉,因爲我們還沒有定義_weatherBody()
這個方法):
4. 設計title
爲了方便我就直接把剩下的佈局單領出來放到_weatherBody()
這個方法:
Widget _weatherBody() {
return
}
由於我們需要實現一個有Title的頁面,所以最外層我選用了Scaffold:
Widget _weatherBody() {
return Scaffold(
backgroundColor: Colors.transparent, //背景透明
appBar: AppBar(
centerTitle: true,
title: Text(
"北京",
style: TextStyle(fontSize: 25.0),
),
backgroundColor: Colors.transparent, //背景透明
actions: <Widget>[ //右側Widget,相當於Android Toolbar中的menu
Container(
alignment: Alignment.center, //向中間對齊
child: Text(
"12:10",
textAlign: TextAlign.center, //文字向中間對齊
),
)
],
),
body: SingleChildScrollView( // 由於到時候整個頁面一個屏幕可能放不下,就放置了一個滾動佈局
),
);
}
這個時候的運行結果是:
5. 設計下面天氣預報頁面
接下來可以開始下下面的天氣顯示頁面了。
當前溫度和天氣情況
首先寫一個縱向線性佈局Column
:
body: SingleChildScrollView(
child: Column(
children: <Widget>[
]
)
),
由於我們要顯示在屏幕最右邊,所以使用Align:
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Align(
alignment: Alignment.centerRight,
child:Text(
"26°C",
style: TextStyle(
fontSize: 50.0,
color: Colors.white,
),
),
),
Align(
alignment: Alignment.centerRight,
child:Text(
"陰",
style: TextStyle(
fontSize: 20.0,
color: Colors.white,
),
),
),
]
)
),
運行結果是:
接下來我們需要顯示3天天氣預報、天氣質量以及生活建議的三個子控件,同樣爲了方便我們也把他們給單領出來,於是上面的Column接下來可以這樣寫:
body: SingleChildScrollView(
child: Column(
children: <Widget>[
... //上面的兩個Text
Padding(
padding: EdgeInsets.only(
left: 15.0,
right: 15.0,
bottom: 15.0,
),
child: Container(
color: Colors.black54,
child: _weatherList(), //3天天氣預報
),
),
Padding(
padding: EdgeInsets.only(
left: 15.0,
right: 15.0,
),
child: Container(
color: Colors.black54,
child: _atmosphereList(), //空氣質量
),
),
Padding(
padding: EdgeInsets.only(
top: 15.0,
left: 15.0,
right: 15.0,
bottom: 15.0,
),
child: Container(
color: Colors.black54,
child: _lifestyleList(), //生活建議
),
),
]
)
),
3天天氣預報
對於3天天氣預報我們主要通過ListView來實現,由於到時候數據比較靈活,所以我們就直接使用ListView.Builder來實現:
List<String> dates = ["2019/10/01", "2019/10/02", "2019/10/03"];
List<String> temperatures = ["29/14", "30/18", "29/18"];
List<String> texts = ["陰", "晴", "雨"];
Widget _weatherList() {
return Column(
children: <Widget>[
Align(
alignment: Alignment.centerLeft,
child: Text(
"預報",
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
),
),
),
ListView.builder(
shrinkWrap: true, //這個是指根據ListView所有子Widget的長度來設定ListView的長度
physics: NeverScrollableScrollPhysics(), //禁止ListView自己的滑動,因爲我們在外面用了個SingleChildScrollView,我們通過他的滑動就可以了
itemCount: dates.length, //ListView子項個數
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, //這個是Row的主軸的子項的分佈格式,spaceBetween是指平均分佈
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(
"${dates[index]}",
style: TextStyle(color: Colors.white),
),
Text(
"${temperatures[index]}",
style: TextStyle(color: Colors.white),
),
Text(
"${texts[index]}",
style: TextStyle(color: Colors.white),
),
],
),
);
}),
],
);
}
這個時候運行結果是:
空氣質量
這塊我們需要用到GridView,由於只有兩個顯示內容,而且我們後期也沒有需要動態添加的需求,所以我們就直接使用GridView構造方法,而不去使用GridView的builder方法:
List<String> atmospheres = ["16", "56"];
Widget _atmosphereList() {
return Column(
children: <Widget>[
Align(
alignment: Alignment.centerLeft,
child: Text(
"空氣質量",
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
),
),
),
GridView(
shrinkWrap: true, //見上面3天天氣預報的ListView處
physics: NeverScrollableScrollPhysics(), //見上面3天天氣預報的ListView處
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, //橫軸三個子widget
childAspectRatio: 2 //顯示區域寬高相等
),
children: <Widget>[
Column(
children: <Widget>[
Text(
"${atmospheres[0]}",
style: TextStyle(
color: Colors.white,
fontSize: 40.0,
),
),
Text(
"能見度",
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
),
),
],
),
Column(
children: <Widget>[
Text(
"${atmospheres[1]}",
style: TextStyle(
color: Colors.white,
fontSize: 40.0,
),
),
Text(
"溼度",
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
),
),
],
),
],
),
],
);
}
運行後的效果是:
生活建議
這塊和3天天氣預報一樣,而且數據比3天天氣預報更多,所以他更適合用ListView.builder:
List<String> _lifestyleWeatherBrf = [
"較舒適",
"較舒適",
"適宜",
"適宜",
"弱",
"較適宜",
"中"
];
List<String> _lifestyleWeatherTxt = [
"白天天氣晴好,早晚會感覺偏涼,午後舒適、宜人。",
"建議着薄外套、開衫牛仔衫褲等服裝。年老體弱者應適當添加衣物,宜着夾克衫、薄毛衣等。",
"各項氣象條件適宜,無明顯降溫過程,發生感冒機率較低。",
"天氣較好,趕快投身大自然參與戶外運動,盡情感受運動的快樂吧。",
"天氣較好,但絲毫不會影響您出行的心情。溫度適宜又有微風相伴,適宜旅遊。",
"紫外線強度較弱,建議出門前塗擦SPF在12-15之間、PA+的防曬護膚品。",
"較適宜洗車,未來一天無雨,風力較小,擦洗一新的汽車至少能保持一天。",
",氣象條件對空氣污染物稀釋、擴散和清除無明顯影響,易感人羣應適當減少室外活動時間。"
];
Widget _lifestyleList() {
return Column(
children: <Widget>[
Align(
alignment: Alignment.centerLeft,
child: Text(
"生活建議",
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
),
),
),
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: _lifestyleWeatherBrf.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"${_lifestyleWeatherBrf[index]}",
style: TextStyle(
color: Colors.white,
),
),
Text(
"${_lifestyleWeatherTxt[index]}",
style: TextStyle(
color: Colors.white,
),
),
],
),
);
},
),
],
);
}
運行結果是: