Flutter — пользовательский таймер с пользовательскими изображениями для пути и значка

Итак, я хочу показать таймер. Ниже приведен простой дизайн того, чего я хочу достичь. Автомобиль должен объезжать трассу по мере того, как время постепенно уменьшается. На данный момент я понятия не имею, как это реализовать. Еще есть момент, когда лицо машины должно вращаться при движении по петле. Если кто-то делал что-то подобное в прошлом, пожалуйста, дайте мне знать. Будем очень признательны за любые руководства/учебники.

используйте виджет CustomPaint

pskink 29.04.2024 18:10
Стоит ли изучать 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
1
76
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Добавить пакет: path_drawing

И попробуйте запустить этот код. На данный момент я использовал таймер для обновления положения автомобиля, вы можете сделать это в соответствии с вашими требованиями.

Изображение автомобиля добавляется в папку ресурсов. В этом примере использовано изображение автомобиля .

Центральное изображение также добавляется в папку ресурсов («pluto-done.png»), и его необходимо преобразовать, поскольку оно также будет рисоваться с использованием специального средства рисования.

import 'dart:async';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_drawing/path_drawing.dart' as pd;

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

  @override
  State<CustomRoadWithCar> createState() => _CustomRoadWithCarState();
}

class _CustomRoadWithCarState extends State<CustomRoadWithCar> {
  Timer? _timer;
  int _elapsedSeconds = 0;

  // Variable to control the movement of the car and path fill
  double _carProgress = 0.0;

  ui.Image? carImage;
  ui.Image? centerImage;

  @override
  void initState() {
    super.initState();
    // Load the car image from the asset
    loadImageFromAsset('assets/car.png', isCarImage: true);
    // Load the center image from the asset
    loadImageFromAsset('assets/pluto-done.png', isCarImage: false);
    startTimer();
  }

  void startTimer() {
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      setState(() {
        _elapsedSeconds++;
        // Calculate the progress based on elapsed seconds
        _carProgress = _elapsedSeconds / 59.0;

        // Check if the elapsed time has reached 59 seconds
        if (_elapsedSeconds >= 59) {
          // Stop the timer
          _timer?.cancel();
        }
      });
    });
  }

  @override
  void dispose() {
    // Cancel the timer when the widget is disposed
    _timer?.cancel();
    super.dispose();
  }

  // Load an image from assets and convert it to a ui.Image object
  Future<void> loadImageFromAsset(String path,
      {required bool isCarImage}) async {
    // Load the asset data
    final ByteData data = await rootBundle.load(path);
    final Uint8List bytes = data.buffer.asUint8List();

    // Decode the image data
    final ui.Codec codec = await ui.instantiateImageCodec(bytes);
    final ui.FrameInfo frameInfo = await codec.getNextFrame();
    final ui.Image loadedImage = frameInfo.image;

    // Set the loaded image to the appropriate state variable
    setState(() {
      if (isCarImage) {
        // If the image is for the car, set it to the image variable
        carImage = loadedImage;
      } else {
        // If the image is for the center of the circle, set it to the centerImage variable
        centerImage = loadedImage;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Circle Border Drawing'),
      ),
      body: Center(
        child: Stack(
          children: [
            //grey color road
            CustomPaint(
              size: const Size(
                  200, 200), // Set the size of the CustomPaint widget
              painter: RoadPainter(25.0), // Set the border width
            ),
            //to be filled by blue color
            CustomPaint(
              size: const Size(
                  200, 200), // Set the size of the CustomPaint widget
              painter: CircleBorderPainter(
                  carProgress:
                      _carProgress, // Pass the current progress to the painter
                  borderWidth: 25.0,
                  image: carImage,
                  centerImage: centerImage,
                  fillColor: const Color(0xff243347)),
            ),
            Text("$_elapsedSeconds")
          ],
        ),
      ),
    );
  }
}

class CircleBorderPainter extends CustomPainter {
  final double carProgress; // Progress of the car and path fill (0.0 to 1.0)
  final double borderWidth;
  final ui.Image? image;
  final ui.Image? centerImage;
  final Color fillColor;

  CircleBorderPainter(
      {required this.carProgress,
      required this.borderWidth,
      required this.image,
      required this.fillColor,
      required this.centerImage});

  @override
  void paint(Canvas canvas, Size size) {
    final double radius = size.width / 2;
    final Paint paint = Paint()
      ..color = fillColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth
      ..strokeCap = StrokeCap.round;

    // Calculate the arc angle based on progress
    double sweepAngle = carProgress * 2 * pi;

    final center = size.center(Offset.zero);

    // Draw the arc up to the current progress
    Rect rect = Rect.fromCircle(center: center, radius: radius);
    canvas.drawArc(rect, -pi / 2, sweepAngle, false, paint);

    // Calculate the car's position along the arc based on progress
    double carAngle = -pi / 2 + sweepAngle;
    double carX = size.width / 2 + radius * cos(carAngle);
    double carY = size.height / 2 + radius * sin(carAngle);
    Offset carPosition = Offset(carX, carY);

    DashedCirclePainter dashedCirclePainter = DashedCirclePainter(
      strokeWidth: 1.0,
      color: Colors.white,
      dashPattern: [10.0, 5.0], // Dash length and gap length
    );
    dashedCirclePainter.paint(canvas, size, center, radius);

    // Draw the car image at the calculated position
    if (image != null) {
      // Desired image width set to 24
      double desiredImageWidth = 24;

      // Calculate the image aspect ratio
      double imageAspectRatio =
          image!.width.toDouble() / image!.height.toDouble();

      // Calculate the height based on the desired width and aspect ratio
      double desiredImageHeight = desiredImageWidth / imageAspectRatio;

      // Save the canvas state
      canvas.save();

      // Translate the canvas to the car position
      canvas.translate(carPosition.dx, carPosition.dy);

      // Rotate the canvas based on the car's angle
      canvas.rotate(carAngle);

      // Draw the car image at the car position
      canvas.drawImageRect(
        image!,
        Rect.fromLTWH(0, 0, image!.width.toDouble(), image!.height.toDouble()),
        Rect.fromCenter(
            center: Offset.zero,
            width: desiredImageWidth,
            height: desiredImageHeight),
        Paint(),
      );

      // Restore the canvas state
      canvas.restore();
    }

// Draw the image at the center of the circle
    if (centerImage != null) {
      // Desired image width set to 50
      double desiredImageWidth = 50;

      // Calculate the image aspect ratio
      double centerImageAspectRatio =
          centerImage!.width.toDouble() / centerImage!.height.toDouble();

      // Calculate the height based on the desired width and aspect ratio
      double desiredImageHeight = desiredImageWidth / centerImageAspectRatio;

      // Calculate the rectangle where the image should be drawn
      Rect imageRect = Rect.fromCenter(
        center: center,
        width: desiredImageWidth,
        height: desiredImageHeight,
      );

      // Draw the image at the center of the circle
      canvas.drawImageRect(
        centerImage!,
        Rect.fromLTWH(0, 0, centerImage!.width.toDouble(),
            centerImage!.height.toDouble()),
        imageRect,
        Paint(),
      );
    }
  }

  @override
  bool shouldRepaint(CircleBorderPainter oldDelegate) {
    return oldDelegate.carProgress != carProgress ||
        oldDelegate.borderWidth != borderWidth ||
        oldDelegate.image != image;
  }
}

class RoadPainter extends CustomPainter {
  final double borderWidth;

  RoadPainter(this.borderWidth);

  @override
  void paint(Canvas canvas, Size size) {
    // Paint for the base road
    final Paint roadPaint = Paint()
      ..color = Colors.grey // Grey color for the road
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth;

    // Calculate the radius and center of the canvas
    final double radius = size.width / 2;
    final Offset center = size.center(Offset.zero);

    // Draw the base circle (road) with specified border width
    canvas.drawCircle(center, radius, roadPaint);

    DashedCirclePainter dashedCirclePainter = DashedCirclePainter(
      strokeWidth: 1.0,
      color: Colors.white,
      dashPattern: [10.0, 5.0], // Dash length and gap length
    );
    dashedCirclePainter.paint(canvas, size, center, radius);
  }

  @override
  bool shouldRepaint(RoadPainter oldDelegate) {
    return oldDelegate.borderWidth != borderWidth;
  }
}

class DashedCirclePainter {
  final double strokeWidth;
  final Color color;
  final List<double> dashPattern;

  DashedCirclePainter({
    required this.strokeWidth,
    required this.color,
    required this.dashPattern,
  });

  void paint(Canvas canvas, Size size, Offset center, double radius) {
    // Paint for the red dashed circle
    final Paint dashedCirclePaint = Paint()
      ..color = color // Color for the dashed circle
      ..style = PaintingStyle.stroke
      ..strokeWidth = strokeWidth;

    // Create a Path for the red dashed circle
    Path dashedCirclePath = Path();
    dashedCirclePath.addOval(Rect.fromCircle(center: center, radius: radius));

    // Draw the dashed red circle using dashPath function
    canvas.drawPath(
      pd.dashPath(
        dashedCirclePath,
        dashArray: pd.CircularIntervalList<double>(dashPattern),
      ),
      dashedCirclePaint,
    );
  }
}

Надеюсь, поможет...

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