Android开发Android开发经验谈Android开发

阿里 Flutter-go 项目拆解笔记(七)

2019-03-17  本文已影响4人  d74f37143a31

Flutter-go 项目地址是:https://github.com/alibaba/flutter-go

上文 我们分析了 第三个 Tab 页面,主要分析了 组件的收藏的实现,EventBus,sqflite 的使用

这篇文章主要拆解 第四个Tab页面(关于手册)。对应的welcome_page.dart文件的路径如下:'package:flutter_go/views /welcome_page/welcome_page.dart';

UI 效果.PNG

下图是整理后的collection_page.dart文件主要的内容:

功能拆解.PNG

页面切换实现

老实说,没用 Flutter 做过项目,直接来阅读源码还是有点吃力的,理解错了欢迎指出。

fourth_page.dart的布局中,我们可以看到children是由Page、PageReveal、PageIndicator、PageDragger几个Widget组成的。那么我们就来分析这几个的Widget的实现,了解他们的作用是什么。

Page 组件分析

作用是:承载每个页面。
Page组件中使用了Stack组件,用于在右上角显示go GitHub按钮。
Page组件的childrenContainer、PositionedContainer用于展示 每个页面 的内容Positioned主要显示右上角go GitHub按钮。

每个页面 的实现分析:

Transform可以在其子Widget绘制时对其应用一个矩阵变换(transformation)
这里的实现主要就是在children集合中添加三个Transform组件,一个用于 顶部图片 的动画,一个用于 中间标题文字 的动画,一个用于 描述文字 的动画。

/// 这里只贴出了顶部图片的变换代码,更多代码请查看源码
Transform(
    // 参数1:x 轴的移动方向,参数2:y 轴的移动方向,参数3:z 轴的移动方向
    transform: Matrix4.translationValues(
        0.0, 50.0 * (1.0 - percentVisible), 0.0),
    child: Padding(
      padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
      /// 顶部图片
      child: Image.asset(viewModel.heroAssetPath,
          width: 160.0, height: 160.0),
    ),
),

/// 标题的实现也是类似
/// 描述文本的实现同上

go GitHub 按钮的实现分析:

使用了RaisedButton.icon组件,该组件的作用是:可生成一个带有icon的按钮。而 半圆角的矩形边框 是使用RoundedRectangleBorder实现的

  /// 回到首页按钮,Github 按钮
  Widget creatButton(
      BuildContext context, String txt, IconData iconName, String type) {
    return RaisedButton.icon(
        onPressed: () async {
          if (type == 'start') {
            await SpUtil.getInstance()
              ..putBool(SharedPreferencesKeys.showWelcome, false);
          /// 跳转首页
            _goHomePage(context);
          } else if (type == 'goGithub') {
        /// 进入 Flutter-go  GitHub 首页
            Application.router.navigateTo(context,
                '${Routes.webViewPage}?title=${Uri.encodeComponent(txt)} Doc&&url=${Uri.encodeComponent("https://github.com/alibaba/flutter-go")}');
          }
        },
        elevation: 10.0,
        color: Colors.black26,
        // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(20.0))),
        //如果不手动设置icon和text颜色,则默认使用foregroundColor颜色
        icon: Icon(iconName, color: Colors.white, size: 14.0),
        label: Text(
          txt,
          maxLines: 1,
          style: TextStyle(
              color: Colors.white, fontSize: 14, fontWeight: FontWeight.w700),
        ));
  }

PageReveal 组件分析

PageReveal 作用是实现页面Page裁剪 效果。在实现过程中继承了CustomClipperCustomClipper重写的方法getClip根据需要露出的百分比revealPercentchild进行裁剪,返回了Rect,但是在CircleRevealClipper的外层嵌套了ClipOvalClipOval是使用椭圆来剪辑其子对象的Widget。实现源码如下:

 @override
  Widget build(BuildContext context) {
    return ClipOval(
      clipper: new CircleRevealClipper(revealPercent),
      // 这里的 child 是传入的 page
      child: child,
    );
  }
}

class CircleRevealClipper extends CustomClipper<Rect>{

  // 显示的百分比
  final double revealPercent;


  CircleRevealClipper(
    this.revealPercent
  );

  @override
  Rect getClip(Size size) {

    final epicenter = new Offset(size.width / 2, size.height * 0.9);

    double theta = atan(epicenter.dy / epicenter.dx);
    final distanceToCorner = epicenter.dy / sin(theta);

    final radius = distanceToCorner * revealPercent;
    final diameter = 2 * radius;

    return new Rect.fromLTWH(epicenter.dx - radius, epicenter.dy - radius, diameter, diameter);
  }

  @override
  bool shouldReclip(CustomClipper<Rect> oldClipper) {
    return true;
  }

}

PagerIndicator 组件分析

底部的指示器实现,在源码中可以看出指示器的实现也使用了Matrix4.translationValues动画,指示器的实现主要是由PageBubble Widget 集合组成。而PageBubble的实现如下:

 @override
  Widget build(BuildContext context) {
    return new Container(
      width: 55.0,
      height: 65.0,
      child: new Center(
        child: new Container(
          // 宽度在(20.0,45.0)线性插值两个数字之间变换
          width: lerpDouble(20.0,45.0,viewModel.activePercent),
         // 高度在(20.0,45.0)线性插值两个数字之间变换
          height: lerpDouble(20.0,45.0,viewModel.activePercent),
          decoration: new BoxDecoration(
            shape: BoxShape.circle,
          // isHollow 是否显示圆圈
          // i > viewModel.activeIndex 从右向左滑动 返回 true
          // i == viewModel.activeIndex && viewModel.slideDirection == SlideDirection.leftToRight) 从左向右滑动 返回 true
          //  bool isHollow = i > viewModel.activeIndex || (i == viewModel.activeIndex && viewModel.slideDirection == SlideDirection.leftToRight);
          // isHollow表示圆点对应的页码是否大于当前页码,如果大于的话显示空心,否则显示实心
            color: viewModel.isHollow
                ? const Color(0x88FFFFFF).withAlpha(0x88 * viewModel.activePercent.round())
                : const Color(0x88FFFFFF),
            border: new Border.all(
              color: viewModel.isHollow
                  ? const Color(0x88FFFFFF).withAlpha((0x88 * (1.0 - viewModel.activePercent)).round())
                  : Colors.transparent,
              width: 3.0,
            ),
          ),
          // 指示器图片
          child: new Opacity(
            opacity: viewModel.activePercent,
            child: Image.asset(
              viewModel.iconAssetPath,
              color: viewModel.color,
            ),
          ),
        ),
      ),
    );
  }

PageDragger 组件分析

PageDragger主要用来接收触摸事件,然后根据触摸事件来进行对应的操作。首先是在FourthPage的构造方法中创建了一个Stream流的事件监听。关于Stream介绍可以查看 官方文档 或者这篇博文 Flutter响应式编程 - Stream

FourthPage的构造方法伪代码如下:

...
slideUpdateStream = new StreamController<SlideUpdate>();
// 开始监听
slideUpdateStream.stream.listen((SlideUpdate event) {
    ...
}
...

在创建了slideUpdateStream之后将其传递个PageDragger构造方法,如下:

 new PageDragger(
          canDragLeftToRight: activeIndex > 0,
          canDragRightToLeft: activeIndex < pages.length - 1,
          slideUpdateStream: this.slideUpdateStream,
        )

在构造方法中控制了左右滑动的边界,而slideUpdateStream就是用于监听触摸事件的。那么在这里是如何去监听触摸事件的呢?

PageDraggerbuild实现中可以看出是通过监听了水平滑动来实现对应的操作

@override
  Widget build(BuildContext context) {
    // 水平触摸监听
    return GestureDetector(
      onHorizontalDragStart: onDragStart ,
      onHorizontalDragUpdate: onDragUpdate ,
      onHorizontalDragEnd: onDragEnd ,
    );
  }

我们可以看其中的一个方法onDragUpdate的实现:

// 正在拖拽
  onDragUpdate(DragUpdateDetails details) {
    if (dragStart != null) {
      final newPosition = details.globalPosition;
      final dx = dragStart.dx - newPosition.dx;
      // 滑动方向
      if (dx > 0 && widget.canDragRightToLeft) {
        slideDirection = SlideDirection.rightToLeft;
      } else if (dx < 0 && widget.canDragLeftToRight) {
        slideDirection = SlideDirection.leftToRight;
      } else {
        slideDirection = SlideDirection.none;
      }
      // 滑动的百分比
      if (slideDirection != SlideDirection.none){
      slidePercent = (dx / FULL_TRANSTITION_PX).abs().clamp(0.0, 1.0);
      } else {
        slidePercent = 0.0;
      }
      // 添加 stream 数据
      widget.slideUpdateStream.add(
          new SlideUpdate(
          UpdateType.dragging,
          slideDirection,
          slidePercent
      ));
    }
  }

在上面的代码可以看到slideUpdateStream通过add方法添加了一个SlideUpdate对象。

所以Stream的使用方式可以分为如下步骤:

  1. 创建slideUpdateStream = new StreamController<SlideUpdate>();
  2. 开启监听slideUpdateStream.stream.listen((SlideUpdate event) {}
  3. 添加被监听的对象slideUpdateStream.add(new SlideUpdate())

该效果实现总结

  1. 创建page装载每个页面
  2. 通过PageReveal去裁剪页面
  3. 根据PageDragger的滑动百分比来控制显示哪个页面

点击右上角 Github 跳转

属于跳转详情页面,在下一篇跳转详情中介绍。

参考文章

FlutterPageReveal:翻页动画

上一篇下一篇

猜你喜欢

热点阅读