Flutter 仿生微信(4):我的頁面搭建

1. 源碼下載

喜歡的話贯溅,別忘了點(diǎn)個(gè)關(guān)注霹娄,還有給個(gè) Github 右上角的小星星吧单默。

源碼下載地址绕辖,代碼會(huì)根據(jù)不斷更新胯府。

Flutter 仿生微信(目錄)
上一篇:Flutter 仿生微信(3):發(fā)現(xiàn)頁面搭建
下一篇:Flutter 仿生微信(5):我的頁面上拉下拉動(dòng)畫
FM Weixin Mine.png
FM Weixin Mine.gif

2. 思路

  • 頁面層級(jí)

我的頁面可以下拉出現(xiàn)掃一掃功能捌肴,所以頁面應(yīng)該分為三個(gè)層級(jí)偏螺。

第一層是手勢層父腕,捕捉用戶滑動(dòng)操作椅邓,并更新頁面位置柠逞,展示上方掃一掃頁面。
第二層是掃一掃景馁,停在頁面最上方板壮,根據(jù)下拉動(dòng)作逐漸展示。
第三層是我的頁面合住,展示個(gè)人信息以及一些功能绰精。

  • 手勢層

這里要捕捉到用戶手勢,所以最后邊一層使用 RawGestureDetector聊疲,然后手勢用 PanGestureRecognizer茬底,捕捉用戶的滑動(dòng),以及滑動(dòng)結(jié)束操作获洲≮灞恚滑動(dòng)時(shí)我們按照 0.5 的阻尼系數(shù)進(jìn)行主頁面移動(dòng),當(dāng)滑動(dòng)停止時(shí)贡珊,根據(jù)頁面高度進(jìn)行判斷最爬,顯示掃一掃還是我的頁面。

  • 動(dòng)態(tài)刷新

由于 flutter 頻繁使用 setState 會(huì)變得很卡頓门岔,所以我們減少 setState 的使用爱致,這里使用 StreamBuilder 來處理動(dòng)態(tài)更新。

  • 我的頁面寒随、掃一掃頁面

這倆頁面就是簡單的 Widget 堆疊了糠悯,由于底層捕獲了滑動(dòng)手勢帮坚,這里我的頁面就使用 Column 來處理了。

  • TODO

onEnd 事件后互艾,過渡動(dòng)畫后邊在做试和。
滑動(dòng)過程中,底部有約束問題導(dǎo)致的黃色條纫普。

3. 示例代碼

FMMine.dart

import 'dart:async';
import 'dart:ui';

import 'package:FMWeixinApp/mine/mine/body/FMMineBody.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

class FMMine extends StatefulWidget {
  @override
  FMMineState createState()=> FMMineState();
}

class FMMineState extends State <FMMine> {

  final StreamController<double> _streamController = StreamController();

  double _topY = 0;
  bool _hideTop = true;

  final double _screenHeight = window.physicalSize.height / 2.0;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return RawGestureDetector(
      gestures: <Type, GestureRecognizerFactory>{
        PanGestureRecognizer : GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
              ()=>PanGestureRecognizer(),
              (PanGestureRecognizer instace){
                instace
                ..onStart = (details) {

                }
                ..onUpdate = (details) {
                  print('update');
                  _streamController.sink.add(
                      _topY += details.delta.dy * (_hideTop ? 0.5 : 0.2 )
                  );
                }
                ..onEnd = (details) {
                  print('end');
                  _streamController.sink.add(
                      _topY = _didHideTopWhenEndPanning()
                  );
                }
                ..onCancel = (){
                  print('cancel');
                }
                ..onDown = (details){
                  print('down');
                };
              },
        ),
      },
      child: StreamBuilder<double>(
        stream: _streamController.stream,
        initialData: _topY,
        builder: (context, snapShot){
          print('topY $_topY');
          return FMMineBody(_topY);
        },
      ),
    );
  }

  double _didHideTopWhenEndPanning(){
    if (!_hideTop) {
      if (_topY < _screenHeight - 100 - 64) {
        _hideTopWhenEndPaning();
      } else {
        _showTopWhenEndPanning();
      }
    } else {
      if (_topY > 200) {
        _showTopWhenEndPanning();
      } else {
        _hideTopWhenEndPaning();
      }
    }

    return _topY;
  }

  void _hideTopWhenEndPaning(){
    _topY = 0;
    _hideTop = true;
  }

  void _showTopWhenEndPanning(){
    _topY = _screenHeight - 64;
    _hideTop = false;
  }
}

FMMineBody.dart

import 'package:FMWeixinApp/mine/mine/content/FMMineContent.dart';
import 'package:FMWeixinApp/mine/mine/top/FMMineTopView.dart';
import 'package:FMWeixinApp/tools/FMColor.dart';
import 'package:flutter/material.dart';

class FMMineBody extends StatelessWidget {
  double _offsetY = 0;
  FMMineBody(this._offsetY);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Stack(
      children: [
        Container(color: FMColors.wx_gray,),
        Positioned(
          top: 0,
          left: 0,
          right: 0,
          height: _offsetY,
          child: FMMineTopView(),
        ),
        Positioned(
          top: _offsetY,
          left: 0,
          right: 0,
          bottom: 0,
          child: FMMineContent(),
        ),
      ],
    );
  }
}

FMMineTopView.dart

import 'package:FMWeixinApp/tools/FMColor.dart';
import 'package:flutter/material.dart';

class FMMineTopView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Stack(
      children: [
        Container(
          color: FMColors.wx_gray,
        ),
      ],
    );
  }
}

FMMineContent.dart

import 'package:FMWeixinApp/find/item/FMFindItem.dart';
import 'package:FMWeixinApp/find/model/FMFindModel.dart';
import 'package:FMWeixinApp/mine/mine/item/FMMineHeader.dart';
import 'package:flutter/material.dart';

class FMMineContent extends StatelessWidget{

  List <FMFindModel> _models = [];
  List <Widget> _items = [];

  @override
  Widget build(BuildContext context) {
    // TODO: implement build

    _initModels();
    _intItems();

    return Column(
      children: _items,
    );
  }

  void _initModels(){
    _models.add(FMFindModel('', '', 'divid'));
    _models.add(FMFindModel('assets/images/mine/mine_pay.png', '支付', 'function'));
    _models.add(FMFindModel('', '', 'divid'));
    _models.add(FMFindModel('assets/images/mine/mine_collection.png', '收藏', ''));
    _models.add(FMFindModel('assets/images/mine/mine_album.png', '相冊(cè)', ''));
    _models.add(FMFindModel('assets/images/mine/mine_wallet.png', '卡包', ''));
    _models.add(FMFindModel('assets/images/mine/mine_face.png', '表情', ''));
    _models.add(FMFindModel('', '', 'divid'));
    _models.add(FMFindModel('assets/images/mine/mine_setting.png', '設(shè)置', ''));
  }

  void _intItems(){

    _items.add(
        Container(
          height: 200,
          child: FMMineHeader(),
        ),
    );

    _models.forEach((model) {
      if (model.type == 'divid') {
        _items.add(Padding(padding: EdgeInsets.only(top: 10),));
      } else {
        _items.add(
            Container(
              height: 60.0,
              child: FMFindItem(
                model: model,
                onTap: (model){
                  print('${model.title}');
                },
              ),
            ),
        );
      }
    });
  }
}
FM Weixin Mine.png
FM Weixin Mine.gif

4. 源碼分析

4.1 手勢層

  • 事件捕捉

使用 RawGestureDetector阅悍,手勢設(shè)置為 PanGestureRecognizer,捕捉 onUpdate, onEnd 兩個(gè)事件昨稼。

  • onUpdate

滑動(dòng)時(shí)會(huì)一直走這個(gè)回調(diào)函數(shù)节视,會(huì)返回 offset,我們這里只使用 details.delta.dy假栓,根據(jù) y 的偏移量 * 0.5 的阻尼系數(shù)寻行,來下拉整個(gè) FMMineContent。

  • onEnd

滑動(dòng)結(jié)束后但指,判斷當(dāng)前 FMMineContent.topY 的值寡痰。分為兩種情況:

正常情況下拉,F(xiàn)MMineContent.topY > 200棋凳,認(rèn)為下拉成功,收起 FMMineContent连躏,展示 FMMineTopView剩岳。

展示 FMMineTopView 時(shí),上拉入热,F(xiàn)MMineContent.topY < (screen.height - 200)拍棕,認(rèn)為上拉成功,收起 FMMineTopView勺良,展示 FMMineContent绰播。

4.2 頁面刷新

由于使用 setState 來做動(dòng)態(tài)刷新非常浪費(fèi)性能,我們這里使用 Stream 來做刷新操作尚困。

  • 創(chuàng)建 StreamController蠢箩,并設(shè)置需要改變的變量,這里我們定義一個(gè) double _topY事甜。

  • 創(chuàng)建 StreamBuilder

    StreamBuilder<double>(
        stream: _streamController.stream,
        initialData: _topY,
        builder: (context, snapShot){
          return FMMineBody(_topY);
     }

這樣當(dāng) StreamController 改變 _topY 時(shí)谬泌,這里就會(huì)監(jiān)聽到,從而刷新控件逻谦。

  • 改變 _topY
                ..onUpdate = (details) {
                  print('update');
                  _streamController.sink.add(
                      _topY += details.delta.dy * 0.5
                  );
                }

在 onUpdate 中掌实,我們使用 StreamController 對(duì) _topY 進(jìn)行改變。

  • 整體分析

StreamController創(chuàng)建 -> StreamBuilder創(chuàng)建 -> StreamController改變_topY -> StreamBuilder刷新控件邦马。

Flutter 仿生微信(目錄)
上一篇:Flutter 仿生微信(3):發(fā)現(xiàn)頁面搭建
下一篇:Flutter 仿生微信(5):我的頁面上拉下拉動(dòng)畫
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贱鼻,一起剝皮案震驚了整個(gè)濱河市宴卖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌邻悬,老刑警劉巖嘱腥,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拘悦,居然都是意外死亡齿兔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門础米,熙熙樓的掌柜王于貴愁眉苦臉地迎上來分苇,“玉大人,你說我怎么就攤上這事屁桑∫绞伲” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蘑斧,是天一觀的道長靖秩。 經(jīng)常有香客問我,道長竖瘾,這世上最難降的妖魔是什么沟突? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮捕传,結(jié)果婚禮上惠拭,老公的妹妹穿的比我還像新娘。我一直安慰自己庸论,他們只是感情好职辅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著聂示,像睡著了一般域携。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鱼喉,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天秀鞭,我揣著相機(jī)與錄音,去河邊找鬼蒲凶。 笑死气筋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的旋圆。 我是一名探鬼主播宠默,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼灵巧!你這毒婦竟也來了搀矫?” 一聲冷哼從身側(cè)響起抹沪,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瓤球,沒想到半個(gè)月后融欧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卦羡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年噪馏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绿饵。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欠肾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拟赊,到底是詐尸還是另有隱情刺桃,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布吸祟,位于F島的核電站瑟慈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏屋匕。R本人自食惡果不足惜葛碧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望炒瘟。 院中可真熱鬧吹埠,春花似錦、人聲如沸疮装。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽廓推。三九已至,卻和暖如春翩隧,著一層夾襖步出監(jiān)牢的瞬間樊展,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工堆生, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留专缠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓淑仆,卻偏偏與公主長得像涝婉,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蔗怠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1. 源碼下載 喜歡的話墩弯,別忘了點(diǎn)個(gè)關(guān)注吩跋,還有給個(gè) Github 右上角的小星星[https://github.c...
    Maojunhao閱讀 1,153評(píng)論 0 1
  • 目前學(xué)習(xí)開發(fā)flutter已經(jīng)有快1年的時(shí)間了,大大小小也做了七七八八個(gè)項(xiàng)目渔工。項(xiàng)目從大到小都有锌钮,之前公司開發(fā)的項(xiàng)目...
    iOS超級(jí)洋閱讀 1,541評(píng)論 0 6
  • 前言 Flutter的很多靈感來自于React,它的設(shè)計(jì)思想是數(shù)據(jù)與視圖分離引矩,由數(shù)據(jù)映射渲染視圖梁丘。所以在Flutt...
    zhx喜籽閱讀 1,235評(píng)論 0 2
  • 前言 Flutter的很多靈感來自于React,它的設(shè)計(jì)思想是數(shù)據(jù)與視圖分離旺韭,由數(shù)據(jù)映射渲染視圖氛谜。所以在Flutt...
    Vadaski閱讀 19,479評(píng)論 14 44
  • 久違的晴天,家長會(huì)茂翔。 家長大會(huì)開好到教室時(shí)混蔼,離放學(xué)已經(jīng)沒多少時(shí)間了。班主任說已經(jīng)安排了三個(gè)家長分享經(jīng)驗(yàn)珊燎。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,523評(píng)論 16 22