LoginSignup
2
2

Flutter reader animation

Last updated at Posted at 2024-04-29

概要

Flutterでレーダーアニメーションを実装した際の備忘録。

プロジェクト作成

FVMについてはこちら

fvm flutter create radar_animation

グリッド作成

lib/radar_grid_painter.dart
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'dart:math';

class RadarGridPainter extends CustomPainter {
  final int divisions;
  final Color gridColor;

  RadarGridPainter({
    this.divisions = 4,
    this.gridColor = Colors.green,
  });

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = gridColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1;

    // Draw the circles
    double radiusStep = size.width / (2 * divisions);
    for (int i = 1; i <= divisions; i++) {
      canvas.drawCircle(size.center(Offset.zero), i * radiusStep, paint);
    }

    // Draw the spokes
    double angleStep = 2 * math.pi / divisions;
    for (int i = 0; i < divisions; i++) {
      double angle = i * angleStep;
      Offset start = size.center(Offset.zero);
      Offset end = size.center(Offset(math.cos(angle) * size.width / 2, math.sin(angle) * size.width / 2));
      canvas.drawLine(start, end, paint);
    }

    final pointPaint = Paint()
    ..color = Colors.red
    ..style = PaintingStyle.fill;

    final random = Random();
    for (int i = 0; i < 5; i++) {
      final angle = random.nextDouble() * 2 * pi;
      final distance = random.nextDouble() * size.width / 2;
      final x = cos(angle) * distance;
      final y = sin(angle) * distance;
      final offset = Offset(x, y);
      canvas.drawCircle(size.center(offset), 5, pointPaint);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}
main.dart
import 'package:flutter/material.dart';
import 'package:radar_animation/radar_grid_painter.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        // レダーグリッドを描画する
        child: CustomPaint(
          size: const Size(300, 300),
          painter: RadarGridPainter(),
        ),
      ),
    );
  }
}

Screenshot_1714396849.png

レーダっぽくする

main.dart
import 'package:flutter/material.dart';
import 'package:radar_animation/radar_grid_painter.dart';
import 'dart:math';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
  with SingleTickerProviderStateMixin {

  late AnimationController _controller;
  final List<Map<String, num>> points = [
    {'fixedAngle': Random().nextDouble() * 2 * pi, 'radius': 1},
    {'fixedAngle': Random().nextDouble() * 2 * pi, 'radius': 2},
    {'fixedAngle': Random().nextDouble() * 2 * pi, 'radius': 3},
    {'fixedAngle': Random().nextDouble() * 2 * pi, 'radius': 2},
    {'fixedAngle': Random().nextDouble() * 2 * pi, 'radius': 1},
  ];

  @override
  void initState(){
    _controller = AnimationController(vsync: this, duration: const Duration(seconds: 20))
      ..repeat();
   super.initState();
  }

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


  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
      ),
      home: Expanded(
            flex: 2,
            child: Center(
              child: Stack(
                alignment: Alignment.center,
                children: [
                  // The grid background
                  CustomPaint(
                    painter: RadarGridPainter(points: points.map((point) => point.map((key, value) => MapEntry(key, value.toDouble()))).toList()),
                    size: Size(MediaQuery.of(context).size.width, MediaQuery.of(context).size.width),
                  ),
                  // The rotating radar sweep
                  RotationTransition(
                    turns: Tween(begin: 0.0, end: 3.0).animate(_controller),
                    child:Container(
                    width: MediaQuery.of(context).size.width,
                    height: MediaQuery.of(context).size.width,
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      gradient: SweepGradient(
                        center: Alignment.center,
                        startAngle: 0.0,
                        endAngle: 2 * pi,
                        colors: [
                          Colors.transparent,
                          Colors.green.withOpacity(0.75),
                          Colors.transparent
                        ],
                        stops: const [0.0, 0.05, 0.1],
                      ),
                    ),
                  ),
                  ),
                ],
              ),
            ),
          ),
    );
  }
}

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

class RadarGridPainter extends CustomPainter {
  final int divisions;
  final Color gridColor;
  final List<Map<String, double>> points;

  RadarGridPainter({
    required this.points,
    this.divisions = 4,
    this.gridColor = Colors.green,
  });

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = gridColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1;

    // Draw the circles
    double radiusStep = size.width / (2 * divisions);
    for (int i = 1; i <= divisions; i++) {
      canvas.drawCircle(size.center(Offset.zero), i * radiusStep, paint);
    }

    // Draw the spokes
    double angleStep = 2 * math.pi / divisions;
    for (int i = 0; i < divisions; i++) {
      double angle = i * angleStep;
      Offset start = size.center(Offset.zero);
      Offset end = size.center(Offset(math.cos(angle) * size.width / 2, math.sin(angle) * size.width / 2));
      canvas.drawLine(start, end, paint);
    }

    final pointPaint = Paint()
    ..color = Colors.red
    ..style = PaintingStyle.fill;

    for (int i = 0; i < points.length; i++) {
      final fixedAngle = points[i]['fixedAngle']!;
      final radius = points[i]['radius']! * min(size.width, size.height) / (2 * divisions);
      final point = Offset(size.width / 2 + radius * cos(fixedAngle - pi / 2), size.height / 2 + radius * sin(fixedAngle - pi / 2));
      canvas.drawCircle(point, 6.0, pointPaint);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

Screenshot_1714398849.png

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2