moonvader
moonvader

Reputation: 21111

How to manage global textScaleFactor in Flutter app properly?

Flutter apps react on system large text making all Text widgets really large. I want to limit textScaleFactor for Text widgets but I want to do it globally not in each Text widget that I'am using.

Now after enabling large text I got exceptions like

flutter: ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ flutter: The following message was thrown during layout: flutter: A RenderFlex overflowed by 14 pixels on the bottom.

What is proper way to do it? One way is to create some wrapper widget instead of using Text, but maybe it can be solved other way?

image 1 image 2

Upvotes: 20

Views: 16108

Answers (5)

Kirill
Kirill

Reputation: 1519

Generally most responses advice you to use MediaQuery.of(context), but that's super unefficiemt . Instead, what you can do is this:

        // Much more efficient way of getting
        // [MediaQueryData] without subscribing for
        // MediaQuery updates to avoid unnecessary rebuilds
        final MediaQueryData data = MediaQueryData.fromView(
          WidgetsBinding.instance.platformDispatcher.views.first,
        ).copyWith(textScaler: TextScaler.linear(value));

        // But we do need to update our layout if orientation changes so do
        // this
        MediaQuery.orientationOf(context);

        return MediaQuery(data: data, child: child);

With this approach you do not subscribe for unnecessary rebuilds of your whole widget tree once something in MediaQuery has changed (for example when keyboard opens, etc).

Also, if you do not care about orientation, you can remove

MediaQuery.orientationOf(context);

call to avoid rebuild during orientation change

Upvotes: 0

jbryanh
jbryanh

Reputation: 2033

Answers have become outdated because MediaQueryData.fromWindow is deprecated. In addition, MediaQuery.of(context).copyWith(textScaleFactor: 1.0), is not always desirable because it forces rebuilds frequently, for instance when a keyboard is displayed.

This solution solves both the deprecation and rebuild issues:

@override
Widget build(BuildContext context) {
  MediaQueryData windowData = MediaQueryData.fromView(View.of(context));
  windowData = windowData.copyWith(
    textScaleFactor: 1.0, //or whatever you want
  );
  return MediaQuery(
    data: windowData,
    child: MaterialApp(
      //...
    ),
  );
}

Upvotes: 4

Er1
Er1

Reputation: 2757

To limit the textScaleFactor for ALL PAGES in your app, you can do this.

Wrap your MaterialApp with a MediaQuery widget with the desired MediaQueryData data which is created from the window.

And set useInheritedMediaQuery to true.

@override
Widget build(BuildContext context) {
  MediaQueryData windowData =
      MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
  windowData = windowData.copyWith(
    textScaleFactor:
        windowData.textScaleFactor > 1.4 ? 1.4 : windowData.textScaleFactor,
  );
  return MediaQuery(
    data: windowData,
    child: MaterialApp(
      useInheritedMediaQuery: true,

      //...
    ),
  );
}

Upvotes: 3

Andrew Nodermann
Andrew Nodermann

Reputation: 690

You can set a limit after which you don't need to scale, as some apple apps do.

MaterialApp(
  builder: (BuildContext context, Widget child) {
    final MediaQueryData data = MediaQuery.of(context);
    return MediaQuery(
      data: data.copyWith(
        textScaleFactor: data.textScaleFactor > 2.0 ? 2.0 : data.textScaleFactor),
        child: child,
       );
    },
  debugShowCheckedModeBanner: false,
  title: 'Flutter app',
)

In addition, I added 2 functions that calculate the size for different cases.

  1. Always returns a fixed text size, for any textScaleFactor:
double getFixedSize(double textSize) {
  return textScaleFactor != 1.0 ? textSize / textScaleFactor : textSize;
}

For example, you can use it for app title text.

  1. Returns scaled textSize until specific scale is not reached, which is set by the second maxScaleFactor parameter:
double getScaledOrMaxSize(double textSize, double maxScaleFactor) {
 return textScaleFactor > maxScaleFactor
   ? textSize * maxScaleFactor / textScaleFactor
   : textSize;
}

This can be used for example for headers that are already large and you don't need to increase them even more.

I hope this helps you make good apps for visually impaired people while still leaving them beautiful.

Upvotes: 11

moonvader
moonvader

Reputation: 21111

This solution was presented during Google I/O'19 (around the 20-minute mark):

MaterialApp(
  builder: (BuildContext context, Widget child){
    final MediaQueryData data = MediaQuery.of(context);
    return MediaQuery(
      data: data.copyWith(
        textScaleFactor: data.textScaleFactor * (_isPassive ? 2 : 1)
      ),
      child: child,
    );
  },

If you wish that everything had a fixed textScaleFactor, you could replace data.textScaleFactor * (_isPassive ? 2 : 1) for a single number. 1.0 would make your design to always follow your fontSizes.

Upvotes: 42

Related Questions