# 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. 特殊擴展符(...)的使用**