gentle-ohige / FlutterDotRouter

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Flutterで90年代のゲーム風画面遷移を実装していきます

画面遷移実装について本家が一流の説明をしてくれるので省きます

Navigate to a new screen and back

実装部分の浅い層を確認するだけでも、Flutterの画面遷移はAnimation機能を そのまま利用していることが理解できます。 アニメーションについて見識があれば、機能を拡張していくことで 容易にカスタマイズできると思います。

1.Router

MaterialPageRouterの構成を参考に Routeの継承を行います

class DotAnimationRoute<T> extends PageRoute<T> {
  Widget child;
  DotAnimationRoute({
    @required this.child,
    RouteSettings settings,
    bool fullscreenDialog = false,

  }) : assert(child != null),
        assert(fullscreenDialog != null),
        assert(opaque),
        super(settings: settings, fullscreenDialog: fullscreenDialog);

  @override
  Color get barrierColor => null;

  @override
  String get barrierLabel => null;

  @override
  bool get maintainState => true;

  @override
  Duration get transitionDuration => Duration(seconds: 2);

  @override
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
    // TODO: implement buildPage
    return child;
  }

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
    // TODO: implement buildTransitions
      double begin = 0;
      double end = 1;
      var curve = Curves.easeInOutSine;
      var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));

      return DotAnimation(
        rotation: animation.drive(tween),
        child: child,
      );
  }
}

2.TransitionAnimatedWidget

アニメーション遷移時にbuildTransitionsが繰り返し呼び出され、 animationの値が更新されます。 AnimatedWidgetを継承したクラスを作成します。

class DotAnimation extends AnimatedWidget {

  DotAnimation({
    Key key,
    @required Animation<double> animation,
    this.transformHitTests = true,
    this.child,
  }):super(key: key, listenable: animation);

  Animation<double> get rotation => listenable;
  final bool transformHitTests;
  final Widget child;


  @override
  Widget build(BuildContext context) {
    return ClipPath(
      clipper: DotClipper(animationValue: rotation.value,),
      child: child,
    );

  }
}

childには遷移先の画面が入っており、 ClipPathを利用してchildの描画箇所を制御します

3.SpliteScreen

画面をDotに分割していきます。

class Dot {
  double size;
  int row;
  int column;

  Dot({this.size, this.column, this.row});

  Rect get rect => Rect.fromLTWH(row * size, column * size, size, size);
}

Dotごとに描画判別を行い、Pathに追加します。

class DotClipper extends  CustomClipper<Path> {
  double animationValue;
  double dotSize;
  DotClipper({this.animationValue, this.dotSize = 24});

  @override
  Path getClip(Size size) {

    int widthDot = (size.width / dotSize).ceil();
    int heightDot = (size.height.ceil() / dotSize).ceil();
    int total = widthDot * heightDot;

    List<Dot> dotList = List.generate(
        total,
            (int index) => Dot(
            size: dotSize,
            row: index % widthDot, column: (index / widthDot).floor()
        )
    );

    Path path = Path();
    dotList.forEach((dot){
      if(DotHitTest.rotate(size: size, value: animationValue, dotRect: dot.rect))
        path.addRect(dot.rect);
    });
    return path;
  }



  @override
  bool shouldReclip(DotClipper oldClipper) {
    return  animationValue != oldClipper.animationValue;
  }

}

4.HitTest

画面をxy図形に見立て、一次関数の式から描画を判別して行きます。 重要な処理となります。

import 'package:vector_math/vector_math.dart' as math_V;
class DotHitTest{
  static bool rotate ({Size size, double value, Rect dotRect}) {

    Offset center = size.center(Offset.zero);
    double rad =  math_V.radians(- 90 + 180 * value);
    double y =  tan(rad) * (dotRect.center.dx - center.dx)  + center.dy ;

    if(center.dx > dotRect.center.dx) {
      if( y <  dotRect.center.dy) return true;
      else return false;
    } else {
      if( y >  dotRect.center.dy) return true;
      else return false;

    }
  }
}

5.最後に

線形関数を用いて様々な遷移アニメーションをカスタマイズしましょう。 https://github.com/gentle-ohige/FlutterDotRouter Mediumに英語版を投稿する予定です。(Gistは正義)

About


Languages

Language:Dart 100.0%