♥️

Flutterで、Flash Cardを作ってみた

2024/08/17に公開

スワイプすると動くカード

Flutterで、Flash Cardを自作してみた。スワイプすると横に動くカードですね。実装するには、アニメーションを使う必要があるみたいですね。

ダミーのデータをリストに格納して、ランダムな色のカードに表示するデモアプリを作ってみました。

これが、サンプルコード

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Flashcard App',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const FlashcardScreen(),
    );
  }
}

class Flashcard {
  final String title;
  final String content;

  Flashcard({required this.title, required this.content});
}

class FlashcardScreen extends StatefulWidget {
  const FlashcardScreen({super.key});

  
  _FlashcardScreenState createState() => _FlashcardScreenState();
}

class _FlashcardScreenState extends State<FlashcardScreen>
    with SingleTickerProviderStateMixin {
  final List<Flashcard> flashcards = [
    Flashcard(
        title: "コーヒーショップの場所",
        content: "Where is the coffee shop after exiting the station?"),
    Flashcard(
        title: "レストランの予約",
        content: "I'd like to make a reservation for dinner."),
    Flashcard(
        title: "電車の乗り方",
        content: "How do I take the train to the city center?"),
    Flashcard(
        title: "観光スポットの推薦",
        content: "Can you recommend some popular tourist attractions?"),
    Flashcard(
        title: "ホテルのチェックイン時間", content: "What time is check-in at the hotel?"),
  ];

  int currentIndex = 0;
  late AnimationController _controller;
  late Animation<Offset> _animation;
  late Animation<double> _rotationAnimation;

  Color backgroundColor = _getRandomColor();

  
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 300), vsync: this);
    _animation = Tween<Offset>(begin: Offset.zero, end: const Offset(1.5, 0.0))
        .animate(_controller);
    _rotationAnimation = Tween<double>(begin: 0, end: 0.1).animate(_controller);
  }

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

  void _nextCard() {
    setState(() {
      if (currentIndex < flashcards.length - 1) {
        currentIndex++;
      } else {
        currentIndex = 0;
      }
      backgroundColor = _getRandomColor();
    });
  }

  void _previousCard() {
    setState(() {
      if (currentIndex > 0) {
        currentIndex--;
      } else {
        currentIndex = flashcards.length - 1;
      }
      backgroundColor = _getRandomColor();
    });
  }

  static Color _getRandomColor() {
    return Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0)
        .withOpacity(1.0);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onHorizontalDragUpdate: (details) {
          _controller.value += details.primaryDelta! / context.size!.width;
        },
        onHorizontalDragEnd: (details) {
          if (_controller.value > 0.5) {
            _controller.forward().then((_) {
              _controller.reset();
              _previousCard();
            });
          } else if (_controller.value < -0.5) {
            _controller.forward().then((_) {
              _controller.reset();
              _nextCard();
            });
          } else {
            _controller.reverse();
          }
        },
        child: Center(
          child: AnimatedBuilder(
            animation: _controller,
            builder: (context, child) {
              return Transform.rotate(
                angle: _rotationAnimation.value,
                child: SlideTransition(
                  position: _animation,
                  child: _buildCard(),
                ),
              );
            },
          ),
        ),
      ),
    );
  }

  Widget _buildCard() {
    return Container(
      width: 300,
      height: 200,
      decoration: BoxDecoration(
        color: backgroundColor,
        borderRadius: BorderRadius.circular(10),
        boxShadow: const [
          BoxShadow(
            color: Colors.black26,
            blurRadius: 5,
            offset: Offset(0, 5),
          ),
        ],
      ),
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              flashcards[currentIndex].title,
              style: const TextStyle(
                  color: Colors.white,
                  fontSize: 20,
                  fontWeight: FontWeight.bold),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 16),
            Text(
              flashcards[currentIndex].content,
              style: const TextStyle(color: Colors.white, fontSize: 16),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }
}
  1. フラッシュカードのデータモデル:

Flashcardクラスを使用してフラッシュカードのデータを定義しています。

  1. カードのスワイプアニメーション:

AnimationController、SlideTransition、Transform.rotateを使用して、カードのスワイプと回転アニメーションを実装しています。

  1. ランダムな背景色:

カードが切り替わるたびに_getRandomColor()メソッドを使用して新しい背景色を生成します。

  1. スワイプによるカード切り替え:

GestureDetectorを使用してスワイプジェスチャーを検出し、カードを切り替えます。

  1. カードのレイアウト:

Containerウィジェットを使用して、カードのデザインと影を実装しています

感想

SwiftUIで同じようなデモアプリを作ってみたのですが、同じiOSでもAndroidと同じ、左から、右へスワイプする動きをしていましたね。動くスピードもぬる〜として遅いような。。。。
リッチなUIにするなら、改良が必要ですね。

Discussion