Flutter Animation — переворачивание карты без использования предварительно определенных пакетов

Я пытаюсь перевернуть карту спереди назад и сзади наперед без использования зависимостей, но не смог реализовать. Я видел, как люди использовали предопределенный пакет Flip Card, но без зависимости у меня возникают проблемы. Пожалуйста, помогите мне.

Я хочу, чтобы карта переворачивалась назад, как только я нажимаю кнопку «Значок», и обратно, как только я нажимаю кнопку «Назад». Я попробовал эту идею без использования анимации, и все работает отлично, но как мне реализовать флип-анимацию, мне кажется, это сложно.

class NotificationItemCard extends StatefulWidget {
  const NotificationItemCard({
    Key? key,
  }) : super(key: key);

  @override
  State<NotificationItemCard> createState() => _NotificationItemCardState();
}

class _NotificationItemCardState extends State<NotificationItemCard> {
  late bool showCardFrontSide;
  @override
  void initState() {
    showCardFrontSide = true;
    super.initState();
  }

  void onChangeView() {
    setState(() {
      showCardFrontSide = !showCardFrontSide;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Container(
          height: 140.h,
          decoration: BoxDecoration(
            border: Border.all(color: Colors.black),
            borderRadius: BorderRadius.circular(8),
          ),
          child: showCardFrontSide
              ? const NotificationCardFrontSide()
              : NotificationCardBackSide(
                  onChangeView: onChangeView,
                ),
        ),
        showCardFrontSide
            ? Align(
                alignment: const Alignment(0.95, -1),
                child: IconButton(
                  key: const ValueKey("IconButton"),
                  onPressed: onChangeView,
                  icon: const Icon(Icons.info_outline),
                ),
              )
            : const SizedBox.shrink()
      ],
    );
  }
}

class NotificationCardFrontSide extends StatelessWidget {
  const NotificationCardFrontSide({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        SizedBox(
          key: const ValueKey("FrontSideSizedBox"),
          width: 126.w,
          child: Center(
            child: CircleAvatar(
              radius: 50.r,
            ),
          ),
        ),
        SizedBox(
          key: const ValueKey("FrontSideSizedTextBox"),
          width: 222.w,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                "Premium Private LOBBY",
                style: Theme.of(context).textTheme.headlineMedium?.copyWith(overflow: TextOverflow.ellipsis),
                key: const ValueKey("FrontSideSizedTextBox1"),
              ),
              Text(
                "Prediction Deadline",
                // "Prediction Deadline - ${DateConverterUtil.convert(lobby.match.start)}",
                style: Theme.of(context).textTheme.headlineMedium?.copyWith(overflow: TextOverflow.ellipsis),
                key: const ValueKey("FrontSideSizedTextBox2"),
              ),
              Text(
                "Premium Private LOBBY",
                style: Theme.of(context).textTheme.headlineMedium?.copyWith(overflow: TextOverflow.ellipsis),
                key: const ValueKey("FrontSideSizedTextBox3"),
              ),
              SizedBox(
                key: const ValueKey("FrontSideSizedButtonBox"),
                width: 150.w,
                height: 45.h,
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: [
                    SizedBox(
                      key: const ValueKey("FrontSideButtonSizedBox"),
                      width: 70.w,
                      child: TextButton(
                        onPressed: () {},
                        child: Text(
                          "deny",
                          style: Theme.of(context).textTheme.bodyMedium,
                        ),
                      ),
                    ),
                    SizedBox(
                      width: 70.w,
                      child: TextButton(
                        onPressed: () {},
                        child: Text(
                          "deny",
                          style: Theme.of(context).textTheme.bodyMedium,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

class NotificationCardBackSide extends StatelessWidget {
  final VoidCallback onChangeView;

  const NotificationCardBackSide({
    Key? key,
    required this.onChangeView,
  }) : super(key: key);

  Widget getTeamLogo(String image) {
    return CircleAvatar(
      backgroundColor: const Color(0xFFD9D9D9),
      radius: 30.r,
      child: Image.network(
        image,
        errorBuilder: (context, error, stackTrace) {
          return Text(
            "Error",
            style: Theme.of(context).textTheme.displayMedium?.copyWith(
                  color: Colors.red,
                ),
          );
        },
        height: 65.h,
        width: 65.w,
        loadingBuilder: (context, child, loadingProgress) {
          if (loadingProgress == null) return child;
          return Center(
            child: Text(
              "Loading...",
              style: Theme.of(context).textTheme.displayMedium,
            ),
          );
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        SizedBox(
          height: 62.h,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              getTeamLogo(""),
              Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    "Premium Private LOBBY",
                    style: Theme.of(context).textTheme.headlineMedium?.copyWith(overflow: TextOverflow.clip),
                    key: const ValueKey("BackSideSizedText1"),
                  ),
                  Text(
                    "Prediction Deadline",
                    // "Prediction Deadline - ${DateConverterUtil.convert(lobby.match.start)}",
                    style: Theme.of(context).textTheme.headlineMedium?.copyWith(overflow: TextOverflow.clip),
                    key: const ValueKey("BackSideSizedText2"),
                  ),
                ],
              ),
              getTeamLogo(""),
            ],
          ),
        ),
        SizedBox(
          key: const ValueKey("BackSideButtonBox"),
          height: 30.h,
          width: 100.w,
          child: OutlinedButton(
            onPressed: onChangeView,
            child: const Text("Go Back"),
            key: const ValueKey("BackSideButtonText"),
            style: ButtonStyle(
              shape: MaterialStateProperty.all(
                RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(
                    8.r,
                  ),
                ),
              ),
            ),
          ),
        )
      ],
    );
  }
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете реализовать это с помощью AnimatedBuilder и Transform. Используйте пример ниже:

import 'dart:math';
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(debugShowCheckedModeBanner: false, home: Scaffold(body: MyApp())));
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  Widget _front = Card1();
  Widget _back = Card2();
  late Widget _card = _front;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 600));
    _controller.addListener(() {
      if (_controller.value >= .5 && _card != _back) {
        setState(() => _card = _back);
      } else if (_controller.value < .5 && _card != _front) {
        setState(() => _card = _front);
      }
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        onTap: () {
          if (_controller.value == 1)
            _controller.reverse(from: 1);
          else
            _controller.forward(from: 0);
        },
        child: AnimatedBuilder(
          animation: _controller,
          builder: (c, anim) => Transform(
            transform: Matrix4.identity()
              ..setEntry(3, 2, 0.0025)
              ..rotateY(_controller.value * pi),
            alignment: FractionalOffset.center,
            child: _card,
          ),
        ),
      ),
    );
  }
}

class Card1 extends StatelessWidget {
  const Card1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 150,
      height: 300,
      color: Colors.red,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('This is Card1'),
          Text('I\'m front of the card'),
        ],
      ),
    );
  }
}

class Card2 extends StatelessWidget {
  const Card2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Transform.scale(
      scaleX: -1,
      child: Container(
        width: 150,
        height: 300,
        color: Colors.blue,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('This is Card2'),
            Text('I\'m back of the card'),
          ],
        ),
      ),
    );
  }
}

Результат:

(Спасибо этому ответу за код преобразования)

Другие вопросы по теме

Похожие вопросы