# 76.Passing Data via Functions Across Widgets(透過小部件之間的函數傳遞數據)
**1.在quiz.dart中新增儲存選定答案的列表**
```dart=
class _QuizState exiends State<Quiz> {
// 新增儲存選定答案的空列表,因為不會重新分配變量所以可以在前面添加final
final List<String> selectedAnswers = [];
```
**2.新增一個名為chooseAnswer,接收一個String參數為answer的函數**
```dart=
// 新增將選定答案加入到selectedAnswers列表的函數
void chooseAnswer(String answer) {
// 將answer加到列表內
selectedAnswers.add(answer);
}
```
**3.將chooseAnswer當作值傳到QuestionScreen中**
```dart=
screenWidget = const QuestionsScreen(chooseAnswer);
```
**4.在QusetionsScreen中接收傳遞過來的值**
```dart=
class QuestionsScreen extends StatefulWidget {
// 這邊老師使用必須提供的命名函數
const QuestionsScreen({super.key, required this.onSelectAnswer});
// 也可以使用位置函數,如下
// const QuestionsScreen(this.onSelectAnswer, {super.key});
// 新增一個接受String參數且不回傳任何內容的函數型別
final void Function(String answer) onSelectAnswer;
```
**5.回到quiz.dart修改screenWidget**
```dart=
screenWidget = QuestionsScreen(onSelectAnswer: chooseAnswer);
// 如果是使用位置函數則用下面程式碼
// screenWidget = QuestionsScreen(chooseAnswer);
```
**6.在QusetionsScreen中處理選定答案**
```dart=
void answerQuestion(String selectedAnswer) {
// widget為State內建的功能,讓此處可以調用class QuestionsScreen內的方法及屬性
widget.onSelectAnswer(selectedAnswer);
setState(() {
currentQuestionIndex++;
});
}
```
**7.修改onTap的動作**
```dart=
// 因為onTap是一個不帶參數且不返回值的函數{required void Function() onTap}
// 所以這邊需要使用匿名函數來傳遞selectedAnswer
return AnswerButton(
answerText: answer,
onTap: () {
answerQuestion(answer);
},
);
```
# 77.More Conditions(更多條件)
**當問題回答完時將畫面導回開始頁面**
```dart=
// 導入questions.dart
import 'package:adv_basics/data/questions.dart';
// 修改chooseAnswer函數
void chooseAnswer(String answer) {
// 在列表的最後添加answer
selectedAnswers.add(answer);
// 檢查選定答案的數量是否等於問題的數量
if (selectedAnswers.length == questions.length) {
setState(() {
// 重置selectedAnswers列表
// 由於要將該列表清空,因此要將上方的selectedAnswers變數進行修正
// 將final拿掉變更為 List<String> selectedAnswers = [];
selectedAnswers = [];
activeSvreen = 'start-screen';
});
}
}
```
# 78.Getting Started with the Results Screen(開始使用結果畫面)
**1.建立一個StatelessWidget的results_screen.dart**
![results](https://hackmd.io/_uploads/By5dWT526.jpg)
**2.在quiz.dart中導入ResultsScreen(),並且將問題答完後的畫面導至結束頁面**
```dart=
import 'package:adv_basics/results_screen.dart';
// 將畫面導至結束頁面
void chooseAnswer(String answer) {
selectedAnswers.add(answer);
if (selectedAnswers.length == questions.length) {
setState(() {
selectedAnswers = [];
activeScreen = 'results-screen';
});
}
}
// 如果 activeScreen 的值是 'results-screen'
if (activeScreen == 'results-screen') {
// 則設置 screenWidget 為 ResultsScreen Widget
screenWidget = const ResultsScreen();
}
```
# 79.Passing Data to the Result Screen(將資料傳遞到結果畫面)
**1.在ResultsScreen中新增chosenAnswers以接受傳遞過來的參數**
```dart=
class ResultsScreen extends StatelessWidget {
// 也可以使用位置函數,如下
// const ResultsScreen(this.chosenAnswers,{super.key});
const ResultsScreen(
{super.key, required this.chosenAnswers});
final List<String> chosenAnswers;
```
**2.在quiz.dart導至結束畫面的函數中增加要傳遞的參數**
```dart=
if (activeScreen == 'results-screen') {
// 如果是使用位置函數則用下面程式碼
// screenWidget = ResultsScreen(selectedAnswers);
screenWidget = ResultsScreen(
chosenAnswers: selectedAnswers,
);
}
```
# 80.Introducing Maps & 'for' Loops(引入地圖與for迴圈)
**1.在ResultsScreen中新增getSummaryData函數**
```dart=
List<Map<String, Object>> getSummaryData {
// 創建一個空的 List 用於存儲摘要資訊
// summary為列表,內容為Map
// Map是一種用於存儲鍵-值對的泛型資料結構,因此為Map<key, value>
// <String, Object> 這邊表示value可以是任何對象,在使用時需要進行型別轉換
// 也可以使用<String, dynamic> 這邊的value一樣可以是任何對象,但不會進行型別檢查
final List<Map<String, Object>> summary = [];
// 遍歷選擇的答案列表
// for迴圈由三個部分組成
// 1.輔助變數,為此循環創建的,名稱可以自行決定,但要進行初始化var i = 0;
// 2.迴圈繼續執行的條件,i < chosenAnswers.length;,只要條件成立就會一直執行
// 3.每次迭代後的條件,i++
for (var i = 0; i < chosenAnswers.length; i++) {
// 將每個問題的摘要資訊添加到 summary 中
// 由於summary是一個列表,但列表內儲存的是Map
// 因此summary.add()內要添加{}
summary.add(
{
// 問題的索引(使用冒號進行分隔而不是用等號)
'question_index': i,
// 問題的文本
'question': questions[i].text,
// 正確答案(因為每個問題的答案都是第一個選項
// 所以這邊answers[]索引為0,指向第一個元素
'correct_answer': questions[i].answers[0],
// 用戶選擇的答案
'user_answer': chosenAnswers[i]
},
);
}
// 返回摘要資訊的 List
return summary;
}
```
**2.導入Questions.dart**
```dart=
import 'package:adv_basics/data/questions.dart';
```
# 81.Using 'for' Loops In Lists(在列表中使用for迴圈)
**1.使用for...in將數值添加到列表中**
```dart=
final numbers = [5, 6];
// 此處myList = [1,2,5,6]
final myList = [
1,
2,
for (final num in numbers)
num,
];
```
**2.使用...擴展運算符**
```dart=
// ...用於將一個集合(列表、集合、映射等)的元素展開或擴展到另一個集合中。
final numbers = [5, 6];
// 此處myList也是=[1,2,5,6]
final myList = [
1,
2,
...numbers
];
```
# 82.Note: A Typo In The Next Lecture(下個單元的拼字錯誤)
```dart=
Text(((data['question'] as int) + 1).toString()),
=> Text(((data['question_index'] as int) + 1).toString()),
```
# 83.Accessing Map Values & Using 'Type Casting'(訪問映射值並使用類型轉換)
**新增questions_summary.dart**
```dart=
import 'package:flutter/material.dart';
class QuestionSummary extends StatelessWidget {
const QuestionSummary(this.summaryData,{super(key: key)});
final List<Map<String, Object>> summaryData;
@override
Widget build(BuildContext context) {
return Column(
children: summaryData.map((data) {
return Row(
children: [
Text(((data['question_index'] as int) + 1).toString()),
// 可以寫成下列程式碼
// Text('${(data['question_index'] as int) + 1}'),
],
);
}).toList(), // 遍歷summaryData並轉換為List放入Row的Text中
);
}
}
```
# 84.Combining Columns & Rows(組合列和行)
**1.添加Column()在問題索引內**
```dart=
Column(
children: [
Text(data['question'] as String),
const SizedBox(height: 5),
Text(data['user_answer'] as String),
Text(data['corrent_answer'] as String),
],
),
```
**2.在ResultsScreen導入questions_summary.dart並將問題摘要放入**
```dart=
import 'package:adv_basics/questions_summary/questions_summary.dart';
QuestionsSummary(getSummaryData());
```
**3.將quiz.dart的chooseAnswer()內的selectedAnswers = [];刪除**
# 85.Expanded To The Rescue!(擴大範圍)
**在questions_summary.dart中進行修改**
```dart=
child: Column(
children: summaryData.map((data) {
return Row(
children: [
Text(((data['question_index'] as int) + 1).toString()),
// 添加此Widget
// Expanded會沿著flex佔用可用的空間
// 此處的Expanded會包著Column並確保此Column佔用Row的可用空間(最大寬度)
Expanded(
child: Column(
children: [
Text(data['question'] as String),
const SizedBox(height: 5),
Text(data['user_answer'] as String),
Text(data['correct_answer'] as String),
],
),
),
],
);
}).toList(),
),
```
# 86.Filtering & Analyzing Lists(過濾和分析列表)
**在ResultsScreen中新增變數並修改程式碼**
```dart=
// 設定summaryData為獲取問卷回答的摘要資料
final summaryData = getSummaryData();
// 設定numTotalQuestions為計算問卷中總共有多少個問題
final numTotalQuestions = questions.length;
// 設定numCorrectQuestions為計算正確回答的問題數
// 因為summaryData的類型為List<Map<String, Object>>
// 所以可以直接使用where方法()
// 根據data['user_answer'] == data['correct_answer']的條件篩選summaryData列表
final numCorrectQuestions = summaryData.where((data) {
return data['user_answer'] == data['correct_answer']
}).length;
SizedBox(
width: double.infinity,
child: Container(
margin: const EdgeInsets.all(40.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'You answered $numCorrectQuestions out of $numTotalQuestions questions correctly!',
), // 將X及Y更改為動態變數,並將const刪除
const SizedBox(height: 30.0),
QuestionSummary(summaryData), // 將getSummaryData()修改為summaryData
const SizedBox(height: 30.0),
TextButton(
onPressed: () {},
child: const Text('Restart Quiz!'),
),
),
],
),
),
);
}
}
```
## **where方法的改寫**
![simple](https://hackmd.io/_uploads/BkuV4hYna.jpg)
# 87.Making Content Scrollable with SingleChildScrollView(使用SingleChildScrollView使內容可捲動)
**修改questions_summary.dart**
```dart=
class QuestionSummary extends StatelessWidget {
const QuestionSummary(this.summaryData, {super.key});
final List<Map<String, Object>> summaryData;
@override
Widget build(BuildContext context) {
// 將Column放入高度300的容器中
return SizedBox(
height: 300.0,
// 使用SingleChildScrollView讓Column內容可以滾動
// 只要Column內容高度超過SizedBox設定的高度300,就可以進行上下滾動
child: SingleChildScrollView(
child: Column(
children: summaryData.map((data) {
return Row(
children: [
Text(((data['question_index'] as int) + 1).toString()),
// 添加此Widget
// Expanded會沿著flex佔用可用的空間
// 此處的Expanded會包著Column並確保此Column佔用Row的可用空間(最大寬度)
Expanded(
child: Column(
children: [
Text(data['question'] as String),
const SizedBox(height: 5),
Text(data['user_answer'] as String),
Text(data['correct_answer'] as String),
],
),
),
],
);
}).toList(),
),
),
```
# 88.Beyond the Basics:Optional,Important Dart Features(超越基礎:可選的重要 Dart 功能)
### **1. 下底線**
在Dart中下底線(_)開頭表示為私有性,只能在定義他的文件中使用。主要是可以幫助我們更好地管理代碼,提高代碼的可維護性和安全性。
### **2. Dart getter方法**
(圖一)
![1](https://hackmd.io/_uploads/B1X2tPSAT.jpg)
(圖二)
![2](https://hackmd.io/_uploads/ryEYjDHCT.jpg)
圖一及圖二的功能是相同的,圖一是一個普通的方法(函數),圖二是利用了Dart內的getter方法
最大的不同就是圖一是函數,所以在後面調用的時候需要getSummarData();
而圖二為屬性,所以調用的時候只要打SummaryData;就可以了。
### **3. 箭頭函數**
箭頭函數通常用於只有一行return的代碼中,主要就是提高代碼的可讀性及維護性
![1](https://hackmd.io/_uploads/BklGzFHCT.jpg)
![3](https://hackmd.io/_uploads/BJ0GztBRa.jpg)
# 89.Module Summary(課程摘要)
**1. main函數必須調用runApp並傳遞小部件**
**2. 小部件中傳遞數據(可以接受函數作為參數的輸入值),然後使用這些數據**
**3. 使用if進行判斷以顯示不同的畫面及數值,使用for循環來取得所需的數據**
**4. 使用三元表達式進行代碼的簡化**
**5. 可將小部件當作狀態值進行管理**
**6. 利用Map<String, Object>進行型別安全的確認**
**7. SingleChildScrollView用法**
**8. 新的配置項及樣式選項(button的圓形邊框)**
**9. 自定義小部件(建立model時使用)**
**10. List的操作、分析(shuffle、add、where)**
**11. 特殊擴展符(...)的使用**