成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

flutter筆記8:實(shí)戰(zhàn)聊天頁(yè)面嵌入交互動(dòng)畫和IOS風(fēng)格適配

NervosNetwork / 2053人閱讀

摘要:當(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 State with 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)整在_handleSubmittedAnimationController對(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 State with 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: [
      new Text(_name, style: Theme.of(context).textTheme.subhead),
      new Container(
        margin: const EdgeInsets.only(top: 5.0),
        child: new Text(text),
      ),
    ],
  ),
),                                                          //new

...
第四步 IOS和安卓風(fēng)格適配

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

相關(guān)文章

  • 跨平臺(tái)技術(shù)演進(jìn)

    摘要:接下來,我將從原理優(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...

    魏憲會(huì) 評(píng)論0 收藏0
  • 跨平臺(tái)技術(shù)演進(jìn)

    摘要:接下來,我將從原理優(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...

    MasonEast 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<