Flutter News App
本章節透過 flutter 撰寫簡單的 news app, 他將會包含了基本的登入以及註冊以及新聞清單等。
github 的 master branch
github 專案: https://github.com/xingobar/flutter-news.git
- 首先沒有安裝環境的可以透過官網的教學進行安裝
2. 透過指定先創建專案
flutter create flutter_news
3. 將 lib/main.dart 程式碼進行修改
return MaterialApp( title: 'News', debugShowCheckedModeBanner: false, // 不顯示測試的 debug mode theme: ThemeData( brightness: Brightness.dark, // 亮度 primarySwatch: Colors.blue, textTheme: TextTheme( // 字型樣式 headline: TextStyle(fontSize: 20.0), )), home: Scaffold( body: SafeArea( child: Container( child: Text('test'), ), ), ), );}
4. 在 lib 新增一個 pages 資料夾, 然後在 pages 資料夾下新增 login.dart
5. login.dart 程式碼架構
6. 在 lib 新建 res 資料夾且在裡面新增 string.dart 以及 index.dart
string.dart 輸入一些文字的變數如下
class Ids { static const LOGIN_ACCOUNT_LABEL = '帳號'; static const LOGIN_ACCOUNT_HINT = '請輸入帳號'; static const LOGIN_PASSWORD_LABEL = '密碼'; static const LOGIN_PASSWORD_HINT = '請輸入密碼';}
index.dart
export 'string.dart'
ps: 這樣只要之後 import res 的 index.dart 的話, 就可以使用 Ids 變數裡面的名稱
然後 Login 因為是 state 會改動的, 所以要使用 StatefulWidget
首先在 _LoginState 宣告
// 帳號 focusfinal _accountFocusNode = new FocusNode();// 密碼 focusfinal _passwordFocusNode = new FocusNode();// 帳號 controllerTextEditingController _accountController = new TextEditingController();// 密碼 controllerTextEditingController _passwordController = new TextEditingController();
接著設定帳號輸入匡
TextField( focusNode: _accountFocusNode, // 設定 focus controller: _accountController, // 設定 controller decoration: InputDecoration( // 設定 input 樣式 border: OutlineInputBorder(), labelText: Ids.LOGIN_ACCOUNT_LABEL, // 使用 string.dart hintText: Ids.LOGIN_ACCOUNT_HINT), // 使用 string.dart onSubmitted: (String value) { print('trigger event'); },)
密碼的輸入匡也跟帳號雷同, 只是多了 obscureText: true
obscureText: true
結果:
接著製作登入按鈕, 註冊按鈕也雷同
ButtonTheme( minWidth: _getInputWidth(), /// 設定按鈕的最小寬度, 也可以使用 SizeBox 設定 height: Dimens.height_dp_50, // 在 res 裡面新增 dimens.dart child: RaisedButton( shape: RoundedRectangleBorder( // 設定圓形的 border borderRadius: BorderRadius.circular(Dimens.border_radius_50)), color: Colors.white30, child: Text(‘登入’), onPressed: () { print(‘login’); }, ),)
結果如下:
接著會發現程式很多重複的地方, 我們來調整一下
創建 _buildTextField 函式, 因為大家的 text field 樣式都雷同
// 創建 text fieldTextField _buildTextField( FocusNode focusNode, TextEditingController inputController, String labelText, String hintText, Function callback) { return TextField( focusNode: focusNode, controller: inputController, decoration: InputDecoration( border: OutlineInputBorder(), labelText: labelText, hintText: hintText, ), onSubmitted: (String value) { // callback callback(value); }, );}
建立 _buildButton 函式
// 登入Widget _buildButton(String title, Color color, Function callback) { return ButtonTheme( minWidth: _getInputWidth(), height: Dimens.height_dp_50, child: RaisedButton( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(Dimens.border_radius_50)), color: color, child: Text(title), onPressed: () { callback(); }, ), );}
新增帳號以及密碼的驗證函示, 以下先使用最簡單的判斷, 判斷帳號密碼是否是所規定的, 倘若不是的話則會顯示錯誤, 正在之後會進行調整
// 檢查帳號有效性void checkAccountValid() { if (_accountController.text != Ids.TEST_ACCOUNT) { setState(() { _accountError = '帳號錯誤'; });; _accountFocusNode.requestFocus(); // 設置焦點 }}// 檢查密碼有效性void checkPasswordValid() { if (_passwordController.text != Ids.TEST_PASSWORD) { setState(() { _passwordError = '密碼錯誤'; }); _passwordFocusNode.requestFocus(); // 設置焦點 }}
完整畫面如下:
以上這樣登入畫面就建置好了
接著我們已同樣的方式建置註冊畫面
首先我們先新增一個 register.dart, 而註冊畫面跟登入畫面一樣是有狀態性的, 所以我們將其設置為 stateful 的 widget 如下代碼:
註冊這邊會使用一個新的 widget 叫做 SingleChildScrollView
這個元件是為了避免因為鍵盤的出現導致畫面繪製的時候出錯, 比較建議使用的場景為元件數較少的情況下使用, 倘若資料會是動態繪製出來的話, 建議改為使用 ListView
SingleChildScrollView(
child: Column( children: <Widget> [
// 建置輸入帳號匡
// 建置輸入密碼匡 // 建置輸入確認密碼匡 // 導回登入畫面的 ] ))
以上為要在 SingleChildScrollView 擺放的 widget
在 string.dart 增加以下 const variable
// 註冊static const REGISTER_ACCOUNT_LABEL = '帳號';static const REGISTER_ACCOUNT_HINT = '請輸入帳號';static const REGISTER_PASSWORD_LABEL = '密碼';static const REGISTER_PASSWORD_HINT = '請輸入密碼';static const REGISTER_CONFIRM_PASSWORD_LABEL = '輸入密碼';static const REGISTER_CONFIRM_PASSWORD_HINT = '請再次輸入密碼';
建置輸入帳號的輸入匡, 而密碼以及確認密碼程式碼都類似
Container( width: this._getInputWidth(), child: TextField( focusNode: _accountFocusNode, controller: _accountController, decoration: InputDecoration( hintText: Ids.REGISTER_ACCOUNT_HINT, labelText: Ids.REGISTER_ACCOUNT_LABEL, border: OutlineInputBorder( borderRadius: BorderRadius.circular(Dimens.border_radius_50), ) ), ),)
增加帳號以及密碼的驗證
帳號以及密碼的驗證雷同
// 確認帳號是否驗證通過Map<String,String> _checkAccountValid() { Map<String, String> errorMessage = {}; // 儲存錯誤訊息 if (_accountController.text.length < 6) { errorMessage['Error'] = Error.ERROR_ACCOUNT_MIN; } return errorMessage;}
新增一個 _handleAuth 函式, 而 handleAuth 跟前面的登入程式略顯不同
void _handleAuth() { Map<String, String> error = {}; // 用於存放錯誤訊息 error = this._checkAccountValid(); if (error.keys.length != 0) { _accountError = error['Error']; _accountFocusNode.requestFocus(); } else { _accountError = null; } error = this._checkPasswordValid(); if (error.keys.length != 0) { _passwordError = error['Error']; _passwordFocusNode.requestFocus(); } else { _passwordError = null; } error = this._checkConfirmPasswordValid(); if (error.keys.length != 0) { _confirmPasswordError = error['Error']; _confirmPasswordFocusNode.requestFocus(); } else { _confirmPasswordError = null; } setState(() { _accountError =_accountError; _passwordError = _passwordError; _confirmPasswordError =_confirmPasswordError; });}
註冊畫面
而我們一開始跳轉的方式是使用
Navigator.of(context).push(context, MaterialPageRouter( builder: (context) => new Login() or new Register()
))
而現在我們改為替 router 命名
在 lib 資料夾下多新增 router.dart
在 main.dart 要設置 onGenerateRoute
這樣我們在頁面就可以使用匿名過的路由了
如下所示:
現在我們來製作首頁
首頁只會單純顯示資料所以是使用 StatelessWidget
在首頁我們先使用 Drawer 製作側邊欄, 只是如下範例將 Drawer 抽成一個 Widget, 供其他頁面使用, 因為代碼有點長, 所以這邊只顯示片段
而要如何顯示 drawer 呢, 要在 scaffold 增加 drawer 的參數, 如下圖
結果會如下:
接著開始製作首頁內容, 我們會使用 listview 建置內容, 並且透過 ListView 搭配 Container 以及 Card, 以利於製作卡片顯示
itemCount: 跑幾次 listview
itemBuilder: 卡片顯示的內容
結果如下:
安裝 fontawesome 套件, 在 pubspec.yaml 增加以下數值
font_awesome_flutter: ^8.10.0
接著創建首頁的 Bottom 導覽列
在 ui 資料夾下新增 BottomNavigationWidget class, 因為要記錄點擊的索引, 所以該 widget 是要 StatefulWidget, 程式如下圖
底部導航結果
首頁增加 tabbar, 如下圖
首先先將 home.dart 的結構調整, 將最外層改為 DefaultTabController 如下圖
在使用 tabController 的同時需要宣告 _tabController
tabList 的變數位於 string.dart, 如下圖
我們接著為首頁增加輪播圖, 首先我們先來安裝 carousel_slider 套件, 先在 pubspec.yaml 增加
根據文件進行備至 CarouselController
這邊讓輪播圖自動播放, 且每三秒換下一張, 然後總數為 5 個以及設置初始值
設置輪播圖的小圓點
增加滑到底的時候會有 loading 圖示去增加 card
要將原先的 NewListWidget 進行調整, 因為要設置 SingleScrollView,
而我們在外面設置 Column , 因為要設置 Expand, 而要設置 Expand 的前提是他的 parent 需要是 Flex。不過我們首先先設置 scroll 監聽的狀態
ListView 我們要先關閉 scroll 事件, 否則會跟 SingleChildScrollView 衝突
多增加是否顯示加載圖示
接著要製作影音畫面
要先將 BottomNavigationWidget 的 constructor 進行調整, 我們讓他可以接受索引值, 指定要亮哪個顏色的 item, 否則因為我們換頁的時候是使用 Navigation pushNamed, 資料會被蓋掉
上面範例的左邊圖片是抓取 youtube 影片的縮圖, 要抓取我們先安裝以下套件
引入套件
接著我們宣告取得影片縮圖的函示
而清單的部份我們使用 ListView 進行繪製, 如下圖
建置爆料畫面
首先在 pages 新增一個 talk.dart , 設置為 StatefulWidget, 且宣告以下 controller
建置 label text 的 function
以下為部分代碼
新增照片功能, 首先要先安裝 image_picker 套件
匯入套件以及 dart:io