摘要:當(dāng)發(fā)送按鈕觸發(fā)事件后調(diào)用函數(shù),在中執(zhí)行了方法,此時(shí)根據(jù)中的變量變更重新渲染對(duì)象,然后大家就可以看到消息記錄框中底部新增了一行消息。
熟悉了flutter的各種控件和相互嵌套的代碼結(jié)構(gòu)后,可以再加深一點(diǎn)難度:加入動(dòng)畫特效。
雖然flutter的內(nèi)置Metarial控件已經(jīng)封裝好了符合其設(shè)計(jì)語(yǔ)言的動(dòng)畫特效,使開發(fā)者節(jié)約了不少視覺處理上的精力,比如點(diǎn)擊或長(zhǎng)按listTile控件時(shí)自帶水波紋動(dòng)畫、頁(yè)面切換時(shí)切入向上或向下的動(dòng)畫、列表上拉或下拉到盡頭有回彈波紋等。flutter也提供了用戶可自定義的動(dòng)畫處理方案,使產(chǎn)品交互更加生動(dòng)親切、富有情趣。
Flutter中封裝了包含有值和狀態(tài)(如向前,向后,完成和退出)的Animation對(duì)象。把Animation對(duì)象附加到控件中或直接監(jiān)聽動(dòng)畫對(duì)象屬性, Flutter會(huì)根據(jù)對(duì)Animation對(duì)象屬性的變化,修改控件的呈現(xiàn)效果并重新構(gòu)建控件樹。
這次,敲一個(gè)APP的聊天頁(yè)面,試試加入Animation后的效果,再嘗試APP根據(jù)運(yùn)行的操作系統(tǒng)進(jìn)行風(fēng)格適配。
第一步 構(gòu)建一個(gè)聊天界面先創(chuàng)建一個(gè)新項(xiàng)目:
flutter create chatPage
進(jìn)入main.dart,貼入如下代碼:
import "package:flutter/material.dart"; //程序入口 void main() { runApp(new FriendlychatApp()); } const String _name = "CYC"; //聊天帳號(hào)昵稱 class FriendlychatApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( //創(chuàng)建一個(gè)MaterialApp控件對(duì)象,其下可塞入支持Material設(shè)計(jì)語(yǔ)言特性的控件 title: "Friendlychat", home: new ChatScreen(), //主頁(yè)面為用戶自定義ChatScreen控件 ); } } //單條聊天信息控件 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:[ new Container( margin: const EdgeInsets.only(right: 16.0), child: new CircleAvatar(child: new Text(_name[0])), //顯示頭像圓圈 ), new Column( //單條消息記錄,昵稱和消息內(nèi)容垂直排列 crossAxisAlignment: CrossAxisAlignment.start, children: [ new Text(_name, style: Theme.of(context).textTheme.subhead), //昵稱 new Container( margin: const EdgeInsets.only(top: 5.0), child: new Text(text), //消息文字 ), ], ), ], ), ); } } //聊天主頁(yè)面ChatScreen控件定義為一個(gè)有狀態(tài)控件 class ChatScreen extends StatefulWidget { @override State createState() => new ChatScreenState(); //ChatScreenState作為控制ChatScreen控件狀態(tài)的子類 } //ChatScreenState狀態(tài)中實(shí)現(xiàn)聊天內(nèi)容的動(dòng)態(tài)更新 class ChatScreenState extends State { final List _messages = []; //存放聊天記錄的數(shù)組,數(shù)組類型為無(wú)狀態(tài)控件ChatMessage final TextEditingController _textController = new TextEditingController(); //聊天窗口的文本輸入控件 //定義發(fā)送文本事件的處理函數(shù) void _handleSubmitted(String text) { _textController.clear(); //清空輸入框 ChatMessage message = new ChatMessage( //定義新的消息記錄控件對(duì)象 text: text, ); //狀態(tài)變更,向聊天記錄中插入新記錄 setState(() { _messages.insert(0, message); //插入新的消息記錄 }); } //定義文本輸入框控件 Widget _buildTextComposer() { return new Container( margin: const EdgeInsets.symmetric(horizontal: 8.0), child: new Row( //文本輸入和發(fā)送按鈕都在同一行,使用Row控件包裹實(shí)現(xiàn) children: [ new Flexible( child: new TextField( controller: _textController, //載入文本輸入控件 onSubmitted: _handleSubmitted, decoration: new InputDecoration.collapsed(hintText: "Send a message"), //輸入框中默認(rèn)提示文字 ), ), new Container( margin: new EdgeInsets.symmetric(horizontal: 4.0), child: new IconButton( //發(fā)送按鈕 icon: new Icon(Icons.send), //發(fā)送按鈕圖標(biāo) onPressed: () => _handleSubmitted(_textController.text)), //觸發(fā)發(fā)送消息事件執(zhí)行的函數(shù)_handleSubmitted ), ] ) ); } //定義整個(gè)聊天窗口的頁(yè)面元素布局 Widget build(BuildContext context) { return new Scaffold( //頁(yè)面腳手架 appBar: new AppBar(title: new Text("Friendlychat")), //頁(yè)面標(biāo)題 body: new Column( //Column使消息記錄和消息輸入框垂直排列 children: [ new Flexible( //子控件可柔性填充,如果下方彈出輸入框,使消息記錄列表可適當(dāng)縮小高度 child: new ListView.builder( //可滾動(dòng)顯示的消息列表 padding: new EdgeInsets.all(8.0), reverse: true, //反轉(zhuǎn)排序,列表信息從下至上排列 itemBuilder: (_, int index) => _messages[index], //插入聊天信息控件 itemCount: _messages.length, ) ), new Divider(height: 1.0), //聊天記錄和輸入框之間的分隔 new Container( decoration: new BoxDecoration( color: Theme.of(context).cardColor), child: _buildTextComposer(), //頁(yè)面下方的文本輸入控件 ), ] ), ); } }
運(yùn)行上面的代碼,可以看到這個(gè)聊天窗口已經(jīng)生成,并且可以實(shí)現(xiàn)文本輸入和發(fā)送了:
如上圖標(biāo)注的控件,最終通過放置在狀態(tài)對(duì)象ChatScreenState控件中的Scaffold腳手架完成安置,小伙伴可以輸入一些文本,點(diǎn)擊發(fā)送按鈕試試ListView控件發(fā)生的變化。
當(dāng)發(fā)送按鈕IconButton觸發(fā)onPressed事件后調(diào)用_handleSubmitted函數(shù),在_handleSubmitted中執(zhí)行了setState()方法,此時(shí)flutter根據(jù)setState()中的變量_messages變更重新渲染_messages對(duì)象,然后大家就可以看到消息記錄框ListView中底部新增了一行消息。
由于ListView中的每一行都是瞬間添加完成,沒有過度動(dòng)畫,使交互顯得非常生硬,因此向ListView中的每個(gè)Item的加入添加動(dòng)畫效果,提升一下交互體驗(yàn)。
第二步 消息記錄加入動(dòng)效改造ChatScreen控件
要讓主頁(yè)面ChatScreen支持動(dòng)效,要在它的定義中附加mixin類型的對(duì)象TickerProviderStateMixin:
class ChatScreenState extends Statewith TickerProviderStateMixin { // modified final List _messages = []; final TextEditingController _textController = new TextEditingController(); ... }
向ChatMessage中植入動(dòng)畫控制器控制動(dòng)畫效果
class ChatMessage extends StatelessWidget { ChatMessage({this.text, this.animationController}); //new 加入動(dòng)畫控制器對(duì)象 final String text; final AnimationController animationController; @override Widget build(BuildContext context) { return new SizeTransition( //new 用SizeTransition動(dòng)效控件包裹整個(gè)控件,定義從小變大的動(dòng)畫效果 sizeFactor: new CurvedAnimation( //new CurvedAnimation定義動(dòng)畫播放的時(shí)間曲線 parent: animationController, curve: Curves.easeOut), //new 指定曲線類型 axisAlignment: 0.0, //new 對(duì)齊 child: new Container( //modified Container控件被包裹到SizeTransition中 margin: const EdgeInsets.symmetric(vertical: 10.0), child: new Row( crossAxisAlignment: CrossAxisAlignment.start, children:[ new Container( margin: const EdgeInsets.only(right: 16.0), child: new CircleAvatar(child: new Text(_name[0])), ), new Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ new Text(_name, style: Theme.of(context).textTheme.subhead), new Container( margin: const EdgeInsets.only(top: 5.0), child: new Text(text), ), ], ), ], ), ) //new ); } }
修改_handleSubmitted()處理函數(shù)
由于ChatMessage對(duì)象的構(gòu)造函數(shù)中添加了動(dòng)畫控制器對(duì)象animationController,因此創(chuàng)建新ChatMessage對(duì)象時(shí)也需要加入animationController的定義:
void _handleSubmitted(String text) { _textController.clear(); ChatMessage message = new ChatMessage( text: text, animationController: new AnimationController( //new duration: new Duration(milliseconds: 700), //new 動(dòng)畫持續(xù)時(shí)間 vsync: this, //new 默認(rèn)屬性和參數(shù) ), //new ); //new setState(() { _messages.insert(0, message); }); message.animationController.forward(); //new 啟動(dòng)動(dòng)畫 }
釋放控件
由于附加了動(dòng)效的控件比較耗費(fèi)內(nèi)存,當(dāng)不需要用到此頁(yè)面時(shí)最好釋放掉這些控件,F(xiàn)lutter會(huì)在復(fù)雜頁(yè)面中自動(dòng)調(diào)用dispose()釋放冗余的對(duì)象,玩家可以通過重寫dispose()指定頁(yè)面中需要釋放的內(nèi)容,當(dāng)然由于本案例只有這一個(gè)頁(yè)面,因此Flutter不會(huì)自動(dòng)執(zhí)行到dispose()。
@override void dispose() { //new for (ChatMessage message in _messages) //new 遍歷_messages數(shù)組 message.animationController.dispose(); //new 釋放動(dòng)效 super.dispose(); //new }
按上面的代碼改造完后,用R而不是r重啟一下APP,可以把之前沒有加入動(dòng)效的ChatMessage對(duì)象清除掉,使整體顯示效果更和諧。這時(shí)候試試點(diǎn)擊發(fā)送按鈕后的效果吧~
可以通過調(diào)整在_handleSubmitted中AnimationController對(duì)象的Duration函數(shù)參數(shù)值(單位:毫秒),改變動(dòng)效持續(xù)時(shí)間。
可通過改變CurvedAnimation對(duì)象的curve參數(shù)值,改變動(dòng)效時(shí)間曲線(和CSS的貝塞爾曲線類似),參數(shù)值可參考Curves
可以嘗試使用FadeTransition替代SizeTransition,試試動(dòng)畫效果如何
實(shí)現(xiàn)了消息列表的滑動(dòng),但是這個(gè)聊天窗口還有很多問題,比如輸入框的文本只能橫向增加不會(huì)自動(dòng)換行,可以空字符發(fā)送消息等,接下來就修復(fù)這些交互上的BUG,順便再?gòu)?fù)習(xí)下setState()的用法。
第三步 優(yōu)化交互杜絕發(fā)送空字符
當(dāng)TextField控件中的文本正在被編輯時(shí),會(huì)觸發(fā)onChanged事件,我們通過這個(gè)事件檢查文本框中是否有字符串,如果沒有則點(diǎn)擊發(fā)送按鈕失效,如果有則可以發(fā)送消息。
class ChatScreenState extends Statewith TickerProviderStateMixin { final List _messages = []; final TextEditingController _textController = new TextEditingController(); bool _isComposing = false; //new 到ChatScreenState對(duì)象中定義一個(gè)標(biāo)志位 ... }
向文本輸入控件_buildTextComposer中加入這個(gè)標(biāo)志位的控制:
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:[ new Flexible( child: new TextField( controller: _textController, onChanged: (String text) { //new 通過onChanged事件更新_isComposing 標(biāo)志位的值 setState(() { //new 調(diào)用setState函數(shù)重新渲染受到_isComposing變量影響的IconButton控件 _isComposing = text.length > 0; //new 如果文本輸入框中的字符串長(zhǎng)度大于0則允許發(fā)送消息 }); //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 當(dāng)沒有為onPressed綁定處理函數(shù)時(shí),IconButton默認(rèn)為禁用狀態(tài) ), ), ], ), ), ); }
當(dāng)點(diǎn)擊發(fā)送按鈕后,重置標(biāo)志位為false:
void _handleSubmitted(String text) { _textController.clear(); setState(() { //new 你們懂的 _isComposing = false; //new 重置_isComposing 值 }); //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(); }
這時(shí)候熱更新一下,再發(fā)送一條消息試試:
自動(dòng)換行
當(dāng)發(fā)送的文本消息超出一行時(shí),會(huì)看到以下效果:
遇到這種情況,使用Expanded控件包裹一下ChatMessage的消息內(nèi)容區(qū)域即可:
... new Expanded( //new Expanded控件 child: new Column( //modified Column被Expanded包裹起來,使其內(nèi)部文本可自動(dòng)換行 crossAxisAlignment: CrossAxisAlignment.start, children:第四步 IOS和安卓風(fēng)格適配[ new Text(_name, style: Theme.of(context).textTheme.subhead), new Container( margin: const EdgeInsets.only(top: 5.0), child: new Text(text), ), ], ), ), //new ...
flutter雖然可以一套代碼生成安卓和IOS的APP,但是這兩者有著各自的設(shè)計(jì)語(yǔ)言:Material和Cupertino。因此為了讓APP能夠更好的融合進(jìn)對(duì)應(yīng)的系統(tǒng)設(shè)計(jì)語(yǔ)言,我們要對(duì)頁(yè)面中的控件進(jìn)行一些處理。
引入IOS控件庫(kù):
前面已經(jīng)引入Material.dart控件庫(kù),但還缺少了IOS的Cupertino控件庫(kù),因此在main.dart的頭部中引入:
import "package:flutter/cupertino.dart";
定義Material和Cupertino的主題風(fēng)格
Material為默認(rèn)主題,當(dāng)檢測(cè)到APP運(yùn)行在IOS時(shí)使用Cupertino主題:
final ThemeData kIOSTheme = new ThemeData( //Cupertino主題風(fēng)格 primarySwatch: Colors.orange, primaryColor: Colors.grey[100], primaryColorBrightness: Brightness.light, ); final ThemeData kDefaultTheme = new ThemeData( //默認(rèn)的Material主題風(fēng)格 primarySwatch: Colors.purple, accentColor: Colors.orangeAccent[400], );
根據(jù)運(yùn)行的操作系統(tǒng)判斷對(duì)應(yīng)的主題:
首先要引入一個(gè)用于識(shí)別操作系統(tǒng)的工具庫(kù),其內(nèi)的defaultTargetPlatform值可幫助我們識(shí)別操作系統(tǒng):
import "package:flutter/foundation.dart";
到程序的入口控件FriendlychatApp中應(yīng)用對(duì)應(yīng)的操作系統(tǒng)主題:
class FriendlychatApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: "Friendlychat", theme: defaultTargetPlatform == TargetPlatform.iOS //newdefaultTargetPlatform用于識(shí)別操作系統(tǒng) ? kIOSTheme //new : kDefaultTheme, //new home: new ChatScreen(), ); } }
頁(yè)面標(biāo)題的風(fēng)格適配
頁(yè)面頂部顯示Friendlychat的標(biāo)題欄的下方,在IOS的Cupertino設(shè)計(jì)語(yǔ)言中沒有陰影,與下面的應(yīng)用主體通過一條灰色的線分隔開,而Material則通過標(biāo)題欄下方的陰影達(dá)到這一效果,因此將兩種特性應(yīng)用到代碼中:
// Modify the build() method of the ChatScreenState class. Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Friendlychat"), elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0), //new 適配IOS的扁平化無(wú)陰影效果 body: new Container( //modified 使用Container控件,方便加入主題風(fēng)格裝飾 child: new Column( //modified children:[ new Flexible( child: new ListView.builder( padding: new EdgeInsets.all(8.0), reverse: true, itemBuilder: (_, int index) => _messages[index], itemCount: _messages.length, ), ), new Divider(height: 1.0), new Container( decoration: new BoxDecoration(color: Theme.of(context).cardColor), child: _buildTextComposer(), ), ], ), decoration: Theme.of(context).platform == TargetPlatform.iOS //new 加入主題風(fēng)格 ? new BoxDecoration( //new border: new Border( //new 為適應(yīng)IOS加入邊框特性 top: new BorderSide(color: Colors.grey[200]), //new 頂部加入灰色邊框 ), //new ) //new : null), //modified ); }
發(fā)送按鈕的風(fēng)格適配
發(fā)送按鈕在APP遇到IOS時(shí),使用Cupertino風(fēng)格的按鈕:
// Modify the _buildTextComposer method. new Container( margin: new EdgeInsets.symmetric(horizontal: 4.0), child: Theme.of(context).platform == TargetPlatform.iOS ? //modified new CupertinoButton( //new 使用Cupertino控件庫(kù)的CupertinoButton控件作為IOS端的發(fā)送按鈕 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, ) ),
總結(jié)一下,為控件加入動(dòng)畫效果,就是把控件用動(dòng)畫控件包裹起來實(shí)現(xiàn)目的。動(dòng)畫控件有很多種,今天只選用了一個(gè)大小變化的控件SizeTransition作為示例,由于每種動(dòng)畫控件內(nèi)部的屬性不同,都需要多帶帶配置,大家可參考官網(wǎng)了解這些動(dòng)畫控件的詳情。
除此之外為了適應(yīng)不同操作系統(tǒng)的設(shè)計(jì)語(yǔ)言,用到了IOS的控件庫(kù)和操作系統(tǒng)識(shí)別的控件庫(kù),這是跨平臺(tái)開發(fā)中常用的功能。
好啦,flutter的入門筆記到本篇就結(jié)束了,今后將更新flutter的進(jìn)階篇和實(shí)戰(zhàn),由于近期工作任務(wù)較重,加上日常還有跟前端大神狐神學(xué)習(xí)golang的任務(wù),以后的更新會(huì)比較慢,因此也歡迎大家到我的Flutter圈子中投稿,分享自己的成果,把這個(gè)專題熱度搞起來,趕上谷歌這次跨平臺(tái)的小火車,也可以加入flutter 中文社區(qū)(官方QQ群:338252156)共同成長(zhǎng),謝謝大家~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93428.html
摘要:接下來,我將從原理優(yōu)缺點(diǎn)等方面為大家分享跨平臺(tái)技術(shù)演進(jìn)。小程序年是微信小程序飛速發(fā)展的一年,年,各大廠商快速跟進(jìn),已經(jīng)有了很大的影響力。下面,我們以微信小程序?yàn)槔?,分析小程序的技術(shù)架構(gòu)。 前言 大家好,我是simbawu ,@BooheeFE Team Leader,關(guān)于這篇文章,有問題歡迎來這里討論。 隨著移動(dòng)互聯(lián)網(wǎng)的普及和快速發(fā)展,手機(jī)成了互聯(lián)網(wǎng)行業(yè)最大的流量分發(fā)入口。以及隨著5G...
摘要:接下來,我將從原理優(yōu)缺點(diǎn)等方面為大家分享跨平臺(tái)技術(shù)演進(jìn)。小程序年是微信小程序飛速發(fā)展的一年,年,各大廠商快速跟進(jìn),已經(jīng)有了很大的影響力。下面,我們以微信小程序?yàn)槔?,分析小程序的技術(shù)架構(gòu)。 前言 大家好,我是simbawu ,@BooheeFE Team Leader,關(guān)于這篇文章,有問題歡迎來這里討論。 隨著移動(dòng)互聯(lián)網(wǎng)的普及和快速發(fā)展,手機(jī)成了互聯(lián)網(wǎng)行業(yè)最大的流量分發(fā)入口。以及隨著5G...
閱讀 476·2021-10-09 09:57
閱讀 483·2019-08-29 18:39
閱讀 820·2019-08-29 12:27
閱讀 3036·2019-08-26 11:38
閱讀 2674·2019-08-26 11:37
閱讀 1300·2019-08-26 10:59
閱讀 1386·2019-08-26 10:58
閱讀 996·2019-08-26 10:48