Flutter 「孔雀開屏」的動畫效果

老孟導讀:今天分享一個相似「孔雀開屏」的動畫效果,打開新的頁面時,新的頁面從屏幕右上角以圓形逐漸打開到全屏。git

先來看下具體的效果微信

ping

不知道這種效果你們叫什麼名字?若是有更合適的名字能夠在評論處告訴我,下面來講下如何實現此效果。ide

在使用Navigator進入一個新的頁面時,一般用法以下:函數

Navigator.of(context).push(MaterialPageRoute(
  builder: (context){
    return PageB();
  }
));

MaterialPageRoute就包含了切換頁面時的動畫效果,在iOS上效果是左右滑動切換,在Android上效果是上下滑動,若是想要自定義切換效果如何實現呢?答案是使用PageRouteBuilder,用法以下:動畫

Navigator.of(context).push(PageRouteBuilder(pageBuilder:
    (BuildContext context, Animation<double> animation,
        Animation<double> secondaryAnimation) {
  ...
}));

pageBuilder函數中使用animation返回新頁面的動畫效果便可。ui

新的頁面以圓形效果逐漸打開,注意並無縮放效果,因此新的頁面是被裁減的,新的頁面以右上角爲圓心,半徑逐漸變大進行裁切,就是咱們想要的效果。this

經過上面的分析,使用ClipPath對新的頁面進行裁切code

Navigator.of(context).push(PageRouteBuilder(pageBuilder:
    (BuildContext context, Animation<double> animation,
        Animation<double> secondaryAnimation) {
  return AnimatedBuilder(
    animation: animation,
    builder: (context, child) {
      return ClipPath(
        clipper: CirclePath(animation.value),
        child: child,
      );
    },
    child: PageB(),
  );
}));

重點是CirclePath,這就是裁切的路徑,blog

class CirclePath extends CustomClipper<Path> {
  CirclePath(this.value);

  final double value;

  @override
  Path getClip(Size size) {
    var path = Path();
    double radius =
        value * sqrt(size.height * size.height + size.width * size.width);
    path.addOval(Rect.fromLTRB(
        size.width - radius, -radius, size.width + radius, radius));
    return path;
  }

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

因爲Path沒有直接添加圓形的API函數,所以使用橢圓方法,只需將橢圓的矩形區域設置爲正方形,那麼裁切出來的就是圓形。繼承

半徑的最大值並非屏幕的寬或者高,而是屏幕的對角線長度。

因爲是從右上角開始,並且裁切的矩形區域必須是正方形,因此裁切的矩形區域是超出頁面區域的。

若是不少頁面都用到了這個效果,能夠進行封裝,相似於MaterialPageRoute,封裝以下:

class CirclePageRoute extends PageRoute {
  CirclePageRoute({
    @required this.builder,
    this.transitionDuration = const Duration(milliseconds: 500),
    this.opaque = true,
    this.barrierDismissible = false,
    this.barrierColor,
    this.barrierLabel,
    this.maintainState = true,
  });

  final WidgetBuilder builder;

  @override
  final Duration transitionDuration;

  @override
  final bool opaque;

  @override
  final bool barrierDismissible;

  @override
  final Color barrierColor;

  @override
  final String barrierLabel;

  @override
  final bool maintainState;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) {
    return AnimatedBuilder(
      animation: animation,
      builder: (context, child) {
        return ClipPath(
          clipper: CirclePath(animation.value),
          child: child,
        );
      },
      child: builder(context),
    );
  }
}

使用

Navigator.of(context).push(CirclePageRoute(builder: (context) {
  return PageB();
}));

若是你查看CupertinoPageRouteMaterialPageRoutePageRouteBuilder的源碼,你會發現這3個都是繼承自PageRoute,因此,不知不覺咱們又學會了自定義路由。

交流

老孟Flutter博客地址(近200個控件用法):http://laomengit.com

歡迎加入Flutter交流羣(微信:laomengit)、關注公衆號【老孟Flutter】:

相關文章
相關標籤/搜索