Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cut Out Text Effect In Flutter

How Can I do a text cut into a box container that leaves the content under it seen?

Here's exactly what I mean:

As you can see the immage is seen under the text. How could I do this?

like image 528
Fabrizio Avatar asked Sep 03 '18 14:09

Fabrizio


2 Answers

You have to use CustomPainter, TextPainter, BlendMode and saveLayer:

result

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Playground',
      home: TestPage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: const BoxDecoration(
        image: DecorationImage(
            image: AssetImage('assets/3g.jpg'), fit: BoxFit.cover),
      ),
      child: Center(
        child: CustomPaint(
          painter: CutOutTextPainter(text: 'YOUR NAME'),
        ),
      ),
    );
  }
}

class CutOutTextPainter extends CustomPainter {
  CutOutTextPainter({required this.text}) {
    _textPainter = TextPainter(
      text: TextSpan(
        text: text,
        style: const TextStyle(
          fontSize: 40.0,
          fontWeight: FontWeight.w600,
        ),
      ),
      textDirection: TextDirection.ltr,
    );
    _textPainter.layout();
  }

  final String text;
  late final TextPainter _textPainter;

  @override
  void paint(Canvas canvas, Size size) {
    // Draw the text in the middle of the canvas
    final textOffset =
        size.center(Offset.zero) - _textPainter.size.center(Offset.zero);
    final textRect = textOffset & _textPainter.size;

    // The box surrounding the text should be 10 pixels larger, with 4 pixels corner radius
    final boxRect = RRect.fromRectAndRadius(
        textRect.inflate(10.0), const Radius.circular(4.0));
    final boxPaint = Paint()
      ..color = Colors.white
      ..blendMode = BlendMode.srcOut;

    canvas.saveLayer(boxRect.outerRect, Paint());

    _textPainter.paint(canvas, textOffset);
    canvas.drawRRect(boxRect, boxPaint);

    canvas.restore();
  }

  @override
  bool shouldRepaint(CutOutTextPainter oldDelegate) {
    return text != oldDelegate.text;
  }
}

like image 89
boformer Avatar answered Nov 14 '22 23:11

boformer


You could use a ShaderMask for that, which allows you to apply a shader to a widget, taking a Blend Mode into account. The Blend Mode is what we're interested in, so the Shader will be a simple color:

class Cutout extends StatelessWidget {
  const Cutout({
    Key key,
    @required this.color,
    @required this.child,
  }) : super(key: key);

  final Color color;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return ShaderMask(
      blendMode: BlendMode.srcOut,
      shaderCallback: (bounds) => LinearGradient(colors: [color], stops: [0.0]).createShader(bounds),
      child: child,
    );
  }
}

Here is an example render: Render of the Cutout widget

For your exact example image, the child should be a Text widget and you should also include this in a ClipRRect for the rounded corners (or you can figure out more optimal solutions using BoxDecoration if the performance impact of ClipRRect is an issue)

The advantage of this solution is that it works with any widget as a child and that it's a composable widget that you can pop in your layout.

like image 10
Andrei Tudor Diaconu Avatar answered Nov 14 '22 22:11

Andrei Tudor Diaconu