1

Need help with simulating transition of widgets from one location to another. The new location is the location of another widget.

If anyone is familiar with Framer Motion's layoutid, it's similar to that but I can't find anything flutter. For those who are not familiar with it, layoutId is like a key on a widget that detects old and new location of the widget; when the widget location changes in the widget tree Framer Motion will moves the widget to its new location where the layoutId is.

I have looked into Hero Animation which uses tags but it's for transition between routes. Position in flutter requires us to specify the location. Can anyone guide me in the right direction?

If the question is not clear enough feel free to ask questions!

Framer Motion code using layoutId

interface CardProp {
  card: TCard;
  isFlipped: boolean;
  playCard?: (c: TCard, p: Player) => void;
  player?: Player;
}

export default function Card({
  card,
  isFlipped,
  playCard,
  player,
}: CardProp) {
  const isFace = !isFlipped && card !== undefined;
  const src = isFace ? createCardSVGPath(card!) : CARD_BACK_SVG_PATH;

  return (
    <Box
      data-testid={`card-${card}-div`}
      id={player == null ? "" : `player${player}-card${card}`}
      margin={{base: "1%"}}
    >
      <motion.img
        data-testid={`card-${card}`}
        initial={{ x: 0, y: 0, opacity: 0, scale: 0.5 }}
        animate={{ opacity: 1, scale: 1 }}
        onClick={() => {
          if (player != null && playCard && !isFlipped) playCard(card, player);
        }}
        src={src}
        layoutId={card?.toString()} // Framer motion use layoutId to animate the image transition whne Card is removed from one component to another component
        className="size-40 inline"
      />
    </Box>
  );
}

Current Flutter card Widget

class Card extends StatelessWidget {
  final int cardNumber;
  final double padding;
  final bool isFlipped;

  const Card({
    super.key,
    required this.cardNumber,
    required this.padding,
    required this.isFlipped,
  });

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: GestureDetector(
        onTap: () {
          // transition the widget to new location
        },
        child: Padding(
          padding: EdgeInsets.all(padding),
          child: isFlipped
              ? FittedBox(
                  fit: BoxFit.contain,
                  child: Image(image: AssetImage(cardBackSVGPath)),
                )
              : FittedBox(
                  fit: BoxFit.contain,
                  child: Image(
                    image: AssetImage(createCardSVGPath(cardNumber)),
                  ),
                ),
        ),
      ),
    );
  }
}

Edit:

This is the page I have now picture of the page. I want the cards in the bottom to move into one of the two center piles. Upon clicking the card in the bottom, the server will check if the action is permitted and send back new game state. The new game state will have the the bottom cards and the center piles updated.

2
  • Please add any images or GIF to the target animation you want to implement Commented Jun 17 at 16:06
  • @MahmoudAl-shehyby I have attached the image, thank you! Commented Jun 17 at 16:11

1 Answer 1

0

You might be looking for a combination of GlobalKey, RenderBox, AnimationController, and Tween

This demo illustrates the usage of those classes.

When a user card and center card are selected, those cards are given GlobalKey and their RenderBox is used to find global positions. The AnimationController is then provided with a Tween.

  void _startAnimation() {
    // Determine the key of the selected user card
    GlobalKey selectedCardKey = _isTopPlayerSelected
        ? _topPlayerCardKeys[_selectedUserCardIndex!]
        : _bottomPlayerCardKeys[_selectedUserCardIndex!];

    // Determine the key of the selected center pile
    GlobalKey targetPileKey = _centerPileKeys[_selectedCenterPileIndex!];

    // Get the RenderBox of the selected user card
    final RenderBox userCardBox =
        selectedCardKey.currentContext?.findRenderObject() as RenderBox;
    _startOffset = userCardBox.localToGlobal(Offset.zero);

    // Get the RenderBox of the target center pile
    final RenderBox targetPileBox =
        targetPileKey.currentContext?.findRenderObject() as RenderBox;
    _endOffset = targetPileBox.localToGlobal(Offset.zero);

    setState(() {
      // Store the asset of the card that will be animated
      _animatingCardAsset = _isTopPlayerSelected
          ? _topPlayerCards[_selectedUserCardIndex!]
          : _bottomPlayerCards[_selectedUserCardIndex!];
    });

    _animation = Tween<Offset>(begin: _startOffset, end: _endOffset).animate(
      CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
    );

    _animationController.forward(from: 0.0);
  }
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for the response! The demo does have the animation I want. But it requires the user to choose one of the center pile. However, in my game the server checks the center pile when user uses a card on their hand, the server will updates the game and send back the new game state. My idea is that we can check the center piles and see which one is updated then get the RenderBox on that one then perform the Tween animation. Does this seem like a good approach?
glad it helped! could simplify if the server sent the center pile index along with the game state
Yeah I currently don't have that right now but I'll try to implement it if needed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.