Flutter - круглый вырез drawAtlas

Я пытаюсь реализовать круговой вырез изображения и использую для этого drawAtlas . Вот моя реализация до сих пор:

      canvas.drawAtlas(
  image!,
  [
    /* Identity transform */
    RSTransform.fromComponents(
      rotation: 0.0,
      scale: 1,
      anchorX: 0,
      anchorY: 0,
      translateX: 0,
      translateY: 0,
    )
  ],
  [
    Rect.fromCircle(
      center: Offset(size.width / 2, size.height / 2),
      radius: 200,
    ),
  ],
  [],
  null,
  null,
  Paint(),
);

Пока он работает, он рисует прямоугольное изображение. Я хочу нарисовать круглый вырез того же изображения с помощью некоторых strokeWidth. Можно ли это сделать с помощью DrawAtlas?

1 ответ

Отличный вопрос! Хотя в вашем коде вы звоните Rect.fromCircleчтобы указать область отсечения, но это все еще прямоугольник (вы строите прямоугольник из круга). Этот метод поддерживает только вырезание прямоугольных областей из большего изображения, как вы, вероятно, уже выяснили.

Ключевым моментом здесь является использование для уменьшения «разрешенной области рисования». Это похоже на добавление поля выбора в Photoshop. После этого все, что находится за пределами поля выбора, будет игнорироваться. Другими словами, позвонив canvas.clipRRect, мы больше не сможем рисовать что-либо за пределами обрезанной области.

Предположительно, после рисования круглого атласа вы все еще можете захотеть рисовать на холсте другие вещи. И для этих других вещей вы, вероятно, не захотите ограничиваться обрезанным кругом. Чтобы решить эту проблему, мы можем использовать canvas.saveLayerчтобы сохранить состояние холста до того, как произойдет отсечение, затем выполните круговое отсечение, затем нарисуйте альты, затем, наконец, вызовите canvas.restoreдля восстановления ранее сохраненного состояния. По сути, это очистит отсечение, что позволит нам рисовать больше вещей в любом месте на холсте, если это необходимо.

Решение:

Короче говоря, метод рисования может выглядеть примерно так:

        void paint(Canvas canvas, Size size) {
    // Save the canvas state so we can clip it and then restore it later
    canvas.saveLayer(Rect.largest, Paint());
    // Clip the canvas, so we are only allowed to draw inside the circle
    canvas.clipRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(0, 0, 100, 100),
        Radius.circular(50),
      ),
    );
    // Draw the atlas (call your method)
    _drawAtlas(canvas, size);
    // Restore the canvas to its original state, so further drawings are not clipped
    canvas.restore();
  }

Демо:

В демоверсии, когда вы нажимаете кнопку, приложение делает снимок экрана с логотипом Flutter внутри серого градиентного контейнера и использует его в качестве ui.imageданные. Тогда он сделает drawAtlasчтобы нарисовать круглую часть изображения на холсте.

Полный демонстрационный код (вставьте все в main.dartбежать):

      import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

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

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

class _MyHomePageState extends State<MyHomePage> {
  final _globalKey = GlobalKey();

  ui.Image? _image;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Column(
          children: [
            RepaintBoundary(
              key: _globalKey,
              child: Container(
                width: 300,
                height: 300,
                decoration: BoxDecoration(
                  gradient: RadialGradient(
                    colors: [Colors.white, Colors.grey],
                  ),
                ),
                child: FlutterLogo(),
              ),
            ),
            ElevatedButton(
              child: Text('Press Me'),
              onPressed: () async {
                final render = (_globalKey.currentContext!.findRenderObject()
                    as RenderRepaintBoundary);
                final image = await render.toImage();
                setState(() {
                  _image = image;
                });
              },
            ),
            if (_image != null)
              CustomPaint(
                size: Size(300, 300),
                painter: MyPainter(_image!),
              ),
          ],
        ),
      ),
    );
  }
}

class MyPainter extends CustomPainter {
  final ui.Image image;

  MyPainter(this.image);

  @override
  void paint(Canvas canvas, Size size) {
    // Save the canvas state so we can clip it and then restore it later
    canvas.saveLayer(Rect.largest, Paint());
    // Clip the canvas, so we are only allowed to draw inside the circle
    canvas.clipRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(0, 0, 100, 100),
        Radius.circular(50),
      ),
    );
    // Draw the atlas (call your method)
    _drawAtlas(canvas, size);
    // Restore the canvas to its original state, so further drawings are not clipped
    canvas.restore();
  }

  _drawAtlas(Canvas canvas, Size size) {
    canvas.drawAtlas(
      image,
      [
        /* Identity transform */
        RSTransform.fromComponents(
          rotation: 0.0,
          scale: 1,
          anchorX: 0,
          anchorY: 0,
          translateX: 0,
          translateY: 0,
        )
      ],
      [
        Rect.fromCircle(
          center: Offset(size.width / 2, size.height / 2),
          radius: 50,
        ),
      ],
      [],
      null,
      null,
      Paint(),
    );
  }

  @override
  bool shouldRepaint(oldDelegate) => true;
}
Другие вопросы по тегам