Dynamically adjust text size in buttons inside Row

2.7k Views Asked by At

I am using localization to support multiple languages in my app. This results in having text in buttons with different length. So I need to have it being responsive.

I have two buttons in a Row(). I want to adjust the textsize inside these buttons so they never produce any overflow. Currently it looks like this in some languages:

enter image description here

I tried using auto_size_text with no success.

This is my code for the dialog:

return Dialog(
    backgroundColor: Colors.transparent,
    elevation: 0,
    child: InkWell(
        onTap: () {
          Navigator.of(context).pop();
        },
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Container(
                  width: kIsWeb ? 40.w : 100.w,
                  color: Theme.of(context).dialogBackgroundColor,
                  padding: EdgeInsets.all(15.sp),
                  child: Column(children: <Widget>[
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        OutlinedButton(
                          style: OutlinedButton.styleFrom(
                              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
                              side: BorderSide(width: 2, color: Theme.of(context).primaryColor),
                              primary: Colors.black54),
                          onPressed: () {
                            Navigator.of(context).pop();
                          },
                          child: Text(AppLocalizations.of(context)!.joinGameDialogCancelButton,
                              style: TextStyle(fontSize: kIsWeb ? 4.sp : 12.sp)),
                        ),
                        ElevatedButton(
                          style: TextButton.styleFrom(
                              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
                              backgroundColor: Theme.of(context).primaryColor,
                              primary: Colors.white),
                          onPressed: () async {
                            if (formKey.currentState!.validate()) {
                              Navigator.of(context).pop();
                              widget.onFinished(nameController.text.trim());
                            }
                          },
                          child: AutoSizeText(
                              AppLocalizations.of(context)!.joinGameDialogJoinButton,
                            style: TextStyle(fontSize: kIsWeb ? 4.sp : 14.sp),
                            overflow: TextOverflow.clip,
                            stepGranularity: 1,
                            maxLines: 1,
                          )
                        ),
                      ],
                    ),
                    Padding(padding: EdgeInsets.only(top: 15.sp)),
                    Text("some eula text"),
                  ]))
            ],
          ),
        )));
6

There are 6 best solutions below

3
On

Ok, here's what I used: I do not have a mobile device to test it, so I used Windows (but I guess this should not be a problem). In this way, the text gets cut off instead of getting an overflow when it can't get smaller:

         return Dialog(
          backgroundColor: Colors.transparent,
          elevation: 0,
          child: InkWell(
              onTap: () {
                Navigator.of(context).pop();
              },
              child: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Container(
                        //width: 100,
                        color: Theme.of(context).dialogBackgroundColor,
                        padding: const EdgeInsets.all(15),
                        child: Column(children: <Widget>[
                          Row(
                            mainAxisAlignment: MainAxisAlignment.spaceBetween,
                            children: [
                              OutlinedButton(
                                style: OutlinedButton.styleFrom(
                                    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
                                    side: BorderSide(width: 2, color: Theme.of(context).primaryColor),
                                    primary: Colors.black54),
                                onPressed: () {
                                  Navigator.of(context).pop();
                                },
                                child: const Text("weerwerewrweee", style: TextStyle(fontSize: 12)),
                              ),
                              Expanded(
                                child: ElevatedButton(
                                    style: TextButton.styleFrom(
                                        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
                                        backgroundColor: Theme.of(context).primaryColor,
                                        primary: Colors.white),
                                    onPressed: () async {
                                      // if (formKey.currentState!.validate()) {
                                      //   Navigator.of(context).pop();
                                      //   widget.onFinished(nameController.text.trim());
                                      // }
                                    },
                                    child: const AutoSizeText(
                                      "AppLocalizations.of(context)!.joinGameDialogJoinButton,",
                                      style: TextStyle(fontSize: 14),
                                      overflow: TextOverflow.clip,
                                      stepGranularity: 1,
                                      minFontSize: 1,
                                      maxLines: 1,
                                    )),
                              ),
                            ],
                          ),
                          const Padding(padding: EdgeInsets.only(top: 15)),
                          const Text("some eula text"),
                        ]))
                  ],
                ),
              )));

The difference with the original code should be only these:

  • I removed the container width because on Windows it was really too small to test
  • I wrapped the ElevatedButton in an Expanded following some auto_size_text suggestion
  • I changed the minFontSize to 1 (obviously too low, but useful for testing)
  • I put some random texts in the buttons, leaving the second one very long
  • I removed the onPressed argument just for testing

This is what I got:

  • big window enter image description here

  • small window enter image description here

The minimum font size must be adjusted, but I think that there's no way to have a readable text AND having maxLines: 1. You probably must choose one of them, or settle for a very small text.

EDIT: here's how it looks with maxLines: 2: enter image description here

EDIT 2: The trick using an empty Expanded to keep the button separated and avoiding a full-width second button:

[...]
const Expanded(child: Text('')),
Expanded(
   child: ElevatedButton(
   [...]

Result, with short text in the second button:

enter image description here

1
On

Please look into the answer i have added spacer() for in between space

Dialog(
                    backgroundColor: Colors.transparent,
                    elevation: 0,
                    child: InkWell(
                        onTap: () {
                          Navigator.of(context).pop();
                        },
                        child: Center(
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: <Widget>[
                              Container(
                                //width: 100,
                                  color: Theme.of(context).dialogBackgroundColor,
                                  padding: const EdgeInsets.all(15),
                                  child: Column(children: <Widget>[
                                    Row(
                                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                                      children: [
                                        OutlinedButton(
                                          style: OutlinedButton.styleFrom(
                                              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
                                              side: BorderSide(width: 2, color: Theme.of(context).primaryColor),
                                              primary: Colors.black54),
                                          onPressed: () {
                                            Navigator.of(context).pop();
                                          },
                                          child: const Text("weerwerewrweee", style: TextStyle(fontSize: 12)),
                                        ),
                                        Spacer(),
                                        Expanded(
                                          flex: 3,
                                          child: ElevatedButton(
                                              style: TextButton.styleFrom(
                                                  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
                                                  backgroundColor: Theme.of(context).primaryColor,
                                                  primary: Colors.white),
                                              onPressed: () async {
                                                // if (formKey.currentState!.validate()) {
                                                //   Navigator.of(context).pop();
                                                //   widget.onFinished(nameController.text.trim());
                                                // }
                                              },
                                              child: const Text(
                                                "TestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTest",
                                                style: TextStyle(fontSize: 14),
                                                overflow: TextOverflow.clip,

                                              )),
                                        ),
                                      ],
                                    ),
                                    const Padding(padding: EdgeInsets.only(top: 15)),
                                    const Text("some eula text"),
                                  ]))
                            ],
                          ),
                        )))
0
On

You can use TextPainter combined without layoutBuilder to determine if the text has overflowed or not, then dynamically resize the text to fit.

Wrapping in a layoutBuilder will determine how much space is available. The textPainter will simulate the rendering of the text and tell you if it has overflowed.

You can save your text size in a variable within the widget state, then decrement the textSize and call setState until the text fits.

See this similar question: How to check if Flutter Text widget was overflowed

0
On

You can use FittedBox Widget

 FittedBox(
      fit: BoxFit.scaleDown,
      child: Text(
        "Your Text Here",
        maxLines: 1,
      ),
    ),
0
On

No need of using any dependency


Use custom class AdaptableText in your project.

adaptable_text.dart

import 'package:flutter/cupertino.dart';

class AdaptableText extends StatelessWidget {
  final String text;
  final TextStyle? style;
  final TextAlign textAlign;
  final TextDirection textDirection;
  final double minimumFontScale;
  final TextOverflow textOverflow;
  const AdaptableText(this.text,
      {this.style,
        this.textAlign = TextAlign.left,
        this.textDirection = TextDirection.ltr,
        this.minimumFontScale = 0.5,
        this.textOverflow = TextOverflow.ellipsis,
        Key? key})
      : super(key: key);

  @override
  Widget build(BuildContext context) {

    TextPainter _painter = TextPainter(
        text: TextSpan(text: this.text, style: this.style),
        textAlign: this.textAlign,
        textScaleFactor: 1,
        maxLines: 100,
        textDirection: this.textDirection);

    return LayoutBuilder(
      builder: (context, constraints) {
        _painter.layout(maxWidth: constraints.maxWidth);
        double textScaleFactor = 1;

        if (_painter.height > constraints.maxHeight) { //
          print('${_painter.size}');
          _painter.textScaleFactor  = minimumFontScale;
          _painter.layout(maxWidth: constraints.maxWidth);
          print('${_painter.size}');

          if (_painter.height > constraints.maxHeight) { //
            //even minimum does not fit render it with minimum size
            print("Using minimum set font");
            textScaleFactor = minimumFontScale;
          } else if (minimumFontScale < 1) {
            //binary search for valid Scale factor
            int h = 100;
            int l =   (minimumFontScale * 100).toInt();
            while (h > l) {
              int mid = (l + (h - l) / 2).toInt();
              double newScale = mid.toDouble()/100.0;
              _painter.textScaleFactor  = newScale;
              _painter.layout(maxWidth: constraints.maxWidth);

              if (_painter.height > constraints.maxHeight) { //
                h = mid - 1;
              } else {
                l = mid + 1;
              }
              if  (h <= l) {
                print('${_painter.size}');
                textScaleFactor = newScale - 0.01;
                _painter.textScaleFactor  = newScale;
                _painter.layout(maxWidth: constraints.maxWidth);
                break;
              }


            }
          }
        }

        return Text(
          this.text,
          style: this.style,
          textAlign: this.textAlign,
          textDirection: this.textDirection,
          textScaleFactor: textScaleFactor,
          maxLines: 100,
          overflow: textOverflow,
        );
      },
    );
  }
}

Now use this class

 Container(
        width: 250,
        height: 20,
        color: Colors.green,
        child: AdaptableText(mediumSizeText, style: const TextStyle()),
      ),

Here i am also showing the difference between normal text, text inside size box and adaptive text

return Scaffold(
  appBar: AppBar(
    title: const Text('ExpandableText'),
  ),
  body: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      const Text('Normal Text'),
      Text(mediumSizeText),
      const SizedBox(
        height: 20,
      ),
      const Text('Container(250*20) Normal Text:'),
      Container(
        width: 250,
        height: 20,
        color: Colors.green,
        child: Text(
          mediumSizeText,
          overflow: TextOverflow.ellipsis,
        ),
      ),
      const SizedBox(
        height: 20,
      ),
      const Text('Container(250*20) => sizebox => Text:'),
      Container(
        width: 250,
        height: 20,
        color: Colors.green,
        child: FittedBox(
          fit: BoxFit.fitWidth,
          child: Text(
            mediumSizeText,
            maxLines: 100,
          ),
        ),
      ),
      const Text('Container(250*20) => AdaptableText => Text:'),
      Container(
        width: 250,
        height: 20,
        color: Colors.green,
        child: AdaptableText(mediumSizeText, style: const TextStyle()),
      ),
    ],
  ),
);

Here is the result:

enter image description here

0
On

SizedBox is used for this condition only. You can use sizedbox to size any of the Widget. In your case, Try with this code -

class CustomButton extends StatelessWidget {
  CustomButton();
  @override
  Widget build(BuildContext context) {
    return Dialog(
        backgroundColor: Colors.transparent,
        elevation: 0,
        child: InkWell(
            onTap: () {
              Navigator.of(context).pop();
            },
            child: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Container(
                      width: 200,
                      color: Theme.of(context).dialogBackgroundColor,
                      padding: EdgeInsets.all(5),
                      child: Column(children: <Widget>[
                        Row(
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: [
                            SizedBox(
                              width: 80,
                              child: OutlinedButton(
                                style: OutlinedButton.styleFrom(
                                    shape: RoundedRectangleBorder(
                                        borderRadius:
                                            BorderRadius.circular(20.0)),
                                    side: BorderSide(
                                        width: 2,
                                        color: Theme.of(context).primaryColor),
                                    primary: Colors.black54),
                                onPressed: () {
                                  Navigator.of(context).pop();
                                },
                                child: Text("Raushan is flutter developer",
                                    style: TextStyle(fontSize: 12)),
                              ),
                            ),
                            SizedBox(
                              width: 80,
                              child: ElevatedButton(
                                style: TextButton.styleFrom(
                                    shape: RoundedRectangleBorder(
                                        borderRadius:
                                            BorderRadius.circular(20.0)),
                                    backgroundColor:
                                        Theme.of(context).primaryColor,
                                    primary: Colors.white),
                                onPressed: () async {},
                                child: Text("Raushan is flutter developer",
                                    style: TextStyle(fontSize: 12)),
                              ),
                            )
                          ],
                        ),
                        Padding(padding: EdgeInsets.only(top: 15)),
                        Text("some eula text"),
                      ]))
                ],
              ),
            )));
  }
}

I think this will fix your issue.