Search code examples
fluttertextgradient

Flutter 2: Gradient Text with TextPainter breaking in Column


I am encouterring problem with my personal GradientText which is made to do not use ShaderMask.

Here the device I use:

enter image description here

I call it like that:

Column(
  children: [
    GradientText(
      'Party Lopes',
      const LinearGradient(
        colors: <Color>[
          Color.fromARGB(255, 227, 82, 0),
          Color.fromARGB(255, 244, 176, 0)
        ],
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
      ),
      style: TextStyle(
          fontWeight: FontWeight.w500
      ),
    ),
  ],
)

And here the code of my Widget:

import 'package:flutter/material.dart';

class GradientText extends StatelessWidget {
  final String text;
  final TextStyle style;
  final Gradient? gradient;
  final int? maxLine;

  const GradientText(
    this.text,
    {
      @required this.gradient,
      this.style = const TextStyle(),
      this.maxLine = 1,
      Key? key,
    }
    ) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final TextPainter _painter = TextPainter(
      maxLines: maxLine,
      textDirection: TextDirection.rtl,
      text: TextSpan(
        text: text,
        style: style
      ),
    );
    _painter.layout();

    print(_painter.size);

    return CustomPaint(
      size: _painter.size,
      painter: _GradientTextPainter(
        text: text,
        style: style,
        gradient: gradient,
        maxLine: maxLine
      ),
    );
  }
}

class _GradientTextPainter extends CustomPainter {
  final Gradient? gradient;
  final String? text;
  final TextStyle? style;
  final int? maxLine;

  _GradientTextPainter({
    Listenable? repaint,
    @required this.text,
    @required this.style,
    @required this.gradient,
    @required this.maxLine,
  }) : super(repaint: repaint);

  @override
  void paint(Canvas canvas, Size size) {
    final Paint _gradientShaderPaint = Paint()
      ..shader = gradient != null ?
      gradient!.createShader(
        Offset.zero & size
      ) :
      null;

    final TextPainter _textPainter = TextPainter(
        maxLines: maxLine,
        textDirection: TextDirection.ltr,
        text: TextSpan(
          text: text!,
          style: TextStyle(
            foreground: _gradientShaderPaint,
            fontSize: style!.fontSize,
            fontWeight: style!.fontWeight,
            height: style!.height,
            decoration: style!.decoration,
            decorationColor: style!.decorationColor,
            decorationStyle: style!.decorationStyle,
            fontStyle: style!.fontStyle,
            letterSpacing: style!.letterSpacing,
            fontFamily: style!.fontFamily,
            locale: style!.locale,
            textBaseline: style!.textBaseline,
            wordSpacing: style!.wordSpacing,
          ),
        )
    );
    _textPainter.layout(
      minWidth: 0,
      maxWidth: size.width,
    );
    _textPainter.paint(
      canvas,
      Offset(
        (size.width - _textPainter.width) / 2,
        (size.height - _textPainter.height) / 2
      )
    );
  }

  @override
  bool shouldRepaint(_GradientTextPainter oldDelegate) {
    return gradient != oldDelegate.gradient || text != oldDelegate.text ||
    style != oldDelegate.style;
  }
}

And here it gives this result :

enter image description here

Whereas it should give this result:

enter image description here

So I don't understand why it doesn't display the entire text, and I don't understand why when I pass 'Party Lo' as a text parameter it displays it.

enter image description here

And also I don't understand why when I change the maxLines to 2 for example it place it under my first line whereas there is so much space already available

enter image description here

Can somebody please provide help fixing these problems ?

Here the result I am trying to have:

enter image description here


Solution

  • i simplified your _GradientTextPainter a little bit and also added optional alignment property to GradientText - if incoming size constraints are not bounded (either width or height is not set) then it takes the minimum intrinsic size to draw itself, otherwise alignment is used to align GradientText within sized parent widget:

    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    
    class GradientText extends StatelessWidget {
      final String text;
      final TextStyle style;
      final Gradient gradient;
      final int maxLines;
      final Alignment alignment;
      final String ellipsis;
    
      const GradientText(
        this.text,
        this.gradient,
        {
          this.style = const TextStyle(),
          this.maxLines = 1,
          this.alignment = Alignment.centerLeft,
          this.ellipsis = '\u2026',
          Key? key,
        }
      ) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return LayoutBuilder(
          builder: (context, constraints) {
            final TextPainter textPainter = TextPainter(
              maxLines: maxLines,
              textDirection: TextDirection.ltr,
              text: TextSpan(
                text: text,
                style: style,
              ),
              ellipsis: ellipsis,
            );
            textPainter.layout(
              maxWidth: constraints.maxWidth,
            );
            final bounded = constraints.hasBoundedHeight &&
                constraints.hasBoundedWidth;
            final size = bounded? constraints.biggest : textPainter.size;
            // print('constraints.biggest: ${constraints.biggest},
            // textPainter.size: ${textPainter.size}');
            // print('incoming constraints are ${bounded? "" : "NOT "}bounded,
            // using ${bounded? "constraint\'s" : "text"} size: $size');
    
            return CustomPaint(
              painter: _GradientTextPainter(
                text: text,
                style: style,
                gradient: gradient,
                textPainter: textPainter,
                alignment: alignment,
              ),
              size: size,
            );
          }
        );
      }
    }
    
    class _GradientTextPainter extends CustomPainter {
      final Gradient gradient;
      final String text;
      final TextStyle style;
      final TextPainter textPainter;
      final Alignment alignment;
    
      _GradientTextPainter({
        Listenable? repaint,
        required this.text,
        required this.style,
        required this.gradient,
        required this.textPainter,
        required this.alignment,
      }) : super(repaint: repaint);
    
      @override
      void paint(Canvas canvas, Size size) {
        final textSpanRect = alignment.inscribe(
          textPainter.size,
          Offset.zero & size
        );
        // print('=== $size * $alignment => $textSpanRect');
    
        if (debugPaintSizeEnabled)
          debugPaintPadding(canvas, textSpanRect, textSpanRect.deflate(2));
    
        textPainter.text = TextSpan(
          text: text,
          style: style.copyWith(
            foreground: Paint()..shader = gradient.createShader(textSpanRect),
          ),
        );
        textPainter.layout(
          minWidth: 0,
          maxWidth: size.width,
        );
        textPainter.paint(canvas, textSpanRect.topLeft);
      }
    
      @override
      bool shouldRepaint(_GradientTextPainter oldDelegate) {
        return gradient != oldDelegate.gradient || text != oldDelegate.text ||
        style != oldDelegate.style;
      }
    }