Day 10. 趨勢頁面
本系列同步發表在 第11屆鐵人賽
趨勢頁面(Trending Page)
好久沒從 UI 設計圖來分解該怎麼轉換成程式碼了。
今天就看個分解圖吧~
lib/pages/trending/trending.dart
import 'package:flutter/material.dart';
import 'package:gitme_reborn/pages/trending/developer.dart';
import 'package:gitme_reborn/pages/trending/project.dart';
class TrendingPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: TabBar(
tabs: <Widget>[
Tab(text: "Project"),
Tab(text: "Developer"),
],
),
actions: <Widget>[
PopupMenuButton(
itemBuilder: (BuildContext context) {
return [
PopupMenuItem(
child: Text("Date range: daily"),
),
];
},
),
],
),
body: TabBarView(
children: <Widget>[
TrendingProjects(),
TrendingDevelopers(),
],
),
),
);
}
}
再來看看程式碼是不是清晰多了呢?
原則上, TrendingPage
與 MainPage
差不多,我幾乎是同一套排版直接拿過來用的。
原設計圖上是分成 Repos 和 Users,不過之後我用的 API 會是使用 github-trending-api
。
github-trending-api 中的分類是 Projects 和 Developers,所以我乾脆改成 TrendingProjects
和 TrendingDevelopers
囉~
趨勢專案(TrendingProjects)
lib/pages/trending/project.dart
import "package:flutter/material.dart";
import 'package:gitme_reborn/utils.dart';
class TrendingProjects extends StatefulWidget {
@override
_TrendingProjectsState createState() => _TrendingProjectsState();
}
class _TrendingProjectsState extends State<TrendingProjects> {
List trendProjectList = [
{
"author": "google",
"name": "gvisor",
"avatar": "https://github.com/google.png",
"url": "https://github.com/google/gvisor",
"description": "Container Runtime Sandbox",
"language": "Go",
"languageColor": "#3572A5",
"stars": 3320,
"forks": 118,
"currentPeriodStars": 1624,
"builtBy": [
{
"href": "https://github.com/viatsko",
"avatar": "https://avatars0.githubusercontent.com/u/376065",
"username": "viatsko"
}
]
},
...(略)
];
@override
Widget build(BuildContext context) {
return Scrollbar(
child: RefreshIndicator(
child: ListView.separated(
padding: EdgeInsets.all(0.0),
itemCount: trendProjectList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
"${trendProjectList[index]["author"]} / ${trendProjectList[index]["name"]}"),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 8.0),
Text(
"★ ${trendProjectList[index]["currentPeriodStars"]} stars today"),
SizedBox(height: 8.0),
Text(trendProjectList[index]["description"]),
SizedBox(height: 8.0),
Row(
children: <Widget>[
Text("★ ${trendProjectList[index]["stars"]}"),
SizedBox(width: 16.0),
...buildBuiltByList(trendProjectList[index]["builtBy"]),
],
)
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("● ", style: TextStyle(color: hexToColor(trendProjectList[index]["languageColor"]), fontSize: 24.0),),
Text(trendProjectList[index]["language"]),
],
),
contentPadding:
EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
onTap: () {},
);
},
separatorBuilder: (BuildContext context, int index) =>
const Divider(height: 0.0),
),
onRefresh: () async {
return Future.delayed(Duration(seconds: 2), () {});
},
),
);
}
List<Padding> buildBuiltByList(List builtByList) {
List builtBys = builtByList.map((builtBy) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.0),
child: CircleAvatar(
radius: 10.0,
backgroundImage: NetworkImage(builtBy["avatar"]),
),
);
}).toList();
if (builtBys.length > 7) {
return builtBys.sublist(0, 6);
} else {
return builtBys;
}
}
}
如果對照的分析圖看,ListTile
的部份應該沒什麼問題。
不過值得一提得是 CircleAvatar(s),這部份是自己寫了 buildBuiltByList
這個函數來實現,讓它能拿成好幾個頭像並列。
小提醒:
- 說個小插曲,其實一開始我在
List.trailing
裡,用Row
來排列兩個Text
時有噴出錯誤,不過後來參考 StackOverflow - Placing two trailing icons in ListTile 解決。trendProjectList
裡面的資料是暫時從github-trending-api
README.md 中借來的。- 顯示語言顏色標籤,由於 Flutter 不能直接吃
#001122
這種色碼字串,於是我又找來 StackOverflow - Flutter/Dart: Convert HEX color string to Color? 作支援~
趨勢開發者(TrendingDevelopers)
沒錯,今天的套路就是先放分解圖~
lib/pages/trending/developer.dart
import "package:flutter/material.dart";
class TrendingDevelopers extends StatefulWidget {
@override
_TrendingDevelopersState createState() => _TrendingDevelopersState();
}
class _TrendingDevelopersState extends State<TrendingDevelopers> {
List trendDeveloperList = [
{
"username": "google",
"name": "Google",
"type": "organization",
"url": "https://github.com/google",
"avatar": "https://avatars0.githubusercontent.com/u/1342004",
"repo": {
"name": "traceur-compiler",
"description":
"Traceur is a JavaScript.next-to-JavaScript-of-today compiler",
"url": "https://github.com/google/traceur-compiler"
}
},
...(略)
];
@override
Widget build(BuildContext context) {
return Scrollbar(
child: RefreshIndicator(
child: ListView.separated(
padding: EdgeInsets.all(0.0),
itemCount: trendDeveloperList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: CircleAvatar(
backgroundImage:
NetworkImage(trendDeveloperList[index]["avatar"]),
radius: 28.0,
),
title: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(trendDeveloperList[index]["name"]),
SizedBox(width: 16.0),
Text(trendDeveloperList[index]["username"]),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 8.0),
Text(
"🔥 ${trendDeveloperList[index]["repo"]["name"]}"),
SizedBox(height: 8.0),
Text(trendDeveloperList[index]["repo"]["description"]),
],
),
contentPadding:
EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
onTap: () {},
);
},
separatorBuilder: (BuildContext context, int index) =>
const Divider(height: 0.0),
),
onRefresh: () async {
return Future.delayed(Duration(seconds: 2), () {});
},
),
);
}
}
理解了 TrendingPrjects
,這個就簡單多了,只需要注意 ListTile
的屬性填入了什麼就好~
小提醒:
- 如果想作的極致點,想再縮短一點程式碼,還可以將
ListTile
個別封裝成 Widget,不過目前看起來還沒有必要性,所以就先這樣吧~
–
成果
感覺起來好像沒作很多操作,但其實了很多時間調整怎麼用 Widget 到最大效果~ 也是挺燒腦的… (暈
30 天中,UI 部份也終於要到尾聲了~ 如果看拉 UI 有些煩的同學,加油! 再撐個 2-3 天囉~ 😂