#Flutter, Реализация тинера как свайп: после удачного свипа стек сразу же возвращается в исходное состояние - PullRequest
0 голосов
/ 18 сентября 2018

Я пытаюсь реализовать эффект смахивания карт, похожий на трут (https://github.com/Ivaskuu/tinder_cards) (плюс эффект переворачивания, создающий вид карты с двусторонним смахиванием) со следующим результатом:

После «действительного» пролистывания на секунду появляется вторая карта и карта в верхней части стопки исчезает, но затем весь стек возвращается к исходному состоянию.

Замечания по отладке: Кажется, что changeCardsOrder () работает нормально, так как данные в карточках [] корректно изменяются, как видно при отладке. Стек строится (через setState) с правильными карточками []., экран показывает первоначально загруженный стек через секунду.

Вот код для съемной карты, которая использует внутреннюю карту Flippable:

import 'dart:math';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:laconically/util/flippable_card.dart';


List<Alignment> cardsAlign = [ new Alignment(0.0, 1.0), new Alignment(0.0, 0.8), new Alignment(0.0, 0.0) ];
List<Size> cardsSize;
List<DocumentSnapshot> wordList;

class SwipableCard extends StatefulWidget
{
  SwipableCard(BuildContext context, List<DocumentSnapshot> wl)
  {
    wordList = wl;
    cardsSize = new List(wl.length);
    for(int i = 0; i < wl.length; i++) {
      cardsSize[i] = new Size(MediaQuery.of(context).size.width * (0.9 - i*0.05), MediaQuery.of(context).size.height * (0.6 - i*0.05));
    }
//    cardsSize[1] = new Size(MediaQuery.of(context).size.width * 0.85, MediaQuery.of(context).size.height * 0.55);
//    cardsSize[2] = new Size(MediaQuery.of(context).size.width * 0.8, MediaQuery.of(context).size.height * 0.5);
  }

  @override
  _SwipableCardState createState() => new _SwipableCardState();
}

class _SwipableCardState extends State<SwipableCard> with SingleTickerProviderStateMixin
{
  int cardsCounter = 0;

  List<FlippableCard> cards = new List();
  AnimationController _controller;

  final Alignment defaultFrontCardAlign = new Alignment(0.0, 0.0);
  Alignment frontCardAlign;
  double frontCardRot = 0.0;

  @override
  void initState()
  {
    super.initState();

    // Init cards
    for (cardsCounter = 0; cardsCounter < wordList.length; cardsCounter++)
    {
      cards.add(new FlippableCard(wordList[cardsCounter].data));
    }

    frontCardAlign = cardsAlign[2];

    // Init the animation controller
    _controller = new AnimationController(duration: new Duration(milliseconds: 700), vsync: this);
    _controller.addListener(() => setState(() {}));
    _controller.addStatusListener((AnimationStatus status)
    {
      if(status == AnimationStatus.completed) changeCardsOrder();
    });
  }

  @override
  Widget build(BuildContext context)
  {
    return Expanded
      (
        child: new Stack
          (
          children: <Widget>
          [
            backCard(),
            middleCard(),
            frontCard(),

            // Prevent swiping if the cards are animating
            _controller.status != AnimationStatus.forward ? new SizedBox.expand
              (
                child: new GestureDetector
                  (
                  // While dragging the first card
                  onPanUpdate: (DragUpdateDetails details)
                  {
                    // Add what the user swiped in the last frame to the alignment of the card
                    setState(()
                    {
                      // 20 is the "speed" at which moves the card
                      frontCardAlign = new Alignment
                        (
                          frontCardAlign.x + 20 * details.delta.dx / MediaQuery.of(context).size.width,
                          frontCardAlign.y + 40 * details.delta.dy / MediaQuery.of(context).size.height
                      );

                      frontCardRot = frontCardAlign.x; // * rotation speed;
                    });
                  },
                  // When releasing the first card
                  onPanEnd: (_)
                  {
                    // If the front card was swiped far enough to count as swiped
                    if(frontCardAlign.x > 3.0 || frontCardAlign.x < - 3.0)
                    {
                      animateCards();
                    }
                    else
                    {
                      // Return to  the initial rotation and alignment
                      setState(()
                      {
                        frontCardAlign = defaultFrontCardAlign;
                        frontCardRot = 0.0;
                      });
                    }
                  },
                )
            ) : new Container(),
          ],
        )
    );
  }

  Widget backCard()
  {
    return new Align
      (
      alignment: _controller.status == AnimationStatus.forward ? CardsAnimation.backCardAlignmentAnim(_controller).value : cardsAlign[0],
      child: new SizedBox.fromSize
        (
          size: _controller.status == AnimationStatus.forward ? CardsAnimation.backCardSizeAnim(_controller).value : cardsSize[2],
          child: cards[2]
      ),
    );
  }

  Widget middleCard()
  {
    return new Align
      (
      alignment: _controller.status == AnimationStatus.forward ? CardsAnimation.middleCardAlignmentAnim(_controller).value : cardsAlign[1],
      child: new SizedBox.fromSize
        (
          size: _controller.status == AnimationStatus.forward ? CardsAnimation.middleCardSizeAnim(_controller).value : cardsSize[1],
          child: cards[1]
      ),
    );
  }

  Widget frontCard()
  {
    return Align
      (
        alignment: _controller.status == AnimationStatus.forward ? CardsAnimation.frontCardDisappearAlignmentAnim(_controller, frontCardAlign).value : frontCardAlign,
        child: new Transform.rotate
          (
          angle: (pi / 180.0) * frontCardRot,
          child: new SizedBox.fromSize
            (
              size: cardsSize[0],
              child:  cards[0]
          ),
        )
    );
  }

  void changeCardsOrder()
  {
    setState(()
    {
      // Swap cards (back card becomes the middle card; middle card becomes the front card, front card becomes a new bottom card)
      var temp = cards[0];
      int i=1;
      for(;i<wordList.length; i++) {
        cards[i-1] = cards[i];
      }
      cards[i-1] = temp;
//      cards[i-1] = new FlippableCard(wordList[cardsCounter - 1].data);
//      cardsCounter++;
      frontCardAlign = defaultFrontCardAlign;
      frontCardRot = 0.0;
    });
  }

  void animateCards()
  {
    _controller.stop();
    _controller.value = 0.0;
    _controller.forward();
  }
}

class CardsAnimation
{
  static Animation<Alignment> backCardAlignmentAnim(AnimationController parent)
  {
    return new AlignmentTween
      (
        begin: cardsAlign[0],
        end: cardsAlign[1]
    ).animate
      (
        new CurvedAnimation
          (
            parent: parent,
            curve: new Interval(0.4, 0.7, curve: Curves.easeIn)
        )
    );
  }

  static Animation<Size> backCardSizeAnim(AnimationController parent)
  {
    return new SizeTween
      (
        begin: cardsSize[2],
        end: cardsSize[1]
    ).animate
      (
        new CurvedAnimation
          (
            parent: parent,
            curve: new Interval(0.4, 0.7, curve: Curves.easeIn)
        )
    );
  }

  static Animation<Alignment> middleCardAlignmentAnim(AnimationController parent)
  {
    return new AlignmentTween
      (
        begin: cardsAlign[1],
        end: cardsAlign[2]
    ).animate
      (
        new CurvedAnimation
          (
            parent: parent,
            curve: new Interval(0.2, 0.5, curve: Curves.easeIn)
        )
    );
  }

  static Animation<Size> middleCardSizeAnim(AnimationController parent)
  {
    return new SizeTween
      (
        begin: cardsSize[1],
        end: cardsSize[0]
    ).animate
      (
        new CurvedAnimation
          (
            parent: parent,
            curve: new Interval(0.2, 0.5, curve: Curves.easeIn)
        )
    );
  }

  static Animation<Alignment> frontCardDisappearAlignmentAnim(AnimationController parent, Alignment beginAlign)
  {
    return new AlignmentTween
      (
        begin: beginAlign,
        end: new Alignment(beginAlign.x > 0 ? beginAlign.x + 30.0 : beginAlign.x - 30.0, 0.0) // Has swiped to the left or right?
    ).animate
      (
        new CurvedAnimation
          (
            parent: parent,
            curve: new Interval(0.0, 0.5, curve: Curves.easeIn)
        )
    );
  }
}

Вот код для скользящей карты:

import 'package:flutter/material.dart';

class FlippableCard extends StatefulWidget {
  Map<String, dynamic> wordMap;

  @override
  State<StatefulWidget> createState() => FlippableCardState(wordMap);

  FlippableCard(Map<String, dynamic> wm) {
    wordMap = wm;
  }
}

class FlippableCardState extends State<FlippableCard>
    with TickerProviderStateMixin {
  Map<String, dynamic> wordMap;

  FlippableCardState(Map<String, dynamic> wm) {
    wordMap = wm;
  }

  AnimationController _controller;
  Animation<double> _frontScale;
  Animation<double> _backScale;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller = new AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 800),
    );
    _frontScale = new Tween(
      begin: 1.0,
      end: 0.0,
    ).animate(new CurvedAnimation(
      parent: _controller,
      curve: new Interval(0.0, 0.5, curve: Curves.easeIn),
    ));
    _backScale = new CurvedAnimation(
      parent: _controller,
      curve: new Interval(0.5, 1.0, curve: Curves.easeOut),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        AnimatedBuilder(
          child: GestureDetector(
            child: Card(
              elevation: 30.0,
              margin: EdgeInsets.fromLTRB(10.0, 80.0, 10.0, 0.0),
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(20.0),
              ),
              child: Padding(
                padding: EdgeInsets.all(20.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Padding(
                      padding: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 30.0),
                      child: Text(
                        wordMap['word'],
                        textScaleFactor: 5.0,
                        style: TextStyle(
                            fontFamily: 'Cursive', color: Colors.green),
                      ),
                    ),
                    Padding(
                      padding: EdgeInsets.fromLTRB(0.0, 20.0, 20.0, 0.0),
                      child: Text(
                        wordMap['type'],
                        softWrap: true,
                        textAlign: TextAlign.center,
                        textScaleFactor: 0.9,
                      ),
                    ),
                    Padding(
                      padding: EdgeInsets.fromLTRB(0.0, 20.0, 20.0, 0.0),
                      child: Text(
                        wordMap['value'],
                        softWrap: true,
                        textAlign: TextAlign.center,
                        textScaleFactor: 1.1,
                      ),
                    )
                  ],
                ),
              ),
            ),
            onTap: () {
              setState(() {
                if (_controller.isCompleted || _controller.velocity > 0)
                  _controller.reverse();
                else
                  _controller.forward();
              });
            },
          ),
          animation: _frontScale,
          builder: (BuildContext context, Widget child) {
            final Matrix4 transform = new Matrix4.identity()
              ..scale(1.0, _frontScale.value, 1.0);
            return Transform(
              transform: transform,
              alignment: FractionalOffset.center,
              child: child,
            );
          },
        ),
        AnimatedBuilder(
          child: GestureDetector(
            child: Card(
                elevation: 30.0,
                margin: EdgeInsets.fromLTRB(10.0, 80.0, 10.0, 30.0),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(20.0),
                ),
                child: Padding(
                  padding: EdgeInsets.all(20.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Padding(
                        padding: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 30.0),
                        child: Text(
                          'Examples',
                          style: TextStyle(color: Colors.black45),
                          softWrap: true,
                          textAlign: TextAlign.left,
                          textScaleFactor: 2.0,
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 30.0),
                        child: Text(
                          wordMap['examples'][0],
                          softWrap: true,
                          textAlign: TextAlign.left,
                          textScaleFactor: 1.1,
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 30.0),
                        child: Text(
                          wordMap['examples'][1],
                          softWrap: true,
                          textAlign: TextAlign.left,
                          textScaleFactor: 1.1,
                        ),
                      )
                    ],
                  ),
                )),
            onTap: () {
              setState(() {
                if (_controller.isCompleted || _controller.velocity > 0)
                  _controller.reverse();
                else
                  _controller.forward();
              });
            },
          ),
          animation: _backScale,
          builder: (BuildContext context, Widget child) {
            final Matrix4 transform = new Matrix4.identity()
              ..scale(1.0, _backScale.value, 1.0);
            return new Transform(
              transform: transform,
              alignment: FractionalOffset.center,
              child: child,
            );
          },
        )
      ],
    );
  }
}

Это домашний файл дротика, в котором реализована карта с возможностью сканирования:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:laconically/util/custom_fab.dart';
import 'package:laconically/util/swipable_card.dart';

List<DocumentSnapshot> wordList;

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => HomeState();

  Home(List<DocumentSnapshot> wl) {
    wordList = wl;
  }
}

class HomeState extends State<Home> {
  bool dragOverTarget = false;
  int cardsCounter = 0;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomPadding: false,
      backgroundColor: Colors.white,
      body: Column(
        children: <Widget>[
          SwipableCard(context, wordList),
          Padding(
            padding: EdgeInsets.fromLTRB(0.0, 0.0, 30.0, 40.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: <Widget>[
                FancyFab(
                  onPressed: null,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Есть идеи, что может быть причиной такого поведения?

...