H Hello Word!
應該是所有人剛接觸任何一個新技術時,第一篇就是教大家寫出這段話吧!
呼~ 好佳在,Flutter不是純程式語言,它是 UI 啊!!!
所以,本篇除了開頭寫出,再來就不會出現囉~
讀者英文程度還可以的,直接看原文會更深入了解,接下來是給比較不想要看太多英文字串的人來閱讀。
環境建置就不再多說明,請先把 flutter 設定好,再執行
$ flutter doctor
檢查所有功能都正常就代表OK了
預期畫面最後會像這樣
main.dart
簡單說明一下這個步驟要完成的事:
接著按下 reload 按鈕後,畫面就會像這樣:
然後,文字被送出後,在畫面中也要呈現使用者輸入的文字訊息。
在 Flutter中,如果想要直接呈現動態數據,需要先將訊息數據封裝在 State 中。然後,將 State 物件繼承於 StatefulWidget (注意,不是先前的 StatelessWidget)。
所以接下來,要把 ChatScreen 改繼承 StatefulWidget。而要讓 TextField 可以處理文字,再定義一個 ChatScreenState 並繼承 State,並且將 AppBar 改寫至此類別中。
main.dart
改寫完成後,開始在 ChatScreenStats 中加入元件吧!
文字輸入框控制器
文字送出後的處理
放入元件,並定義
Container 就是一個容器的概念,margin 定義左右邊距為8,但在 iOS 是 px,Android 則是 dp
child 為加入的內容物 TextField,並定義行為:
接著畫面就改成這樣囉
大至上是完成了,但是還是有一些地方覺得怪怪的。
原來是配色啊~
加上APP當前的主題配色之後,系色就會符合主題了。
現在畫面長得符合主題囉
如畫面所示,上方要有一個可以滾動的列表並長到最高,接著是前一步實作的訊息輸入列,但在列表及輸入列中間,要有一層小陰影來呈現。每一個則訊息由 Row 包住。最左邊是圓形頭像、中間為直列排放的發件人名稱及消息內容的 Text,而訊息要長到最寬。
會用到 Container,就像前面說的都是為了可以加邊距之類的參數。
上方程式中的 _name 請自行替換成發起人名稱,或參考另一篇文章可以匯入 firebase 上的資料。
要有獨特風格的 CircleAvatar 元件,就把 _name 變數的第一個字符傳遞給子層的 Text 元件,便可以讓這個圖示變成名稱的第一個字。而兩層排列都有 CrossAxisAlignment.start,橫式排列就代表靠左,直式排列就代表靠頂端。
而發起人名稱的字體要大於訊息內容,所以在這套用了 Theme.of(context).textTheme.subhead),您可以前往 Material Design樣式 參考其定義。在此篇文章後,會依 Android 及 iOS 的不同系統而重新設置。
接著要在 ChatScreenState 類別中增加一個 List 變數 _messages,用來儲存每個聊天訊息。而每個項目都是一個 ChatMessage 實例,剛開始會初始化為空的 List。
再把先前的 _handleSubmitted() 方法完全取代如下
setState() 是用來修改 _messages,並通知 framework 這個元件有修改,需要重新整理 UI。
使用一般的 ListView 會有一個問題,就是它不會自動偵測內部元件的數量,也就是不會自動增減。所以在這改用 ListView.builder。
解說一下使用的基本元件:
然後試著打些文字再送出,就可以看到
當使用者發送新訊息時,會讓訊息有動畫效果,而不是簡單地在訊息列中顯示一則新訊息。
在 Flutter 中的動畫被封裝成 Animation,包含 type value 及 status (例如向前、向後、完成及消失)。您可以在元件中新增動畫或監聽動畫物件的變動。再根據動畫物件屬性的變動, framework 可以重新整理 UI 並建立新的元件。
建立一個 AnimationController 時,必須傳遞一個 vsync 參數,以防止已經不存在螢幕的元件還給予動畫效果。需要在 ChatScreenState 類別繼承中加上一個 TickerProviderStateMixin。
在 ChatMessage 類別修改如下
再來修改 _handleSubmitted(),實作一個 AnimationController 物件並定義到 ChatMessage 實例中。設定動畫運行時間為 700 毫秒(採用較長的秒數是為了測試效果,一般來說應該設定少一點,才不會影響操作效能),並指定動畫向前播放。
這時重新啟動您的APP,並試著輸入一些訊息。在此不是用熱重新加載,因為要清除任何沒有動畫控制器的元件。
如果想要進一步試驗的動畫效果,下面提供一些方式:
首先,定義一個布林變數 _isComposing,用來偵測輸入框是否有文字被輸入,有就為 true。
要在文字有所變更時收到通知,需要將 onChanged 回應傳遞給 TextField 函數。並用 setState() 來改變 _isComposing 的值。
然後在發送按鈕被按下後的 onPressed 事件中,判斷 _isComposing 是否為 true,並送出。為 false 時回應 null。
當文字內容被清除時,修改 _handleSubmitted 為 _isComposing 改成 false。
現在 _isComposing 可以控制發送按鈕的行為及外觀。
在這一步驟中,將在訊息文字框外加一個 Expanded 元件來充許像 Column 這樣的元件在子層可以限制其寬度或高度(在Column中則是限制其寬度)。
在這裡,可以使用 IntelliJ 的一個方便的功能(Android Studio 就是),下面圖片將示範如何快速加入:
說明:
再次執行熱執行,應該可以看到錯誤訊息不見了,而且過長的文字也換行成功了。
呈現如下:
首先,為 iOS 定義一個新的 ThemeData 並命名為 kIOSTheme,並設定主配色為淺灰色、可動元件為橙色、亮度為明亮。為 Android 定義另一個並命名為 kDefaultTheme,設定主配色為紫色、可動元件為橙色。
修改類別 FriendlychatApp,使用 MaterialApp 中的 theme 來變改主題。在這可以用 defaultTargetPlatform 屬性來判別所在系統為何。
我們也可以將主題套用到 AppBar 元件。在 AppBar 中新增 elevation 屬性,並定義 iOS 為 0.0(無陰影),Android 為 4.0。當然這是兩個系統一般的使用者界面設定,不照這樣的設定也是可以(略過此步驟)。
接著修改發送按鈕的樣式,下面的程式碼僅顯示修改按鈕的部份
到這一步為止,在 iOS 的視覺上,可能訊息列表與 AppBar 之間並沒有明顯示的區別。可以再加上一點點陰影的效果,那麼就在 ChatScreenState build() 的 body 再加上一層 Container 來包覆原本的 Column。(在此可以用 new widget 技巧來加,會比較容易哦)
熱重載後就會看到 iOS 與 Android 有不一樣的顏色、陰影和按鈕圖示。
在 offline_steps 資料夾中,有每一個步驟的原始碼來參照。
再來,就是了解如何串接 Firebase 來實作。
應該是所有人剛接觸任何一個新技術時,第一篇就是教大家寫出這段話吧!
呼~ 好佳在,Flutter不是純程式語言,它是 UI 啊!!!
所以,本篇除了開頭寫出,再來就不會出現囉~
一、正式前言
本篇完全參考 Codelabs 上的 Building Beautiful UIs with Flutter。讀者英文程度還可以的,直接看原文會更深入了解,接下來是給比較不想要看太多英文字串的人來閱讀。
環境建置就不再多說明,請先把 flutter 設定好,再執行
$ flutter doctor
檢查所有功能都正常就代表OK了
二、開始進入主題
首先,建立一個 Flutter 專案。接著可以把原本的程式碼清乾淨囉預期畫面最後會像這樣
iOS | Android |
1、標題先搞定
目標建立一個 MateriaApp 包一個 Scaffold 再定義 AppBarmain.dart
// 取代原先在 main.dart 中的程式碼
import 'package:flutter/material.dart';
void main() {
runApp(new FriendlychatApp());
}
class FriendlychatApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Friendlychat",
home: new ChatScreen(),
);
}
}
class ChatScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Friendlychat")),
);
}
}
簡單說明一下這個步驟要完成的事:
- 所有看得到的元件都定義成 StatelessWidget
- 每個StatelessWidget都複寫一個方法:build()
- 而元件 Scaffold 及 AppBar 都是定義在 Material Design 中。而 Text 則是一般的元件,任何地方都可以使用。
接著按下 reload 按鈕後,畫面就會像這樣:
iOS | Android |
2、加入送出訊息的功能
預期在使用者按下文字輸入框時要跳出鍵盤,輸入完文字後可以按下送出鍵。送出鍵被按下,需要判斷是否空字串,空字串就不做任何動作;如果有文字就送出並清空文字輸入框。然後,文字被送出後,在畫面中也要呈現使用者輸入的文字訊息。
加入互動式的輸入文字框
Flutter 提供了一個可個行定義字段行為屬性的動態元件 TextField。它可以同步讀取訊息,並可以在它的生命期週裡更改訊息內容。但要把它加入本專案時,還是需要做一些小修改。在 Flutter中,如果想要直接呈現動態數據,需要先將訊息數據封裝在 State 中。然後,將 State 物件繼承於 StatefulWidget (注意,不是先前的 StatelessWidget)。
所以接下來,要把 ChatScreen 改繼承 StatefulWidget。而要讓 TextField 可以處理文字,再定義一個 ChatScreenState 並繼承 State,並且將 AppBar 改寫至此類別中。
main.dart
// 修改 ChatScreen 類別並改繼承於 StatefulWidget.
class ChatScreen extends StatefulWidget { //修改此
@override
State createState() => new ChatScreenState(); //新內容
}
// 加入此類別
class ChatScreenState extends State { //新內容
@override
Widget build(BuildContext context) {
return new Scaffold( //移到此
appBar: new AppBar(
title: new Text("Friendlychat")
),
);
}
}
改寫完成後,開始在 ChatScreenStats 中加入元件吧!
文字輸入框控制器
final TextEditingController _textController = new TextEditingController();
文字送出後的處理
void _handleSubmitted(String text) {
_textController.clear();
}
放入元件,並定義
Widget _buildTextComposer() {
return new Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: new TextField(
controller: _textController,
onSubmitted: _handleSubmitted,
decoration: new InputDecoration.collapsed(
hintText: "Send a message"),
),
);
}
Container 就是一個容器的概念,margin 定義左右邊距為8,但在 iOS 是 px,Android 則是 dp
child 為加入的內容物 TextField,並定義行為:
- controller 為 _textController
- onSubmitted 送出時的處理行為 _handleSubmitted
- decoration 修飾,加上 hint 讓使用者了解可以做些什麼
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Friendlychat"),
),
body: _buildTextComposer(), //這行
);
}
接著畫面就改成這樣囉
iOS | Android |
加入送出按鈕
加入前,要先加入 Row 元件,才能讓排列變成橫向的方式。但為了讓畫面好看,而且希望輸入框大一點,送出按鈕靠在最右側,就讓輸入框加到 Flexible 中,而按鈕需要放到另一個 Container 中
Widget _buildTextComposer() {
return new Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: new Row(
children: <Widget>[
new Flexible(
child: new TextField(
controller: _textController,
onSubmitted: _handleSubmitted,
decoration: new InputDecoration.collapsed(
hintText: "Send a message"),
),
),
new Container( //new
margin: new EdgeInsets.symmetric(horizontal: 4.0), //new
child: new IconButton( //new
icon: new Icon(Icons.send), //new
onPressed: () => _handleSubmitted(_textController.text)), //new
), //new
],
),
);
}
大至上是完成了,但是還是有一些地方覺得怪怪的。
原來是配色啊~
加上APP當前的主題配色之後,系色就會符合主題了。
Widget _buildTextComposer() {
return new IconTheme( //new
data: new IconThemeData(color: Theme.of(context).accentColor), //new
child: new Container( //modified
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: new Row(
children: <Widget>[
new Flexible(
child: new TextField(
controller: _textController,
onSubmitted: _handleSubmitted,
decoration: new InputDecoration.collapsed(
hintText: "Send a message"),
),
),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 4.0),
child: new IconButton(
icon: new Icon(Icons.send),
onPressed: () => _handleSubmitted(_textController.text)),
),
],
),
), //new
);
}
現在畫面長得符合主題囉
iOS | Android |
3、增加訊息列表的UI
準備讓聊天訊息出來見人啦!預期畫面要長成這樣如畫面所示,上方要有一個可以滾動的列表並長到最高,接著是前一步實作的訊息輸入列,但在列表及輸入列中間,要有一層小陰影來呈現。每一個則訊息由 Row 包住。最左邊是圓形頭像、中間為直列排放的發件人名稱及消息內容的 Text,而訊息要長到最寬。
實作訊息列表
在這步驟,在呈現可以滾動的訊息列表前,先完成每則訊息的元件排列。 定義一個 StatelessWidget 的 ChatMessage,用 Container 包 Row。左邊是 Container 包 CircleAvatar,右邊是 Column 包 Widget列表,上 Text 下 Container 包 Text。會用到 Container,就像前面說的都是為了可以加邊距之類的參數。
// 增加這個類別到 main.dart
class ChatMessage extends StatelessWidget {
ChatMessage({this.text});
final String text;
@override
Widget build(BuildContext context) {
return new Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
margin: const EdgeInsets.only(right: 16.0),
child: new CircleAvatar(child: new Text(_name[0])),
),
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(_name, style: Theme.of(context).textTheme.subhead),
new Container(
margin: const EdgeInsets.only(top: 5.0),
child: new Text(text),
),
],
),
],
),
);
}
}
上方程式中的 _name 請自行替換成發起人名稱,或參考另一篇文章可以匯入 firebase 上的資料。
// 加入這行程式到 main.dart
const String _name = "Your Name";
要有獨特風格的 CircleAvatar 元件,就把 _name 變數的第一個字符傳遞給子層的 Text 元件,便可以讓這個圖示變成名稱的第一個字。而兩層排列都有 CrossAxisAlignment.start,橫式排列就代表靠左,直式排列就代表靠頂端。
而發起人名稱的字體要大於訊息內容,所以在這套用了 Theme.of(context).textTheme.subhead),您可以前往 Material Design樣式 參考其定義。在此篇文章後,會依 Android 及 iOS 的不同系統而重新設置。
實作聊天訊息列表
接下來就要實作取得聊天訊息放入 UI 中顯示。也希望此列表可以滾動,方便使用者查看著聊天記錄,並且依照時間排序,最新的訊息會在下方。接著要在 ChatScreenState 類別中增加一個 List 變數 _messages,用來儲存每個聊天訊息。而每個項目都是一個 ChatMessage 實例,剛開始會初始化為空的 List。
class ChatScreenState extends State<chatscreen> {
final List<ChatMessage> _messages = <ChatMessage>[]; // new
final TextEditingController _textController = new TextEditingController();
再把先前的 _handleSubmitted() 方法完全取代如下
void _handleSubmitted(String text) {
_textController.clear();
ChatMessage message = new ChatMessage(
text: text,
);
setState(() {
_messages.insert(0, message);
});
}
setState() 是用來修改 _messages,並通知 framework 這個元件有修改,需要重新整理 UI。
放入訊息列表
目前步驟已準備好聊天息列表了。開始著手放入 ListView,先把下方程式替換,接著會說明
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Friendlychat")),
body: new Column( //modified
children: <Widget>[ //new
new Flexible( //new
child: new ListView.builder( //new
padding: new EdgeInsets.all(8.0), //new
reverse: true, //new
itemBuilder: (_, int index) => _messages[index], //new
itemCount: _messages.length, //new
), //new
), //new
new Divider(height: 1.0), //new
new Container( //new
decoration: new BoxDecoration(
color: Theme.of(context).cardColor), //new
child: _buildTextComposer(), //modified
), //new
], //new
), //new
);
}
使用一般的 ListView 會有一個問題,就是它不會自動偵測內部元件的數量,也就是不會自動增減。所以在這改用 ListView.builder。
解說一下使用的基本元件:
- Column,故名思義就是跟 Row 不同,為排列垂直的元件
- Flexible 放在ListView外層,就是要讓ListView自動長至最高,同時讓 TextField 保持固定的大小
- Divider 用於上下兩者間的水平間隔
- Container 通常用來定義背景圖、填充、設定邊距及很多設定用。再宣告 BoxDecoration 物件,來定義背景色,就能讓訊息列表及輸入列有的顏色有所區別
- padding 為訊息內容的內縮空白
- reverse 會使得 ListView 排列翻轉成貼近底部
- itemCount 指定列表中的訊息數量
- itemBuilder 用於取得每個元件的 index 函數
iOS | Android |
然後試著打些文字再送出,就可以看到
iOS | Android |
3、讓 APP 動起來
您可以將動畫效果加到 APP 中,讓使用者體驗更加流暢和直覺。當使用者發送新訊息時,會讓訊息有動畫效果,而不是簡單地在訊息列中顯示一則新訊息。
在 Flutter 中的動畫被封裝成 Animation,包含 type value 及 status (例如向前、向後、完成及消失)。您可以在元件中新增動畫或監聽動畫物件的變動。再根據動畫物件屬性的變動, framework 可以重新整理 UI 並建立新的元件。
定義 AnimationController
使用 AnimationController 類別來指定動畫該如何運行。並可以指定它的持續時間及播放方向(正向或反向)。建立一個 AnimationController 時,必須傳遞一個 vsync 參數,以防止已經不存在螢幕的元件還給予動畫效果。需要在 ChatScreenState 類別繼承中加上一個 TickerProviderStateMixin。
class ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin { // 修改這行
final List<ChatMessage> _messages = <ChatMessage>[];
final TextEditingController _textController = new TextEditingController();
在 ChatMessage 類別修改如下
class ChatMessage extends StatelessWidget {
ChatMessage({this.text, this.animationController}); // 修改這行
final String text;
final AnimationController animationController; //新增這行
再來修改 _handleSubmitted(),實作一個 AnimationController 物件並定義到 ChatMessage 實例中。設定動畫運行時間為 700 毫秒(採用較長的秒數是為了測試效果,一般來說應該設定少一點,才不會影響操作效能),並指定動畫向前播放。
void _handleSubmitted(String text) {
_textController.clear();
ChatMessage message = new ChatMessage(
text: text,
animationController: new AnimationController( //new
duration: new Duration(milliseconds: 700), //new
vsync: this, //new
), //new
); //new
setState(() {
_messages.insert(0, message);
});
message.animationController.forward(); //new
}
增加 SizeTransition 元件
修改 ChatMessage 的 build() 方法,用 SizeTransition 包裝之前定義的 Container,而且給定一個子層的寬度或高度。而 CurvedAnimation 跟 SizeTransition 類別一起產生緩和的動畫效果。緩和效果會導致訊息在動畫開始時快速滑入,快停止前放慢速度。
class ChatMessage extends StatelessWidget {
ChatMessage({this.text, this.animationController});
final String text;
final AnimationController animationController;
@override
Widget build(BuildContext context) {
return new SizeTransition( //new
sizeFactor: new CurvedAnimation( //new
parent: animationController, curve: Curves.easeOut), //new
axisAlignment: 0.0, //new
child: new Container( //modified
margin: const EdgeInsets.symmetric(vertical: 10.0),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
margin: const EdgeInsets.only(right: 16.0),
child: new CircleAvatar(child: new Text(_name[0])),
),
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(_name, style: Theme.of(context).textTheme.subhead),
new Container(
margin: const EdgeInsets.only(top: 5.0),
child: new Text(text),
),
],
),
],
),
) //new
);
}
}
配置動畫
設定動畫控制器在不需要此資源時釋放,這是一個很好的做法。下方的程式碼演示在 ChatScreenState 類別中如何通過複寫 dispose() 方法來實作。
// 將下列的程式碼加到 ChatScreenState 類別中
@override
void dispose() {
for (ChatMessage message in _messages)
message.animationController.dispose();
super.dispose();
}
這時重新啟動您的APP,並試著輸入一些訊息。在此不是用熱重新加載,因為要清除任何沒有動畫控制器的元件。
如果想要進一步試驗的動畫效果,下面提供一些方式:
- 於 _handleSubmitted() 方法中修改 duration 的值來加快或減慢動畫效果。
- 通過使用 Curves 類別中定義的常數來指定不同的動畫曲線。可以參考 Curves 的曲線來決定。
- 試著把 SizeTransition 大小變化效果改成 FadeTransition 的淡出淡入效果,當然有一些參數還是要自行修改。
4、套用觸擊事件
在這一個步驟中,將為 APP 提供一些複雜的細節,例如只能在有效長度的文字下才能按下發送按鈕,又以 Android 或 iOS 的外觀模式套用按鈕。發送按鈕的內文感知
在目前程式碼中,文字輸入框就算沒有文字,發送按鈕也能被點擊並發出訊息。首先,定義一個布林變數 _isComposing,用來偵測輸入框是否有文字被輸入,有就為 true。
class ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
final List<ChatMessage> _messages = <ChatMessage>[];
final TextEditingController _textController = new TextEditingController();
bool _isComposing = false; // 增加這行
要在文字有所變更時收到通知,需要將 onChanged 回應傳遞給 TextField 函數。並用 setState() 來改變 _isComposing 的值。
然後在發送按鈕被按下後的 onPressed 事件中,判斷 _isComposing 是否為 true,並送出。為 false 時回應 null。
Widget _buildTextComposer() {
return new IconTheme(
data: new IconThemeData(color: Theme.of(context).accentColor),
child: new Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: new Row(
children: <Widget>[
new Flexible(
child: new TextField(
controller: _textController,
onChanged: (String text) { //new
setState(() { //new
_isComposing = text.length > 0; //new
}); //new
}, //new
onSubmitted: _handleSubmitted,
decoration:
new InputDecoration.collapsed(hintText: "Send a message"),
),
),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 4.0),
child: new IconButton(
icon: new Icon(Icons.send),
onPressed: _isComposing
? () => _handleSubmitted(_textController.text) //modified
: null, //modified
),
),
],
),
),
);
}
當文字內容被清除時,修改 _handleSubmitted 為 _isComposing 改成 false。
void _handleSubmitted(String text) {
_textController.clear();
setState(() { //new
_isComposing = false; //new
}); //new
ChatMessage message = new ChatMessage(
text: text,
animationController: new AnimationController(
duration: new Duration(milliseconds: 700),
vsync: this,
),
);
setState(() {
_messages.insert(0, message);
});
message.animationController.forward();
}
現在 _isComposing 可以控制發送按鈕的行為及外觀。
- 當使用者在輸入框輸入文字時,_isComposing 變 true,顏色設置為 Theme.of(context).accentColor。而當使用者按下發送按鈕後,系統則呼叫 _handleSubmitted()。
- 當使用者沒有輸入任何文字時,_isComposing 變 false,而此元件的 onPressed 屬性設為 null 來禁用發送按鈕。而按鈕的顏色更改為 Theme.of(context).disabledColor。
顯示更高的文字輸入框
當使用者的文字內容超出界面的寬度時,應該要有換行的行為來顯示更多的訊息內容。但現在會被截斷而且還會有錯誤訊息在畫面上。為了確保訊息的正確,簡單的方法就是填加 Expanded 元件。在這一步驟中,將在訊息文字框外加一個 Expanded 元件來充許像 Column 這樣的元件在子層可以限制其寬度或高度(在Column中則是限制其寬度)。
在這裡,可以使用 IntelliJ 的一個方便的功能(Android Studio 就是),下面圖片將示範如何快速加入:
說明:
- 1、將游標移至 new Column 上。
- 2、點擊左側的燈泡圖示,然後從彈出的菜單中選擇 『Wrap with new widget』,就填加了一個通用 new widget 的程式碼。快捷鍵可以更快實作這個動作,就是 option + return (macOS) 或 alt + Enter (Linux/Windows)。
- 3、將游標放在出現警告的 widget 文字上,再按下上步的組合鍵,可以直接選擇 Expanded 來完成。如果有人跟小編我一樣都不會出現,也可以圈選 widget 文字後自己輸入 Expanded 即可。
再次執行熱執行,應該可以看到錯誤訊息不見了,而且過長的文字也換行成功了。
為 Android 和 iOS 定製化
在這之前,送出按鈕都長得一樣,而且是 Android 通用的圖示。但在 iOS 通常可能是用『Send』的文字按鈕來表示。為了處理不同平台要有不同的樣式來呈現,這步驟就會針對 iOS 給予 CupertionButton,而 Android 則是 Material Design 的 IconButton。呈現如下:
iOS | Android |
首先,為 iOS 定義一個新的 ThemeData 並命名為 kIOSTheme,並設定主配色為淺灰色、可動元件為橙色、亮度為明亮。為 Android 定義另一個並命名為 kDefaultTheme,設定主配色為紫色、可動元件為橙色。
// 加到 main.dart
final ThemeData kIOSTheme = new ThemeData(
primarySwatch: Colors.orange,
primaryColor: Colors.grey[100],
primaryColorBrightness: Brightness.light,
);
final ThemeData kDefaultTheme = new ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.orangeAccent[400],
);
修改類別 FriendlychatApp,使用 MaterialApp 中的 theme 來變改主題。在這可以用 defaultTargetPlatform 屬性來判別所在系統為何。
// 這行加到 main.dart.
import 'package:flutter/foundation.dart'; //new
// 下面則是修改 FriendlychatApp 類別
class FriendlychatApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Friendlychat",
theme: defaultTargetPlatform == TargetPlatform.iOS //new
? kIOSTheme //new
: kDefaultTheme, //new
home: new ChatScreen(),
);
}
}
我們也可以將主題套用到 AppBar 元件。在 AppBar 中新增 elevation 屬性,並定義 iOS 為 0.0(無陰影),Android 為 4.0。當然這是兩個系統一般的使用者界面設定,不照這樣的設定也是可以(略過此步驟)。
// 修改 ChatScreenState 類別的 build() 方法
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Friendlychat"), //modified
elevation:
Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0, //new
),
接著修改發送按鈕的樣式,下面的程式碼僅顯示修改按鈕的部份
// 這行加到 main.dart
import 'package:flutter/cupertino.dart'; //new
// 修改 _buildTextComposer 方法中的按鈕部份
new Container(
margin: new EdgeInsets.symmetric(horizontal: 4.0),
child: Theme.of(context).platform == TargetPlatform.iOS ? //modified
new CupertinoButton( //new
child: new Text("Send"), //new
onPressed: _isComposing //new
? () => _handleSubmitted(_textController.text) //new
: null,) : //new
new IconButton( //modified
icon: new Icon(Icons.send),
onPressed: _isComposing ?
() => _handleSubmitted(_textController.text) : null,
)
),
到這一步為止,在 iOS 的視覺上,可能訊息列表與 AppBar 之間並沒有明顯示的區別。可以再加上一點點陰影的效果,那麼就在 ChatScreenState build() 的 body 再加上一層 Container 來包覆原本的 Column。(在此可以用 new widget 技巧來加,會比較容易哦)
// 修改 ChatScreenState build() 中的 body
// 原先的 body: new Column( 會改成
body: new Container( //新增的外層
child: new Column(
...
),
decoration: Theme.of(context).platform == TargetPlatform.iOS //new
? new BoxDecoration( //new
border: new Border( //new
top: new BorderSide(color: Colors.grey[200]), //new
), //new
) //new
: null //new
), // Container結尾
熱重載後就會看到 iOS 與 Android 有不一樣的顏色、陰影和按鈕圖示。
結束囉
做到這,就已經結束所有的說明及實作範例。如果遇到編碼出現問題,可能是某個步驟還是不太了解,建議下載原文原版程式碼來參考:
git clone https://github.com/flutter/friendlychat-steps.git
在 offline_steps 資料夾中,有每一個步驟的原始碼來參照。
再來,就是了解如何串接 Firebase 來實作。
留言
張貼留言