Flutter 仿生微信

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

2020-11-20  本文已影响0人  Maojunhao

1. 源码下载

喜欢的话,别忘了点个关注,还有给个 Github 右上角的小星星吧。

源码下载地址,代码会根据不断更新。

Flutter 仿生微信(目录)
上一篇:Flutter 仿生微信(3):发现页面搭建
下一篇:Flutter 仿生微信(5):我的页面上拉下拉动画
FM Weixin Mine.png FM Weixin Mine.gif

2. 思路

我的页面可以下拉出现扫一扫功能,所以页面应该分为三个层级。

第一层是手势层,捕捉用户滑动操作,并更新页面位置,展示上方扫一扫页面。
第二层是扫一扫,停在页面最上方,根据下拉动作逐渐展示。
第三层是我的页面,展示个人信息以及一些功能。

这里要捕捉到用户手势,所以最后边一层使用 RawGestureDetector,然后手势用 PanGestureRecognizer,捕捉用户的滑动,以及滑动结束操作。滑动时我们按照 0.5 的阻尼系数进行主页面移动,当滑动停止时,根据页面高度进行判断,显示扫一扫还是我的页面。

由于 flutter 频繁使用 setState 会变得很卡顿,所以我们减少 setState 的使用,这里使用 StreamBuilder 来处理动态更新。

这俩页面就是简单的 Widget 堆叠了,由于底层捕获了滑动手势,这里我的页面就使用 Column 来处理了。

onEnd 事件后,过渡动画后边在做。
滑动过程中,底部有约束问题导致的黄色条。

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', '相册', ''));
    _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', '设置', ''));
  }

  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,手势设置为 PanGestureRecognizer,捕捉 onUpdate, onEnd 两个事件。

滑动时会一直走这个回调函数,会返回 offset,我们这里只使用 details.delta.dy,根据 y 的偏移量 * 0.5 的阻尼系数,来下拉整个 FMMineContent。

滑动结束后,判断当前 FMMineContent.topY 的值。分为两种情况:

正常情况下拉,FMMineContent.topY > 200,认为下拉成功,收起 FMMineContent,展示 FMMineTopView。

展示 FMMineTopView 时,上拉,FMMineContent.topY < (screen.height - 200),认为上拉成功,收起 FMMineTopView,展示 FMMineContent。

4.2 页面刷新

由于使用 setState 来做动态刷新非常浪费性能,我们这里使用 Stream 来做刷新操作。

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

这样当 StreamController 改变 _topY 时,这里就会监听到,从而刷新控件。

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

在 onUpdate 中,我们使用 StreamController 对 _topY 进行改变。

StreamController创建 -> StreamBuilder创建 -> StreamController改变_topY -> StreamBuilder刷新控件。

Flutter 仿生微信(目录)
上一篇:Flutter 仿生微信(3):发现页面搭建
下一篇:Flutter 仿生微信(5):我的页面上拉下拉动画
上一篇 下一篇

猜你喜欢

热点阅读