owned this note
owned this note
Published
Linked with GitHub
<!-- description[簡單的 Flutter 路由範例] -->
# Flutter 中的物件導向和路由
[筆記資源](https://niiuu.notion.site/Flutter-e623d577622a4013916e42723e40fdcf)
---
## UI / UX
**UI:USER INTERFACE 使用者介面
UX:USER EXPERIENCE 使用者體驗**
看的到的是UI,像是介面設計,抱括按鈕、圖示、排版、間距等設計。
體驗到的是UX,像是功能使用上的互動,能不能有良好的體驗。
兩者皆需要註冊帳號
[Figma官網](https://www.figma.com/)
[Flutter Flow 官網](https://flutterflow.io/)
[沒有好或壞的UI](https://hahow.in/contents/articles/60643d6526c9ad7b36be286a)
[Figma好處](https://hahow.in/contents/articles/609232fc4dd9f8ab3c4f1bc7)
## OOP
[codelabs](https://developers.google.com/codelabs/from-java-to-dart?index=..%2F..index#1)
**物件導向**是一種在設計程式的一種方式
但不是每一種程式語言都有這種概念
像是**C語言**就沒有,但在這之上的
**C++、Objective-C**就有物件導向
其他如 **Java**以及等等要介紹的 **Dart**
最容易判斷是不是物件導向只要認得
**Class 類別**這個概念
[DartPad](https://dartpad.dev/) 使用線上編譯器來學習吧
![](https://i.imgur.com/EgyME96.png)
![](https://i.imgur.com/ZHfa4jM.png)
Dart 沒有 Private 屬性
要設定唯獨變數要在變數名稱前面加底線`_`
例如 `String _name;`
在呼叫類別的時候,建構子引數可以直接使用 `this`
快速指定變數到類別中
在實力化物件的時候可以省略 `new` 關鍵字
[其他更詳細的介紹](https://ithelp.ithome.com.tw/articles/10265940)
## ROUTE
路由有兩種,一種是直接呼叫介面;另一種是幫每一頁命名
### 步驟
1. 創建兩個頁面,分別為 'first_screen.dart' 與 'second_screen.dart',參考下方的程式,建立Widget的基本架構
![](https://i.imgur.com/lZpJN4z.png)
#### 第一個頁面
```dar=
import 'package:flutter/material.dart';
class FirstScreen extends StatelessWidget {
const FirstScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
//TODO
},
child: const Text('Launch screen'),
),
),
);
}
}
```
### [這個筆記裡有BTN的介紹](https://niiuu.notion.site/Lesson-3-2c0a93ef7a664614b979a8d2f55908e1)
#### 第二個頁面
```dar=
import 'package:flutter/material.dart';
// 第二個頁面
class SecondScreen extends StatelessWidget {
const SecondScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
//TODO
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: Column(
children: [
//TODO
Center(
child: ElevatedButton(
onPressed: () {
//TODO
},
child: const Text('Go back!'),
),
),
],
));
}
}
```
**`標記TODO的地方,請暫時忽略它,稍後將繼續完善裡面的程式`**
**以下方法是定義路由名稱跳轉的方式**
2. 在 'main.dart' 中定義路由
![](https://i.imgur.com/T9BmViH.png)
3. 在 'first_screen.dart' 中的 **onPressed** 使用 **Navigator.pushNamed()** 跳轉到頁面二
![](https://i.imgur.com/y74nsy6.png)
4. 在 'second_screen.dart' 中的 **onPressed** 使用 **Navigator.pop()** 返回頁面一
![](https://i.imgur.com/oP3nmPI.png)
5. 讓我開始頁面間傳送一些參數,我們想要傳送三個東西分別為 ID, Title, Imgurl 到第二個頁面,我們需要在頁面一 'first_screen.dart' 中定義要傳入的參數
![](https://i.imgur.com/2mneT9X.png)
6. 在第二個頁面 'second_screen.dart' 定義這些要傳入的參數
![](https://i.imgur.com/8hBintY.png)
7. 我們可以使用這些參數了,我們用 Text 與 Image 配合 Column 來顯示傳進來的內容
```dar=
import 'package:flutter/material.dart';
// 第二個頁面
class SecondScreen extends StatelessWidget {
const SecondScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final routeArgs =
ModalRoute.of(context)?.settings.arguments as Map<String, String>;
final categoryTitle = routeArgs['title'];
final categoryId = routeArgs['id'];
final categoryImg = routeArgs['imgUrl'];
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: Column(
children: [
Text(categoryTitle ?? 'Nothing from the screen one'),
Text(categoryId ?? '?'),
Image.network(categoryImg ?? ''),
Center(
child: ElevatedButton(
onPressed: () {
// 通過從堆疊弹出當前路由
Navigator.of(context).pop();
},
child: const Text('Go back!'),
),
),
],
));
}
}
```
`🧠 Text、Image、Column 的用法,上次已經介紹過,忘記的請參考之前的筆記`
### 完整程式 [DartPad 可以直接點這裡看](https://dartpad.dev/?id=42073ff6d86dc88cc243f2049e0d65de)
1. first_screen.dart
```dart=
// import 'package:flutter/material.dart';
// 在AndroidStudio中記得要 import material(上面這行)
// 第一個頁面
class FirstScreen extends StatelessWidget {
const FirstScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// 使用命名路由跳轉到第二個界面
Navigator.of(context).pushNamed(
'/second', // 第二頁名稱
arguments: {
'id': 'Krabby Patty',
'title': 'From Screen One',
'imgUrl': 'https://i.imgur.com/sbOoRM2.gif',
}, // 傳參數到下一頁
);
},
child: const Text('Launch screen'),
),
),
);
}
}
```
2. second_screen.dart
```dart=
// import 'package:flutter/material.dart';
// 在AndroidStudio中記得要 import material(上面這行)
// 第二個頁面
class SecondScreen extends StatelessWidget {
const SecondScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final routeArgs =
ModalRoute.of(context)?.settings.arguments as Map<String, String>;
final categoryTitle = routeArgs['title'];
final categoryId = routeArgs['id'];
final categoryImg = routeArgs['imgUrl'];
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: Column(
children: [
Text(categoryTitle ?? 'Nothing from the screen one'),
Text(categoryId ?? '?'),
Image.network(categoryImg ?? ''),
Center(
child: ElevatedButton(
onPressed: () {
// 通過從堆疊弹出當前路由
Navigator.of(context).pop();
},
child: const Text('Go back!'),
),
),
],
));
}
}
```
3. main.dart
```dart=
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Named Routes Demo',
// 使用“/”命名路由来啟動程式
// 在這裡,程式將從 FirstScreen Widget 啟動
initialRoute: '/',
//'***名稱***': (context) => const ***頁面 Widget 名稱***
routes: {
'/': (context) => const FirstScreen(),
'/second': (context) => const SecondScreen(),
},
);
}
}
```
`print(A ?? B)`=>
if A == NULL print(B)
else print(A)
[影片詳細教學](https://www.youtube.com/watch?v=Lq4yBH7fATI)
[导航到对应名称的 routes 里 | Flutter 中文文档 | Flutter 中文开发者网站](https://flutter.cn/docs/cookbook/navigation/named-routes)
(更多跳轉參數)
[Flutter中管理路由栈的方法和应用 - 知乎](https://zhuanlan.zhihu.com/p/56289929)
[路由管理 | 《Flutter实战·第二版》](https://book.flutterchina.club/chapter2/flutter_router.html#_2-4-1-%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%A4%BA%E4%BE%8B)
---
`main.dart`
```dart=
import 'package:flutter/material.dart';
import 'package:route/second_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: {
'/': (context) => const FirstPage(),
'/second': (context) => const SecondPage()
},
theme: ThemeData( // 設定不同的主題讓UI更好看
primarySwatch: Colors.blue,
appBarTheme: const AppBarTheme(color: Colors.amber),
useMaterial3: true),
);
}
}
class FirstPage extends StatelessWidget {
const FirstPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Page 1'),
),
body: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent,
foregroundColor: Colors.white),
onPressed: () {
// 使用 MaterialPageRoute 跳轉頁面
// Navigator.of(context)
// .push(MaterialPageRoute(builder: (context) => SecondPage()));
// 使用命名規則跳轉 並傳入 List argument
Navigator.of(context).pushNamed('/second',
arguments: ['青麥香', '彩虹堂', '金香堡', '莫尼', '緣來', '尼好']);
},
child: const Text('Go To Second Page'),
),
),
);
}
}
```
`second_page.dart`
```dart=
import 'dart:math';
import 'package:flutter/material.dart';
class SecondPage extends StatefulWidget {
const SecondPage({Key? key}) : super(key: key);
@override
State<SecondPage> createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
int random = 0; // 定義 random index 變數
@override
Widget build(BuildContext context) {
// 接收傳入的變數 指定為 List 型態
final routeArgs = ModalRoute.of(context)!.settings.arguments as List;
return Scaffold(
appBar: AppBar(
title: const Text('早餐吃什麼?'),
),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
border: Border.all(width: 5, color: Colors.black87),
borderRadius: BorderRadius.circular(24)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center, // Column 置中參數設定
children: [
Text(
routeArgs[random]!,
style:
const TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 24,
),
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24),
backgroundColor: Colors.purple,
foregroundColor: Colors.white),
onPressed: () {
// Navigator.of(context).pop(); // 關閉 SecondPage()
setState(() {
random = Random().nextInt(routeArgs.length);
});
},
child: const Text('Random', style: TextStyle(fontSize: 18),),
),
Text('BXXXXXXX GDSC NIU')
],
),
),
),
);
}
}
```
---
## 成果幫我截圖放這裡!!
範例
![](https://i.imgur.com/uKbuJ05.png)
![](https://i.imgur.com/uS49Bp8.png)
![](https://i.imgur.com/doSH0Nf.png)
![](https://i.imgur.com/cw2B1O8.png)
![](https://i.imgur.com/8GAazLW.png)