Flutter getting the dimensions of a string as text

110 Views Asked by At

I have a text widget and I want to place a container which covers the whole word in that text. To achieve that I found a code that gives the dimensions of a string for the specified text style. Using that I created a simple flutter app in which you can press the button to place that container on the next word.

Here is the whole program

import 'package:flutter/material.dart';

const loremIpsum =
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      debugShowCheckedModeBanner: false,
      home: SelectText(),
    );
  }
}

class SelectText extends StatefulWidget {
  SelectText({super.key});
  late List<String> splitted;
  int current = 0;
  double left = 0;
  Size size = const Size(0, 0);

  @override
  State<SelectText> createState() => _SelectTextState();
}

class _SelectTextState extends State<SelectText> {
  @override
  void initState() {
    super.initState();
    widget.splitted = loremIpsum.split(' ').toList();
  }

  @override
  Widget build(BuildContext context) {
    double textScaleFactor = MediaQuery.of(context).textScaleFactor;

    return SafeArea(
      child: Scaffold(
        body: Column(
          children: [
            Stack(
              children: [
                Positioned(
                  left: widget.left,
                  top: 0,
                  child: Container(
                    width: widget.size.width,
                    height: widget.size.height,
                    color: Colors.red,
                  ),
                ),
                const Text(loremIpsum),
              ],
            ),
            const SizedBox(height: 20),
            FilledButton(
              onPressed: () {
                var (pos, size) =
                    getNextPositionAndSize(textScaleFactor: textScaleFactor);

                setState(() {
                  widget.left = pos;
                  widget.size = size;
                });
              },
              child: const Text("FF GO NEXT"),
            ),
          ],
        ),
      ),
    );
  }

  (double, Size) getNextPositionAndSize({required double textScaleFactor}) {
    String text = "";

    for (int i = 0; i <= widget.current; i++) {
      text += " ";
      text += widget.splitted[i];
    }

    double nextPos = getTextSize(
      text: text,
      textStyle: const TextStyle(),
      textScaleFactor: textScaleFactor,
    ).width;

    Size size = getTextSize(
      text: widget.splitted[widget.current + 1],
      textStyle: const TextStyle(),
      textScaleFactor: textScaleFactor,
    );

    setState(() {
      widget.current += 1;
    });

    return (nextPos, size);
  }
}

Size getTextSize({
  required String text,
  required TextStyle textStyle,
  required double textScaleFactor,
}) {
  final size = (TextPainter(
          text: TextSpan(text: text, style: textStyle),
          maxLines: 1,
          textScaleFactor: textScaleFactor,
          textDirection: TextDirection.ltr)
        ..layout())
      .size;

  return size;
}

This code works fine for the iPhone 14 - iOS 17 simulator and macOS Desktop App but when I hand it over to my friend and he runs it on an android device I don't get the desired outcome which means the code doesn't calculate the dimensions right. Could you help me with that?

1

There are 1 best solutions below

0
On

You don't need to calculate the string dimensions of every word for your purpose. Instead, you can use RichText Widget that you can style/manipulate every word separately.

I rewrote the code that is suitable for your purpose below. Regardless of the font type or font size, the following code will always work.

class SelectText extends StatefulWidget {
  SelectText({super.key});

  @override
  State<SelectText> createState() => _SelectTextState();
}

class _SelectTextState extends State<SelectText> {
  late List<String> splitted;
  int current = 0;
  String loremIpsum =
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

  @override
  void initState() {
    super.initState();
    splitted = loremIpsum.split(' ').toList();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: Column(
          children: [
            FilledButton(
              onPressed: () {
                setState(() {
                  current += 1;
                  current = current % splitted.length;
                  print(current);
                });
              },
              child: const Text("FF GO NEXT"),
            ),
            RichText(
              text: TextSpan(
                style: TextStyle(fontSize: 25, color: Colors.black),
                children: List.generate(
                  splitted.length,
                  (index) => TextSpan(
                    text: splitted[index],
                    style: TextStyle(
                      backgroundColor:
                          current == index ? Colors.red : Colors.transparent,
                    ),
                    children: [
                      TextSpan(
                        text: " ",
                        style: TextStyle(
                          backgroundColor: Colors.transparent,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

Hope this helps. Good luck :)