Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save CodeBunny786/dabff5108a89f9dfb516b5f5689fbdd0 to your computer and use it in GitHub Desktop.

Select an option

Save CodeBunny786/dabff5108a89f9dfb516b5f5689fbdd0 to your computer and use it in GitHub Desktop.

Revisions

  1. @hnvn hnvn revised this gist Jun 28, 2018. 1 changed file with 64 additions and 25 deletions.
    89 changes: 64 additions & 25 deletions flutter_flip_animation_4.dart
    Original file line number Diff line number Diff line change
    @@ -50,7 +50,7 @@ class MyHomePage extends StatelessWidget {
    ),
    itemsCount: digits.length,
    period: Duration(milliseconds: 1000),
    loop: 1,
    loop: -1,
    ),
    ),
    );
    @@ -157,7 +157,8 @@ class _FlipPanelState<T> extends State<FlipPanel>
    bool _isReversePhase;
    bool _isStreamMode;
    bool _running;
    final _perspective = 0.006;
    final _perspective = 0.003;
    final _zeroAngle = 0.0001; // There's something wrong in the perspective transform, I use a very small value instead of zero to temporarily get it around.
    int _loop;
    T _currentValue, _nextValue;
    Timer _timer;
    @@ -185,17 +186,15 @@ class _FlipPanelState<T> extends State<FlipPanel>
    }
    if (status == AnimationStatus.dismissed) {
    _currentValue = _nextValue;
    setState(() {
    _running = false;
    });
    _running = false;
    }
    })
    ..addListener(() {
    setState(() {
    _running = true;
    });
    });
    _animation = Tween(begin: 0.0, end: math.pi / 2).animate(_controller);
    _animation = Tween(begin: _zeroAngle, end: math.pi / 2).animate(_controller);

    if (widget.period != null) {
    _timer = Timer.periodic(widget.period, (_) {
    @@ -309,24 +308,36 @@ class _FlipPanelState<T> extends State<FlipPanel>
    Widget _buildUpperFlipPanel() => widget.direction == FlipDirection.up
    ? Stack(
    children: [
    _upperChild1,
    Transform(
    alignment: Alignment.bottomCenter,
    transform: (Matrix4.identity()..setEntry(3, 2, _perspective)) *
    Matrix4.rotationX(
    _isReversePhase ? _animation.value : math.pi / 2),
    transform: Matrix4.identity()
    ..setEntry(3, 2, _perspective)
    ..rotateX(_zeroAngle),
    child: _upperChild1
    ),
    Transform(
    alignment: Alignment.bottomCenter,
    transform: Matrix4.identity()
    ..setEntry(3, 2, _perspective)
    ..rotateX(_isReversePhase ? _animation.value : math.pi / 2),
    child: _upperChild2,
    ),
    ],
    )
    : Stack(
    children: [
    _upperChild2,
    Transform(
    alignment: Alignment.bottomCenter,
    transform: (Matrix4.identity()..setEntry(3, 2, _perspective)) *
    Matrix4.rotationX(
    _isReversePhase ? math.pi / 2 : _animation.value),
    transform: Matrix4.identity()
    ..setEntry(3, 2, _perspective)
    ..rotateX(_zeroAngle),
    child: _upperChild2
    ),
    Transform(
    alignment: Alignment.bottomCenter,
    transform: Matrix4.identity()
    ..setEntry(3, 2, _perspective)
    ..rotateX(_isReversePhase ? math.pi / 2 : _animation.value),
    child: _upperChild1,
    ),
    ],
    @@ -335,24 +346,36 @@ class _FlipPanelState<T> extends State<FlipPanel>
    Widget _buildLowerFlipPanel() => widget.direction == FlipDirection.up
    ? Stack(
    children: [
    _lowerChild2,
    Transform(
    alignment: Alignment.topCenter,
    transform: (Matrix4.identity()..setEntry(3, 2, _perspective)) *
    Matrix4.rotationX(
    _isReversePhase ? math.pi / 2 : -_animation.value),
    transform: Matrix4.identity()
    ..setEntry(3, 2, _perspective)
    ..rotateX(_zeroAngle),
    child: _lowerChild2
    ),
    Transform(
    alignment: Alignment.topCenter,
    transform: Matrix4.identity()
    ..setEntry(3, 2, _perspective)
    ..rotateX(_isReversePhase ? math.pi / 2 : -_animation.value),
    child: _lowerChild1,
    )
    ],
    )
    : Stack(
    children: [
    _lowerChild1,
    Transform(
    alignment: Alignment.topCenter,
    transform: (Matrix4.identity()..setEntry(3, 2, _perspective)) *
    Matrix4.rotationX(
    _isReversePhase ? -_animation.value : math.pi / 2),
    transform: Matrix4.identity()
    ..setEntry(3, 2, _perspective)
    ..rotateX(_zeroAngle),
    child: _lowerChild1
    ),
    Transform(
    alignment: Alignment.topCenter,
    transform: Matrix4.identity()
    ..setEntry(3, 2, _perspective)
    ..rotateX(_isReversePhase ? -_animation.value : math.pi / 2),
    child: _lowerChild2,
    )
    ],
    @@ -362,6 +385,8 @@ class _FlipPanelState<T> extends State<FlipPanel>
    return _running
    ? Column(
    mainAxisSize: MainAxisSize.min,
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.center,
    children: [
    _buildUpperFlipPanel(),
    Padding(
    @@ -374,13 +399,27 @@ class _FlipPanelState<T> extends State<FlipPanel>
    ? Container()
    : Column(
    mainAxisSize: MainAxisSize.min,
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.center,
    children: [
    _upperChild1,
    Transform(
    alignment: Alignment.bottomCenter,
    transform: Matrix4.identity()
    ..setEntry(3, 2, _perspective)
    ..rotateX(_zeroAngle),
    child: _upperChild1
    ),
    Padding(
    padding: EdgeInsets.only(top: widget.spacing),
    ),
    _lowerChild1
    Transform(
    alignment: Alignment.topCenter,
    transform: Matrix4.identity()
    ..setEntry(3, 2, _perspective)
    ..rotateX(_zeroAngle),
    child: _lowerChild1
    )
    ],
    );
    }
    }
    }
  2. @hnvn hnvn revised this gist Jun 26, 2018. 1 changed file with 1 addition and 12 deletions.
    13 changes: 1 addition & 12 deletions flutter_flip_animation_4.dart
    Original file line number Diff line number Diff line change
    @@ -19,18 +19,7 @@ class MyApp extends StatelessWidget {
    }

    class MyHomePage extends StatelessWidget {
    final digits = [
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    ];
    final digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9,];
    final String title;

    MyHomePage({this.title});
  3. @hnvn hnvn created this gist Jun 26, 2018.
    397 changes: 397 additions & 0 deletions flutter_flip_animation_4.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,397 @@
    import 'package:flutter/material.dart';
    import 'dart:math' as math;
    import 'dart:async';

    void main() => runApp(new MyApp());

    class MyApp extends StatelessWidget {
    // This widget is the root of your application.
    @override
    Widget build(BuildContext context) {
    return new MaterialApp(
    title: 'Flutter Demo',
    theme: new ThemeData(
    primarySwatch: Colors.blue,
    ),
    home: new MyHomePage(title: 'Flip Animation'),
    );
    }
    }

    class MyHomePage extends StatelessWidget {
    final digits = [
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    ];
    final String title;

    MyHomePage({this.title});

    @override
    Widget build(BuildContext context) {
    return new Scaffold(
    appBar: new AppBar(
    title: new Text(title),
    ),
    body: new Center(
    child: FlipPanel.builder(
    itemBuilder: (context, index) => Container(
    alignment: Alignment.center,
    width: 96.0,
    height: 128.0,
    decoration: BoxDecoration(
    color: Colors.black,
    borderRadius: BorderRadius.all(Radius.circular(4.0)),
    ),
    child: Text(
    '${digits[index]}',
    style: TextStyle(
    fontWeight: FontWeight.bold,
    fontSize: 80.0,
    color: Colors.yellow),
    ),
    ),
    itemsCount: digits.length,
    period: Duration(milliseconds: 1000),
    loop: 1,
    ),
    ),
    );
    }
    }

    /// Signature for a function that creates a widget for a given index, e.g., in a
    /// list.
    typedef Widget IndexedItemBuilder(BuildContext, int);

    /// Signature for a function that creates a widget for a value emitted from a [Stream]
    typedef Widget StreamItemBuilder<T>(BuildContext, T);

    /// A widget for flip panel with built-in animation
    /// Content of the panel is built from [IndexedItemBuilder] or [StreamItemBuilder]
    ///
    /// Note: the content size should be equal
    enum FlipDirection { up, down }

    class FlipPanel<T> extends StatefulWidget {
    final IndexedItemBuilder indexedItemBuilder;
    final StreamItemBuilder<T> streamItemBuilder;
    final Stream<T> itemStream;
    final int itemsCount;
    final Duration period;
    final Duration duration;
    final int loop;
    final int startIndex;
    final T initValue;
    final double spacing;
    final FlipDirection direction;

    FlipPanel({
    Key key,
    this.indexedItemBuilder,
    this.streamItemBuilder,
    this.itemStream,
    this.itemsCount,
    this.period,
    this.duration,
    this.loop,
    this.startIndex,
    this.initValue,
    this.spacing,
    this.direction,
    }) : super(key: key);

    /// Create a flip panel from iterable source
    /// [itemBuilder] is called periodically in each time of [period]
    /// The animation is looped in [loop] times before finished.
    /// Setting [loop] to -1 makes flip animation run forever.
    /// The [period] should be two times greater than [duration] of flip animation,
    /// if not the animation becomes jerky/stuttery.
    FlipPanel.builder({
    Key key,
    @required IndexedItemBuilder itemBuilder,
    @required this.itemsCount,
    @required this.period,
    this.duration = const Duration(milliseconds: 500),
    this.loop = 1,
    this.startIndex = 0,
    this.spacing = 0.5,
    this.direction = FlipDirection.up,
    }) : assert(itemBuilder != null),
    assert(itemsCount != null),
    assert(startIndex < itemsCount),
    assert(period == null ||
    period.inMilliseconds >= 2 * duration.inMilliseconds),
    indexedItemBuilder = itemBuilder,
    streamItemBuilder = null,
    itemStream = null,
    initValue = null,
    super(key: key);

    /// Create a flip panel from stream source
    /// [itemBuilder] is called whenever a new value is emitted from [itemStream]
    FlipPanel.stream({
    Key key,
    @required this.itemStream,
    @required StreamItemBuilder<T> itemBuilder,
    this.initValue,
    this.duration = const Duration(milliseconds: 500),
    this.spacing = 0.5,
    this.direction = FlipDirection.up,
    }) : assert(itemStream != null),
    indexedItemBuilder = null,
    streamItemBuilder = itemBuilder,
    itemsCount = 0,
    period = null,
    loop = 0,
    startIndex = 0,
    super(key: key);

    @override
    _FlipPanelState<T> createState() => _FlipPanelState<T>();
    }

    class _FlipPanelState<T> extends State<FlipPanel>
    with TickerProviderStateMixin {
    AnimationController _controller;
    Animation _animation;
    int _currentIndex;
    bool _isReversePhase;
    bool _isStreamMode;
    bool _running;
    final _perspective = 0.006;
    int _loop;
    T _currentValue, _nextValue;
    Timer _timer;
    StreamSubscription<T> _subscription;

    Widget _child1, _child2;
    Widget _upperChild1, _upperChild2;
    Widget _lowerChild1, _lowerChild2;

    @override
    void initState() {
    super.initState();
    _currentIndex = widget.startIndex;
    _isStreamMode = widget.itemStream != null;
    _isReversePhase = false;
    _running = false;
    _loop = 0;

    _controller =
    new AnimationController(duration: widget.duration, vsync: this)
    ..addStatusListener((status) {
    if (status == AnimationStatus.completed) {
    _isReversePhase = true;
    _controller.reverse();
    }
    if (status == AnimationStatus.dismissed) {
    _currentValue = _nextValue;
    setState(() {
    _running = false;
    });
    }
    })
    ..addListener(() {
    setState(() {
    _running = true;
    });
    });
    _animation = Tween(begin: 0.0, end: math.pi / 2).animate(_controller);

    if (widget.period != null) {
    _timer = Timer.periodic(widget.period, (_) {
    if (widget.loop < 0 || _loop < widget.loop) {
    if (_currentIndex + 1 == widget.itemsCount - 2) {
    _loop++;
    }
    _currentIndex = (_currentIndex + 1) % widget.itemsCount;
    _child1 = null;
    _isReversePhase = false;
    _controller.forward();
    } else {
    _timer.cancel();
    _currentIndex = (_currentIndex + 1) % widget.itemsCount;
    setState(() {
    _running = false;
    });
    }
    });
    }

    if (_isStreamMode) {
    _currentValue = widget.initValue;
    _subscription = widget.itemStream.distinct().listen((value) {
    if (_currentValue == null) {
    _currentValue = value;
    } else if (value != _currentValue) {
    _nextValue = value;
    _child1 = null;
    _isReversePhase = false;
    _controller.forward();
    }
    });
    } else if (widget.loop < 0 || _loop < widget.loop) {
    _controller.forward();
    }
    }

    @override
    void dispose() {
    _controller.dispose();
    if (_subscription != null) _subscription.cancel();
    if (_timer != null) _timer.cancel();
    super.dispose();
    }

    @override
    Widget build(BuildContext context) {
    _buildChildWidgetsIfNeed(context);

    return _buildPanel();
    }

    void _buildChildWidgetsIfNeed(BuildContext context) {
    Widget makeUpperClip(Widget widget) {
    return ClipRect(
    child: Align(
    alignment: Alignment.topCenter,
    heightFactor: 0.5,
    child: widget,
    ),
    );
    }

    Widget makeLowerClip(Widget widget) {
    return ClipRect(
    child: Align(
    alignment: Alignment.bottomCenter,
    heightFactor: 0.5,
    child: widget,
    ),
    );
    }

    if (_running) {
    if (_child1 == null) {
    _child1 = _child2 != null
    ? _child2
    : _isStreamMode
    ? widget.streamItemBuilder(context, _currentValue)
    : widget.indexedItemBuilder(
    context, _currentIndex % widget.itemsCount);
    _child2 = null;
    _upperChild1 =
    _upperChild2 != null ? _upperChild2 : makeUpperClip(_child1);
    _lowerChild1 =
    _lowerChild2 != null ? _lowerChild2 : makeLowerClip(_child1);
    }
    if (_child2 == null) {
    _child2 = _isStreamMode
    ? widget.streamItemBuilder(context, _nextValue)
    : widget.indexedItemBuilder(
    context, (_currentIndex + 1) % widget.itemsCount);
    _upperChild2 = makeUpperClip(_child2);
    _lowerChild2 = makeLowerClip(_child2);
    }
    } else {
    _child1 = _child2 != null
    ? _child2
    : _isStreamMode
    ? widget.streamItemBuilder(context, _currentValue)
    : widget.indexedItemBuilder(
    context, _currentIndex % widget.itemsCount);
    _upperChild1 =
    _upperChild2 != null ? _upperChild2 : makeUpperClip(_child1);
    _lowerChild1 =
    _lowerChild2 != null ? _lowerChild2 : makeLowerClip(_child1);
    }
    }

    Widget _buildUpperFlipPanel() => widget.direction == FlipDirection.up
    ? Stack(
    children: [
    _upperChild1,
    Transform(
    alignment: Alignment.bottomCenter,
    transform: (Matrix4.identity()..setEntry(3, 2, _perspective)) *
    Matrix4.rotationX(
    _isReversePhase ? _animation.value : math.pi / 2),
    child: _upperChild2,
    ),
    ],
    )
    : Stack(
    children: [
    _upperChild2,
    Transform(
    alignment: Alignment.bottomCenter,
    transform: (Matrix4.identity()..setEntry(3, 2, _perspective)) *
    Matrix4.rotationX(
    _isReversePhase ? math.pi / 2 : _animation.value),
    child: _upperChild1,
    ),
    ],
    );

    Widget _buildLowerFlipPanel() => widget.direction == FlipDirection.up
    ? Stack(
    children: [
    _lowerChild2,
    Transform(
    alignment: Alignment.topCenter,
    transform: (Matrix4.identity()..setEntry(3, 2, _perspective)) *
    Matrix4.rotationX(
    _isReversePhase ? math.pi / 2 : -_animation.value),
    child: _lowerChild1,
    )
    ],
    )
    : Stack(
    children: [
    _lowerChild1,
    Transform(
    alignment: Alignment.topCenter,
    transform: (Matrix4.identity()..setEntry(3, 2, _perspective)) *
    Matrix4.rotationX(
    _isReversePhase ? -_animation.value : math.pi / 2),
    child: _lowerChild2,
    )
    ],
    );

    Widget _buildPanel() {
    return _running
    ? Column(
    mainAxisSize: MainAxisSize.min,
    children: [
    _buildUpperFlipPanel(),
    Padding(
    padding: EdgeInsets.only(top: widget.spacing),
    ),
    _buildLowerFlipPanel(),
    ],
    )
    : _isStreamMode && _currentValue == null
    ? Container()
    : Column(
    mainAxisSize: MainAxisSize.min,
    children: [
    _upperChild1,
    Padding(
    padding: EdgeInsets.only(top: widget.spacing),
    ),
    _lowerChild1
    ],
    );
    }
    }