從0開始寫一個基於Flutter的開源中國客戶端(6)——各個靜態頁面的實現
上一篇中我記錄了基於Flutter的開源中國客戶端的整體佈局框架的搭建,本篇記錄的是每個頁面的靜態實現,關於具體的資料載入和儲存,放在下一篇中記錄,希望自己在溫故知新的同時,能給Flutter初學者一些幫助。
在基於Flutter的開源中國客戶端中,使用得最多的就是ListView元件了,基本上80%的頁面都需要用列表展示,下面分別說明每個頁面的實現過程。
側滑選單頁面的實現
上一篇中我們僅僅在側滑選單中放置了一個Center元件並顯示了一行文字,這一篇中需要實現的側滑選單效果如下圖:
側滑選單的頭部是一個封面圖,下面是一個選單列表,我們可以將封面圖和各個選單都當作ListView的Item,所以這裡涉及到了ListView的子Item的多佈局。
上一篇的程式碼裡我們是直接為MaterialApp添加了一個drawer引數並new了一個Drawer物件,為了合理組織程式碼,這裡我們在lib/
目錄下新建一個widgets/
目錄,用於存放我們自定義的一些元件,並新建dart檔案MyDrawer.dart
,由於該頁面不需要重新整理,所以我們在MyDrawer.dart
中定義無狀態的元件MyDrawer,在該元件中定義需要用到的如下幾個變數:
class MyDrawer extends StatelessWidget {
// 選單文字前面的圖示大小
static const double IMAGE_ICON_WIDTH = 30.0 ;
// 選單後面的箭頭的圖示大小
static const double ARROW_ICON_WIDTH = 16.0;
// 選單後面的箭頭圖片
var rightArrowIcon = new Image.asset(
'images/ic_arrow_right.png',
width: ARROW_ICON_WIDTH,
height: ARROW_ICON_WIDTH,
);
// 選單的文字
List menuTitles = ['釋出動彈', '動彈小黑屋', '關於', '設定'];
// 選單文字前面的圖示
List menuIcons = [
'./images/leftmenu/ic_fabu.png' ,
'./images/leftmenu/ic_xiaoheiwu.png',
'./images/leftmenu/ic_about.png',
'./images/leftmenu/ic_settings.png'
];
// 選單文字的樣式
TextStyle menuStyle = new TextStyle(
fontSize: 15.0,
);
// 省略後續程式碼
// ...
}
在MyDrawer
類的build
方法中,返回一個ListView元件即可:
@override
Widget build(BuildContext context) {
return new ConstrainedBox(
constraints: const BoxConstraints.expand(width: 304.0),
child: new Material(
elevation: 16.0,
child: new Container(
decoration: new BoxDecoration(
color: const Color(0xFFFFFFFF),
),
child: new ListView.builder(
itemCount: menuTitles.length * 2 + 1,
itemBuilder: renderRow,
),
),
),
);
}
build
方法中的ConstraintedBox
元件和Material
元件都是直接參考的Drawer類的原始碼,constraints
引數指定了側滑選單的寬度,elevation
引數控制的是Drawer後面的陰影的大小,預設值就是16(所以這裡可以不指定elevation引數),最主要的是ListView的命名構造方法build
,itemCount引數代表item的個數,這裡之所以是menuTitles.length * 2 + 1
,其中的*2是將分割線算入到item中了,+1則是把頂部的封面圖算入到item中了。下面是關鍵的renderRow
方法:
Widget renderRow(BuildContext context, int index) {
if (index == 0) {
// render cover image
var img = new Image.asset(
'images/cover_img.jpg',
width: 304.0,
height: 304.0,
);
return new Container(
width: 304.0,
height: 304.0,
margin: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 10.0),
child: img,
);
}
// 捨去之前的封面圖
index -= 1;
// 如果是奇數則渲染分割線
if (index.isOdd) {
return new Divider();
}
// 偶數,就除2取整,然後渲染選單item
index = index ~/ 2;
// 選單item元件
var listItemContent = new Padding(
// 設定item的外邊距
padding: const EdgeInsets.fromLTRB(10.0, 15.0, 10.0, 15.0),
// Row元件構成item的一行
child: new Row(
children: <Widget>[
// 選單item的圖示
getIconImage(menuIcons[index]),
// 選單item的文字
new Expanded(
child: new Text(
menuTitles[index],
style: menuStyle,
)
),
rightArrowIcon
],
),
);
return new InkWell(
child: listItemContent,
onTap: () {
print("click list item $index");
},
);
}
renderRow方法體較長,主要是因為涉及到3個不同佈局的渲染:頭部封面圖、分割線、選單item。以上程式碼中已有相關注釋,其中有幾點需要注意:
1. 在渲染選單item文字時用到了Expanded元件,該元件類似於在Android中佈局時新增android:layout_weight=”1”屬性,上面使用Expanded包裹的Text元件在水平方向上會佔據除icon和箭頭圖示外的剩餘的所有空間;
2. 最後返回了一個InkWell元件,用於給選單item新增點選事件,但是在Drawer中點選選單時並沒有水波紋擴散的效果(不知道是什麼原因)。
資訊列表頁面的實現
本篇要實現的資訊列表頁面如下圖所示:
資訊列表的頭部是一個輪播圖,可以左右滑動切換不同的資訊,下面是一個列表,顯示了資訊的標題,釋出時間,評論數,資訊圖等資訊。
輪播圖的實現
輪播圖主要使用了Flutter內建的TabBarView元件,該元件類似於Android中的ViewPager,可以左右滑動切換頁面。為了合理組織程式碼,我們將輪播圖單獨抽出來作為一個自定義元件,在widgets/
目錄下新建SlideView.dart
檔案並新增如下程式碼:
import 'package:flutter/material.dart';
class SlideView extends StatefulWidget {
var data;
// data表示輪播圖中的資料
SlideView(data) {
this.data = data;
}
@override
State<StatefulWidget> createState() {
// 可以在構造方法中傳參供SlideViewState使用
// 或者也可以不傳引數,直接在SlideViewState中通過this.widget.data訪問SlideView中的data變數
return new SlideViewState(data);
}
}
class SlideViewState extends State<SlideView> with SingleTickerProviderStateMixin {
// TabController為TabBarView元件的控制器
TabController tabController;
List slideData;
SlideViewState(data) {
slideData = data;
}
@override
void initState() {
super.initState();
// 初始化控制器
tabController = new TabController(length: slideData == null ? 0 : slideData.length, vsync: this);
}
@override
void dispose() {
// 銷燬
tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
List<Widget> items = [];
if (slideData != null && slideData.length > 0) {
for (var i = 0; i < slideData.length; i++) {
var item = slideData[i];
// 圖片URL
var imgUrl = item['imgUrl'];
// 資訊標題
var title = item['title'];
// 資訊詳情URL
var detailUrl = item['detailUrl'];
items.add(new GestureDetector(
onTap: () {
// 點選頁面跳轉到詳情
},
child: new Stack( // Stack元件用於將資訊標題文字放置到圖片上面
children: <Widget>[
// 載入網路圖片
new Image.network(imgUrl),
new Container(
// 標題容器寬度跟螢幕寬度一致
width: MediaQuery.of(context).size.width,
// 背景為黑色,加入透明度
color: const Color(0x50000000),
// 標題文字加入內邊距
child: new Padding(
padding: const EdgeInsets.all(6.0),
// 字型大小為15,顏色為白色
child: new Text(title, style: new TextStyle(color: Colors.white, fontSize: 15.0)),
)
)
],
),
));
}
}
return new TabBarView(
controller: tabController,
children: items,
);
}
}
TabBarView元件主要的引數是controller和children,controller代表這個TabBarView的控制器,children表示這個元件中的各個頁面。SliderView中的data是在new這個物件時通過構造方法傳入的,data是一個map陣列,map中包含imgUrl
title
detailUrl
3個欄位。
注意:本專案的輪播圖裡沒有加入小圓點頁面指示器,小夥伴們可自行新增相關程式碼。
輪播圖和列表的組合
上面實現了自定義的輪播圖元件,下面就需要將這個元件和列表組合起來。
由於資訊列表的item佈局稍微有些複雜,所以這裡有必要進行拆分,整體上可以將item分為左右兩部分,左邊展示了資訊標題,時間,評論數等資訊,右邊展示了資訊的圖片。所以整體是一個Row元件,而左邊又是一個Column元件,Column元件的第一列是標題,第二列又是一個Row元件,其中有時間、作者頭像、評論數等資訊。下面直接上NewsListPage.dart的程式碼,在程式碼中做詳細的註釋:
import 'package:flutter/material.dart';
import 'package:flutter_osc/widgets/SlideView.dart';
// 資訊列表頁面
class NewsListPage extends StatelessWidget {
// 輪播圖的資料
var slideData = [];
// 列表的資料(輪播圖資料和列表資料分開,但是實際上輪播圖和列表中的item同屬於ListView的item)
var listData = [];
// 列表中資訊標題的樣式
TextStyle titleTextStyle = new TextStyle(fontSize: 15.0);
// 時間文字的樣式
TextStyle subtitleStyle = new TextStyle(color: const Color(0xFFB5BDC0), fontSize: 12.0);
NewsListPage() {
// 這裡做資料初始化,加入一些測試資料
for (int i = 0; i < 3; i++) {
Map map = new Map();
// 輪播圖的資訊標題
map['title'] = 'Python 之父透露退位隱情,與核心開發團隊產生隔閡';
// 輪播圖的詳情URL
map['detailUrl'] = 'https://www.oschina.net/news/98455/guido-van-rossum-resigns';
// 輪播圖的圖片URL
map['imgUrl'] = 'https://static.oschina.net/uploads/img/201807/30113144_1SRR.png';
slideData.add(map);
}
for (int i = 0; i < 30; i++) {
Map map = new Map();
// 列表item的標題
map['title'] = 'J2Cache 2.3.23 釋出,支援 memcached 二級快取';
// 列表item的作者頭像URL
map['authorImg'] = 'https://static.oschina.net/uploads/user/0/12_50.jpg?t=1421200584000';
// 列表item的時間文字
map['timeStr'] = '2018/7/30';
// 列表item的資訊圖片
map['thumb'] = 'https://static.oschina.net/uploads/logo/j2cache_N3NcX.png';
// 列表item的評論數
map['commCount'] = 5;
listData.add(map);
}
}
@override
Widget build(BuildContext context) {
return new ListView.builder(
// 這裡itemCount是將輪播圖元件、分割線和列表items都作為ListView的item算了
itemCount: listData.length * 2 + 1,
itemBuilder: (context, i) => renderRow(i)
);
}
// 渲染列表item
Widget renderRow(i) {
// i為0時渲染輪播圖
if (i == 0) {
return new Container(
height: 180.0,
child: new SlideView(slideData),
);
}
// i > 0時
i -= 1;
// i為奇數,渲染分割線
if (i.isOdd) {
return new Divider(height: 1.0);
}
// 將i取整
i = i ~/ 2;
// 得到列表item的資料
var itemData = listData[i];
// 代表列表item中的標題這一行
var titleRow = new Row(
children: <Widget>[
// 標題充滿一整行,所以用Expanded元件包裹
new Expanded(
child: new Text(itemData['title'], style: titleTextStyle),
)
],
);
// 時間這一行包含了作者頭像、時間、評論數這幾個
var timeRow = new Row(
children: <Widget>[
// 這是作者頭像,使用了圓形頭像
new Container(
width: 20.0,
height: 20.0,
decoration: new BoxDecoration(
// 通過指定shape屬性設定圖片為圓形
shape: BoxShape.circle,
color: const Color(0xFFECECEC),
image: new DecorationImage(
image: new NetworkImage(itemData['authorImg']), fit: BoxFit.cover),
border: new Border.all(
color: const Color(0xFFECECEC),
width: 2.0,
),
),
),
// 這是時間文字
new Padding(
padding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
child: new Text(
itemData['timeStr'],
style: subtitleStyle,
),
),
// 這是評論數,評論數由一個評論圖示和具體的評論數構成,所以是一個Row元件
new Expanded(
flex: 1,
child: new Row(
// 為了讓評論數顯示在最右側,所以需要外面的Expanded和這裡的MainAxisAlignment.end
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text("${itemData['commCount']}", style: subtitleStyle),
new Image.asset('./images/ic_comment.png', width: 16.0, height: 16.0),
],
),
)
],
);
var thumbImgUrl = itemData['thumb'];
// 這是item右側的資訊圖片,先設定一個預設的圖片
var thumbImg = new Container(
margin: const EdgeInsets.all(10.0),
width: 60.0,
height: 60.0,
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFECECEC),
image: new DecorationImage(
image: new ExactAssetImage('./images/ic_img_default.jpg'),
fit: BoxFit.cover),
border: new Border.all(
color: const Color(0xFFECECEC),
width: 2.0,
),
),
);
// 如果上面的thumbImgUrl不為空,就把之前thumbImg預設的圖片替換成網路圖片
if (thumbImgUrl != null && thumbImgUrl.length > 0) {
thumbImg = new Container(
margin: const EdgeInsets.all(10.0),
width: 60.0,
height: 60.0,
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFECECEC),
image: new DecorationImage(
image: new NetworkImage(thumbImgUrl), fit: BoxFit.cover),
border: new Border.all(
color: const Color(0xFFECECEC),
width: 2.0,
),
),
);
}
// 這裡的row代表了一個ListItem的一行
var row = new Row(
children: <Widget>[
// 左邊是標題,時間,評論數等資訊
new Expanded(
flex: 1,
child: new Padding(
padding: const EdgeInsets.all(10.0),
child: new Column(
children: <Widget>[
titleRow,
new Padding(
padding: const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 0.0),
child: timeRow,
)
],
),
),
),
// 右邊是資訊圖片
new Padding(
padding: const EdgeInsets.all(6.0),
child: new Container(
width: 100.0,
height: 80.0,
color: const Color(0xFFECECEC),
child: new Center(
child: thumbImg,
),
),
)
],
);
// 用InkWell包裹row,讓row可以點選
return new InkWell(
child: row,
onTap: () {
},
);
}
}
動彈列表頁面的實現
動彈列表要實現的效果如下圖:
為了區分普通的動彈和熱門動彈,需要使用兩個Tab來分別展示不同的頁面,這裡使用的是Flutter提供的DefaultTabController
元件,該元件的用法也比較簡單,下面是TweetsList.dart的build
方法的程式碼:
@override
Widget build(BuildContext context) {
// 獲取螢幕寬度
screenWidth = MediaQuery.of(context).size.width;
return new DefaultTabController(
length: 2,
child: new Scaffold(
appBar: new TabBar(
tabs: <Widget>[
new Tab(text: "動彈列表"),
new Tab(text: "熱門動彈")
],
),
body: new TabBarView(
children: <Widget>[getNormalListView(), getHotListView()],
)),
);
}
// 獲取普通動彈列表
Widget getNormalListView() {
return new ListView.builder(
itemCount: normalTweetsList.length * 2 - 1,
itemBuilder: (context, i) => renderNormalRow(i)
);
}
// 獲取熱門動彈列表
Widget getHotListView() {
return new ListView.builder(
itemCount: hotTweetsList.length * 2 - 1,
itemBuilder: (context, i) => renderHotRow(i),
);
}
// 渲染普通動彈列表Item
renderHotRow(i) {
if (i.isOdd) {
return new Divider(
height: 1.0,
);
} else {
i = i ~/ 2;
return getRowWidget(hotTweetsList[i]);
}
}
// 渲染熱門動彈列表Item
renderNormalRow(i) {
if (i.isOdd) {
return new Divider(
height: 1.0,
);
} else {
i = i ~/ 2;
return getRowWidget(normalTweetsList[i]);
}
}
在TabBarView中,children引數是一個數組,代表不同的頁面,這裡使用兩個方法分別返回普通的動彈列表和熱門動彈列表,編碼實現動彈列表前,先定義如下一些變數供後面使用,並在TweetsList類的構造方法中初始化這些變數:
import 'package:flutter/material.dart';
// 動彈列表頁面
class TweetsListPage extends StatelessWidget {
// 熱門動彈資料
List hotTweetsList = [];
// 普通動彈資料
List normalTweetsList = [];
// 動彈作者文字樣式
TextStyle authorTextStyle;
// 動彈時間文字樣式
TextStyle subtitleStyle;
// 螢幕寬度
double screenWidth;
// 構造方法中做資料初始化
TweetsListPage() {
authorTextStyle = new TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold);
subtitleStyle = new TextStyle(fontSize: 12.0, color: const Color(0xFFB5BDC0));
// 新增測試資料
for (int i = 0; i < 20; i++) {
Map<String, dynamic> map = new Map();
// 動彈釋出時間
map['pubDate'] = '2018-7-30';
// 動彈文字內容
map['body'] = '早上七點十分起床,四十出門,花二十多分鐘到公司,必須在八點半之前打卡;下午一點上班到六點,然後加班兩個小時;八點左右離開公司,呼呼登自行車到健身房鍛鍊一個多小時。到家已經十點多,然後準備第二天的午飯,接著收拾廚房,然後洗澡,吹頭髮,等能坐下來吹頭髮時已經快十二點了。感覺很累。';
// 動彈作者暱稱
map['author'] = '紅薯';
// 動彈評論數
map['commentCount'] = 10;
// 動彈作者頭像URL
map['portrait'] = 'https://static.oschina.net/uploads/user/0/12_50.jpg?t=1421200584000';
// 動彈中的圖片,多張圖片用英文逗號隔開
map['imgSmall'] = 'https://b-ssl.duitang.com/uploads/item/201508/27/20150827135810_hGjQ8.thumb.700_0.jpeg,https://b-ssl.duitang.com/uploads/item/201508/27/20150827135810_hGjQ8.thumb.700_0.jpeg,https://b-ssl.duitang.com/uploads/item/201508/27/20150827135810_hGjQ8.thumb.700_0.jpeg,https://b-ssl.duitang.com/uploads/item/201508/27/20150827135810_hGjQ8.thumb.700_0.jpeg';
hotTweetsList.add(map);
normalTweetsList.add(map);
}
}
}
有了測試資料,下面最主要的是實現列表的展示,而列表展示最為麻煩的,是列表item的渲染。每個item中要展示使用者頭像,使用者暱稱,動彈釋出時間,動彈評論數,如果動彈中有圖片,還需要以九宮格的方式顯示圖片。簡單分析下動彈列表的item,應該是用Column元件展示,Column元件的第一行顯示使用者頭像、暱稱、釋出動彈的時間,第二行應該顯示動彈的內容,第三行是可展示可不展示的九宮格,如果動彈中有圖片,則顯示,否則不限時,第四行是動彈評論數,顯示在右下角。下面分小步來實現列表item的渲染:
第一行,顯示使用者頭像,暱稱和釋出時間
這一行用個Row元件展示即可,程式碼如下:
// 列表item的第一行,顯示動彈作者頭像、暱稱、評論數
var authorRow = new Row(
children: <Widget>[
// 使用者頭像
new Container(
width: 35.0,
height: 35.0,
decoration: new BoxDecoration(
// 頭像顯示為圓形
shape: BoxShape.circle,
color: Colors.transparent,
image: new DecorationImage(
image: new NetworkImage(listItem['portrait']),
fit: BoxFit.cover),
// 頭像邊框
border: new Border.all(
color: Colors.white,
width: 2.0,
),
),
),
// 動彈作者的暱稱
new Padding(
padding: const EdgeInsets.fromLTRB(6.0, 0.0, 0.0, 0.0),
child: new Text(
listItem['author'],
style: new TextStyle(fontSize: 16.0)
)
),
// 動彈評論數,顯示在最右邊
new Expanded(
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text(
'${listItem['commentCount']}',
style: subtitleStyle,
),
new Image.asset(
'./images/ic_comment.png',
width: 16.0,
height: 16.0,
)
],
),
)
],
);
第二行,顯示動彈內容
這一行僅僅是一段文字,所以程式碼比較簡單:
// 動彈內容,純文字展示
var _body = listItem['body'];
var contentRow = new Row(
children: <Widget>[
new Expanded(child: new Text(_body))
],
);
第三行,顯示動彈中的圖片,沒有圖片則不展示這一行
以九宮格的形式顯示圖片稍微麻煩些,這也是為什麼之前我們要在build方法中獲取螢幕的寬度,因為要根據這個寬度來計算九宮格中圖片的寬度。另外,九宮格中的圖片URL是以字串形式給出的,以英文逗號隔開的,所以需要對圖片URL做分割處理。如果動彈中有圖片,可能有1~9張,下面用一個方法來確定用九宮格顯示時,總共有幾行:
// 獲取行數,n表示圖片的張數
// 如果n取餘不為0,則行數為n取整+1,否則n取整就是行數
int getRow(int n) {
int a = n % 3; // 取餘
int b = n ~/ 3; // 取整
if (a != 0) {
return b + 1;
}
return b;
}
比如一共有9張圖片,9 % 3為0,則一共有9 ~/3 = 3行,如果一共有5張圖片,5 % 3 != 0,則行數為5 ~/ 3再+1即兩行。
下面是生成九宮格圖片的程式碼:
// 動彈中的圖片資料,字串,多張圖片以英文逗號分隔
String imgSmall = listItem['imgSmall'];
if (imgSmall != null && imgSmall.length > 0) {
// 動彈中有圖片
List<String> list = imgSmall.split(",");
List<String> imgUrlList = new List<String>();
// 開源中國的openapi給出的圖片,有可能是相對地址,所以用下面的程式碼將相對地址補全
for (String s in list) {
if (s.startsWith("http")) {
imgUrlList.add(s);
} else {
imgUrlList.add("https://static.oschina.net/uploads/space/" + s);
}
}
List<Widget> imgList = [];
List<List<Widget>> rows = [];
num len = imgUrlList.length;
// 通過雙重for迴圈,生成每一張圖片元件
for (var row = 0; row < getRow(len); row++) { // row表示九宮格的行數,可能有1行2行或3行
List<Widget> rowArr = [];
for (var col = 0; col < 3; col++) { // col為列數,固定有3列
num index = row * 3 + col;
double cellWidth = (screenWidth - 100) / 3;
if (index < len) {
rowArr.add(new Padding(
padding: const EdgeInsets.all(2.0),
child: new Image.network(imgUrlList[index],
width: cellWidth, height: cellWidth),
));
}
}
rows.add(rowArr);
}
for (var row in rows) {
imgList.add(new Row(
children: row,
));
}
columns.add(new Padding(
padding: const EdgeInsets.fromLTRB(52.0, 5.0, 10.0, 0.0),
child: new Column(
children: imgList,
),
));
}
上面程式碼的最後有個columns
變數,代表的是整個item的一個列布局,在生成九宮格佈局前,已經將第一行和第二行新增到columns中:
var columns = <Widget>[
// 這是item中第一行
new Padding(
padding: const EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 2.0),
child: authorRow,
),
// 這是item中第二行
new Padding(
padding: const EdgeInsets.fromLTRB(52.0, 0.0, 10.0, 0.0),
child: contentRow,
),
];
如果動彈中有圖片,則columns中還要新增九宮格圖片元件。
第四行,顯示動彈釋出時間
這一行佈局比較簡單:
var timeRow = new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text(
listItem['pubDate'],
style: subtitleStyle,
)
],
);
columns.add(new Padding(
padding: const EdgeInsets.fromLTRB(0.0, 10.0, 10.0, 6.0),
child: timeRow,
));
最後返回一個用一個InkWell元件包裹的columns即可:
return new InkWell(
child: new Column(
children: columns,
),
onTap: () {
// 跳轉到動彈詳情
}
);
“發現”頁面的實現
本篇要實現的發現頁面效果圖如下:
該頁面就是一個簡單的ListView,但是稍微有些不同的是,ListView中的分割線有的長,有的短,有的分割線之間還有空白區域分隔,為了實現這個佈局,我用了一種方法是將長短不同的分割線,或者兩條分割線間的空白區域,都用不同的字串來標記,在渲染列表的時候,根據不同的字串來渲染不同的元件,程式碼很容易理解,所以這裡直接放原始碼連結了:原始碼,原始碼中已有詳細註釋。
“我的”頁面的實現
本篇要實現的我的頁面效果圖如下:
這個頁面也比較簡單,頭部的綠色區域也屬於ListView的一部分,也是ListView的多佈局,具體實現方式就不細說了,直接放程式碼:原始碼。
原始碼
本篇相關的所有原始碼都在GitHub上demo-flutter-osc專案的v0.2分支。
後記
本篇主要記錄的是基於Flutter的開源中國客戶端各個靜態頁面的實現,僅限於UI,具體的網路請求,資料儲存和其他邏輯在下一篇中做記錄。
我的開源專案
基於Google Flutter的開源中國客戶端,希望大家給個Star支援一下,原始碼:
基於Flutter的俄羅斯方塊小遊戲,希望大家給個Star支援一下,原始碼:
相關推薦
從0開始寫一個基於Flutter的開源中國客戶端(6)——各個靜態頁面的實現
上一篇中我記錄了基於Flutter的開源中國客戶端的整體佈局框架的搭建,本篇記錄的是每個頁面的靜態實現,關於具體的資料載入和儲存,放在下一篇中記錄,希望自己在溫故知新的同時,能給Flutter初學者一些幫助。 在基於Flutter的開源中國客戶端中,使
從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎
我的上一篇部落格中記錄了Flutter基礎和一些常用的Widgets,這一篇中主要記錄Flutter常用的一些佈局,希望自己在記錄的同時能溫故知新,同時給初學者一些幫助。 Flutter佈局容器 在Android開發中,我們使用xml檔案寫佈局,有諸
從0開始寫一個基於Flutter的開源中國客戶端(8)——外掛的使用
上一篇中我記錄了基於Flutter的開源中國客戶端裡網路請求和資料儲存的部分,本篇記錄的是app中外掛的使用,由於很多功能並沒有內建到Flutter中,所以我們需要引入一些外掛來幫助我們完成某些功能,比如app內網頁的載入,相簿選擇照片等。 搜尋外掛包
從0開始寫一個基於Flutter的開源中國客戶端(5)——App整體佈局框架搭建
上一篇中我記錄了Flutter中常用的一些佈局,本篇開始開發基於Flutter的開源中國客戶端了。在本篇部落格中,要實現的是一個App的整體框架,包括頁面底部的Tab導航選單、頁面的側滑選單以及跳轉到新的頁面這幾個功能。希望自己在記錄的同時能溫故知新,同時
從0開始寫一個通用的Makefile檔案
什麼是Makefile檔案 一個工程中的原始檔不計其數,其按型別、功能、模組分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些檔案需要先編譯,哪些檔案需要後編譯,哪些檔案需要重新編譯,
從零開始構建一個的asp.net Core 項目(二)
mage .... cfi web execute 運行 figure 今天 deb 接著上一篇博客繼續進行。上一篇博客只是顯示了簡單的MVC視圖頁,這篇博客接著進行,連接上數據庫,進行簡單的CRUD。 首先我在Controllers文件夾點擊右鍵,添加->控制器 彈
手把手教你仿一個知乎日報Android客戶端(一)多圖
本文為作者原創,轉載請註明出處@大蘑菇的部落格 圖片託管服務由貼相簿提供 作為一隻網蟲,肯定是經常泡在網路的海洋裡,有一天偶然看到了知乎日報的API,各方面介面都還挺全面,於是本著“不用白不用”的真理,我決定仿一個知乎日報Adnroid客戶端。
手把手教你仿一個知乎日報Android客戶端(三)主頁面設計
各位朋友,從本篇文章和開始,手把手教你仿一個Android客戶端就要正式開始探究怎樣實現我們前面的那些需求了。在此開發我們將會使用git作為版本控制工具,並且將程式碼託管到github,好啦,廢話少說,咱們開工。 一、建立專案 怎麼建立專案大家應該都知道
基於Bmob從零開始寫一個部落格小程式
實現以下技能點 1、整合Bmob小程式SDK作為資料儲存 2、wemark解析markdown文字 3、列表頁佈局與上拉無限載入 實現的效果 一、建立Bmob應用 進入Bmob官網:http://bmob.cn/,註冊一個賬號,免費的賬號可以建立8個子應用,每個應用單表列數是20列,如要購買付費,可以分
一起學習造輪子(三):從零開始寫一個React-Redux
導致 href dispatch 判斷 som render connect mis 回調 本文是一起學習造輪子系列的第三篇,本篇我們將從零開始寫一個React-Redux,本系列文章將會選取一些前端比較經典的輪子進行源碼分析,並且從零開始逐步實現,本系列將會學習Prom
【Python】從0開始寫爬蟲——開發環境
stdin charm ready indicate importlib mirror upgrade war change python小白,稍微看了點語法而已, 連字典的切片都永不順的那種。本身是寫java的,其實java也寫得菜, 每天下了班不是太想寫ja
【Python】從0開始寫爬蟲——扒狗東先流產了
https 數據 圖片 rip 取數據 很好 strip use str 上回寫到一半臨時有事,竟然沒有保存到!!!。這幾天也是因為家人過來玩。。我也不知道寫到哪兒了。我發現狗東這個奸賊很多數據是請求請求再請求,然後才拿到我們看到的數據顯示上去的。我嘗試了一下找齊這個數據確
【Python】從0開始寫爬蟲——豆瓣電影
for tag pes wing 信息 kit headers 自動 動畫 1. 最近略忙。。java在搞soap,之前是用工具自動生成代碼的。最近可能會寫一個soap的java調用 2. 這個豆瓣電影的爬蟲。扒信息的部分暫時先做到這了。扒到的信息如下 from s
從零開始寫一個Spark Structured Streaming程式來統計單詞個數
本文將從零開始寫一個Spark Structured Streaming程式來統計單詞的個數。單詞的來源是socket,讀者也可以換成kafka,計算的結果輸出到控制檯,讀者也可以改成輸出到kafka的某個topic。 準備環境: JDK和Scala安裝,並配置好環境變數JAVA_H
從0開始搭建一個微服務的持續交付系統
本文介紹瞭如何利用開源軟體快速搭建一套微服務的持續交付系統。本文假設的環境是Linux作業系統,用到的軟體包括Git、Jenkins、Salt、ZooKeeper、Apache等。開始之前,我先簡單介紹下持續交付和微服務的概念,以便大家更好的理解本文的精華。 什麼是持續交付?我們先舉個物流的例子
教你從零開始寫一個雜湊表--導讀
雜湊表是一個可以提供快速實現關聯陣列的資料結構。“雜湊”一詞會讓人產生困惑,下面我做了個總結。 雜湊表由一系列的桶組成,每一個桶儲存一個鍵值對。為了能夠確定一個鍵值對應該儲存在哪個桶裡,關鍵字要傳遞給雜湊函式。雜湊函式返回一個指明桶陣列索引的整數。當我們想要查詢一個鍵值對時,我們對關
教你從零開始寫一個雜湊表--雜湊表結構
我們的鍵-值對(items),每一個都會被儲存在結構體中: // hash_table.h typedef struct { char* key; char* value; } ht_item; 我們的雜湊表儲存了一組鍵值對的指標,以及雜湊表大小的一些細節和
教你從零開始寫一個雜湊表--雜湊衝突
雜湊函式把一個無窮大的輸入集合對映到一個有限大小的輸出集合。不同的關鍵字輸入會被對映到同一個陣列下標,這就導致了桶的衝突。雜湊表必須實現解決衝突的方法。 我們的雜湊表將使用開放地址法和再雜湊法。在桶索引衝突後,再雜湊法會使用兩個雜湊函式來計算鍵值對將要儲存的桶索引值。 有關其他雜
教你從零開始寫一個雜湊表--雜湊函式
在這一節,我們來編寫雜湊函式。 我們選擇的雜湊函式應該具有(以下特性): 把字串作為輸入,返回0到m(我們設計的桶陣列的長度)的數字; 對於一組平均的輸入返回分佈比較均勻的桶索引。如果我們的雜湊函式不是均勻分佈的,它可將會把較多的一些鍵值對放在某幾個桶中。這將會導致更
教你從零開始寫一個雜湊表--附錄
附錄:其他的衝突處理方案 常見的兩種雜湊衝突解決方案如下: 連結串列法 開放定址法 連結串列法 分離連結串列法中,每一個桶包含一個連結表。當鍵值對的鍵衝突時,鍵值對會被加入到這個列表中。它支援的方法如下: 插入: 計算關鍵字的雜湊值來查詢桶的下表索引。