Flutter作業#04 — 製作 about 頁面

作業:打造吸睛的 About 頁面

成品 gif

匯入圖片

  1. 創建 assets 資料夾
  2. 將圖片導入(直接複製貼入資料夾即可)
  3. pubspec.yaml 頁面的 flutter 下一階層(要空 2 個空格!)放入 assets 與圖片路徑( “-” 前也要 2 個空格、“-”後要 1 個空格)。

各 widget/ method 拆分

buildAppBar()

  AppBar buildAppBar() { 
return AppBar(
title: Text(
"達菲 Duffy",
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
);
}

*AppBar 如果使用 extract widget 會出現錯誤提示: ‘The argument type ‘buildBarTitle’ can’t be assigned to the parameter type ‘PreferredSizeWidget?’,不過改用 extract method 就沒問題了。

buildBackground()

class buildBackground extends StatelessWidget {
const buildBackground({
super.key,
});

@override
Widget build(BuildContext context) {
return Container(
constraints: BoxConstraints.expand(), //使 child widget 對齊 parent widget 四邊
child: Opacity(
opacity: 0.24, //透明度
child: Image.asset(
'assets/background.jpg', //呼叫圖片路徑
fit: BoxFit.cover,//使圖片自動縮放以填滿整個空間
),
),
);
}
}

BoxConstraints.expand() 是 Flutter 中用來建立一個 BoxConstraints 的方法,這個方法會將 BoxConstraints 的寬度和高度設置為無限,這意味著子 widget 將被擴展到父 widget 可用的所有空間。這在需要讓 widget 充滿整個可用空間時非常有用,例如在構建佈局或佈局容器時。

BoxFit.cover 是 Flutter 中的一個屬性,用於控制如何將圖像或其他可伸縮的內容適應其容器的大小。當使用 BoxFit.cover 時,內容會被縮放並裁剪,以確保無論容器的比例如何變化,都能填滿整個容器,並且不會出現未填滿的空白區域。

buildAvatar()

  Padding buildAvatar(String fileName) {
return Padding(
padding: const EdgeInsets.all(8.0), //使 child widget 上下左右四方都能有相同的空間距離
child: SizedBox( //使用 SizedBox 設置 child widget 的尺寸
width: 320,
height: 320,
child: CircleAvatar( //此 widget 自動生成圓形遮罩的圖片
foregroundImage: AssetImage('assets/avatar/$fileName.jpg'),
),
),
);
}
}

buildDescription()

  Padding buildDescription(String descrip) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 24),//設置上下方皆有 24 的間距
child: Text(
descrip, //設置成參數以便後續重複使用
style: TextStyle(fontSize: 18),
),
);
}

buildDetailRow()

  Row buildDetailRow(
IconData iconName, Color colorName, String subtitle, String gender) {
return Row(
children: [
Expanded(
flex: 1, //在 Row 中將每個 children widgets 用 Expanded 包住並設置flex ,來決定不同 widgets 所佔的空間比例。
child: Container(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, //Column 的 corssAxis 是水平向,對齊最左側。
children: [
Icon(
iconName, //設置成型別為 IconData 的參數
size: 36,
color: colorName, //設置成型別為 String 的參數
),
Text(
subtitle, //設置成型別為 String 的參數
style: TextStyle(fontSize: 14),
)
],
),
),
),
),
Expanded(
flex: 5, //這個 flex 比較高,在此 row 中所佔空間就比較大
child: Container(
child: Text(
gender, //設置成型別為 String 的參數
style: TextStyle(
fontSize: 18,
),
),
),
),
],
);
}

Expanded widget 是 Flutter 中的一個布局 widget,它用來在 Flex 容器(例如 RowColumn)中展開子 widget 以填滿可用空間。 Expanded 使用 flex 屬性來確定如何在可用空間中分配其大小。

flex 屬性是用來指定這個 Expanded widget 應該分配到多少可用空間,默認值是 1。當有多個 Expanded widget 存在時,flex 屬性的值會決定每個 Expanded widget 占用的空間比例。

buildGallery()

  SingleChildScrollView buildGallery(String characterName) {
//有三張圖片,本來想試試用 for loop 方式回傳三次 galleryItem 的,但水平還不夠沒能成功🤣 ,就先一步步手動設置啦。
String file1 = characterName + "1";
String file2 = characterName + "2";
String file3 = characterName + "3";

return SingleChildScrollView(
scrollDirection: Axis.horizontal, //這次是要橫向滑動
child: SizedBox(
width: 810,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, //最左跟最右對齊 parent widget,中間生成等距空位。
children: [
galleryItem(file1),
galleryItem(file2),
galleryItem(file3),
],
),
),
);
}



//galleryItem():
SizedBox galleryItem(String fileName) {
return SizedBox(
width: 260,
height: 260,
child: ClipRRect( //製作圓角圖片! 是 swift 的 clipToBound 了😆
borderRadius: BorderRadius.circular(12), //= 設置 layer.cornerRadius
child: Image.asset('assets/gallery/$fileName.jpg')),
);
}

ClipRRect 是 Flutter 中的一個 widget,用來裁剪其子 widget 的形狀,通常是以圓角矩形的形式。

borderRadius: 用來設定圓角的半徑。可以使用 BorderRadius.circular(radius) 來設置所有四個角的半徑相同,也可以使用 BorderRadius.only 來為不同的角設置不同的半徑。

完整頁面拼接

class MainApp extends StatelessWidget {
MainApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false, //隱藏模擬器右上方的 "debug" 小紅標
home: Scaffold(
appBar: buildAppBar(),
body: Stack(children: [ //使用 Stack 使得背景圖片與前面的文字內容可以z軸疊加
buildBackground(),
Padding(
padding: EdgeInsets.symmetric(horizontal: 24),//設置左右方皆有 24 的間距
child: SingleChildScrollView( //scrollView 使得超出螢幕高度的內容可以滑動顯示
child: Column( //使用 column 來安裝直向的 widgets
children: [
buildAvatar("duffy"),
buildDescription(
"""米妮為了讓米奇在環遊世界不會感到孤單,於是在米奇準備出海遠航前親手為米奇風之了一直泰迪熊玩偶,這便是達菲熊故事的開始。由於米奇實在喜歡這隻玩偶,甚至希望能和他一起散步,所以後來奇妙仙子邊賦予了達菲熊生命。達菲變作為了了在達菲熊家族第一個誕生的人物。他臉上有著與米奇相似的形狀,有著棕色的毛髮。"""),
Divider(height: 1), //分隔線
buildDetailRow(
Icons.account_circle, Colors.red.shade400, "性別", "男"),
Divider(height: 1), //分隔線
buildDetailRow(
Icons.stars, Colors.orange, "特徵", "腳掌和屁股上有著米奇形狀的印記"),
Divider(height: 1), //分隔線
buildDetailRow(Icons.mood, Colors.green.shade600, "性格", "害羞"),
Divider(height: 1), //分隔線
buildDetailRow(Icons.recommend, Colors.blue.shade800, "愛好",
"旅遊,拍照,認識新朋友"),
Divider(height: 1), //分隔線
SizedBox(height: 24), //手動設置空位
buildGallery("duffy"),
SizedBox(height: 36), //手動設置空位
],
),
),
),
]),
),
);
}


void main() {
runApp(MainApp());
}

*其實有幾次 widget 的小燈泡沒有提供直接 extract method 的選項,就乾脆自行手動創建 Row xxxxx(),再把這一整段程式碼剪下後直接貼上😆 確認最後 return 是正確的 widget 型別就好~

--

--