在 Flutter 中按主题调整文本大小

Resizing Text by Theme in Flutter

我正在尝试使用 GestureDetector 来允许用户通过捏合来更改字体大小:

class _PinchToScaleFontState extends State<PinchToScaleFont> {
  double _baseFontScale = 1;
  double _fontScale = 1;
  ThemeData _themeData;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Theme(
        data: _themeData, 
        child: widget.child       // The desired outcome is that all Text is resized
      ),
      onScaleStart: (ScaleStartDetails scaleStartDetails) {
        _baseFontScale = _fontScale;
      },
      onScaleUpdate: (ScaleUpdateDetails scaleUpdateDetails) {
        // don't update the UI if the scale didn't change
        if (scaleUpdateDetails.scale == 1.0) {
          return;
        }
        setState(() {
          double fontScale = (_baseFontScale * scaleUpdateDetails.scale).clamp(0.5, 5.0);
          _updateFontScale(fontScale);
          SharedPreferences.getInstance().then((prefs) => prefs.setDouble('fontScale', fontScale));
        });
      },
    );
  }

我可以获得以下代码来调整 TextField 的比例,但它不会调整任何 Text 小部件的大小。

  _updateFontScale(double fontScale) {
    setState(() {
      _fontScale = fontScale;
      ThemeData theme = Theme.of(context);

      /// This doesn't seem to work at all
      // _themeData = theme.copyWith(textTheme: theme.textTheme.merge(TextTheme(bodyText2: TextStyle(fontSize: 14 * fontScale))));

      /// This works for `TextField` but not `Text`
      _themeData = theme.copyWith(textTheme: theme.textTheme.apply(fontSizeFactor: fontScale)); // merge(TextTheme()));
    });
    // }
  }

很奇怪。在下面的代码中,我 可以 使用保存的 fontScale 在下次加载时为整个应用程序初始化字体大小,但为什么上面的代码不会,它似乎正在访问相同的主题 属性 给出相同的结果?

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  double savedFontScale = (await SharedPreferences.getInstance()).getDouble('fontScale') ?? 1.0;

  runApp(MyApp(savedFontScale));
}

class MyApp extends StatelessWidget {
  final double fontScale;

  MyApp(this.fontScale);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: APP_NAME,
      theme: ThemeData(
        textTheme: TextTheme(
          /// This works for all `Text` widgets - but you've got to restart the app
          bodyText2: TextStyle(fontSize: 14 * fontScale),
      ...
      home: 
         ...
         PinchToScaleFont(
             ...
             TextField('This _will_ resize '),
             Text('This will not resize, but it should '),

您可以复制粘贴 运行 下面的完整代码
因为 textScaleFactor of Text 引用 MediaQueryData.textScaleFactor
Text.dart https://github.com/flutter/flutter/blob/97295dc9a885c995cda99ba9cee421d3ab1a8e2d/packages/flutter/lib/src/widgets/text.dart#L479

的源代码
  /// The value given to the constructor as textScaleFactor. If null, will
  /// use the [MediaQueryData.textScaleFactor] obtained from the ambient
  /// [MediaQuery], or 1.0 if there is no [MediaQuery] in scope.
  final double? textScaleFactor;

您可以将 widget.child 换成 MediaQuery
并设置 mediaQueryData.copyWith(textScaleFactor: fontScale)
代码片段

  MediaQueryData _mediaQueryData;

  _updateFontScale(double fontScale) {
    setState(() {
      _fontScale = fontScale;
      ThemeData theme = Theme.of(context);
      MediaQueryData mediaQueryData = MediaQuery.of(context);
      ...

      _mediaQueryData = mediaQueryData.copyWith(textScaleFactor: fontScale);
    });
  ...   

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Theme(
          data: _themeData,
          child: MediaQuery(data: _mediaQueryData, child: widget.child)          
          ),

工作演示

完整代码

import 'package:flutter/material.dart';

class PinchToScaleFont extends StatefulWidget {
  final Widget child;

  const PinchToScaleFont({Key key, this.child}) : super(key: key);
  @override
  _PinchToScaleFontState createState() => _PinchToScaleFontState();
}

class _PinchToScaleFontState extends State<PinchToScaleFont> {
  double _baseFontScale = 1;
  double _fontScale = 1;
  ThemeData _themeData;
  MediaQueryData _mediaQueryData;

  _updateFontScale(double fontScale) {
    setState(() {
      _fontScale = fontScale;
      ThemeData theme = Theme.of(context);
      MediaQueryData mediaQueryData = MediaQuery.of(context);

      /// This doesn't seem to work at all
      // _themeData = theme.copyWith(textTheme: theme.textTheme.merge(TextTheme(bodyText2: TextStyle(fontSize: 14 * fontScale))));

      /// This works for `TextField` but not `Text`
      _themeData = theme.copyWith(
          textTheme: theme.textTheme
              .apply(fontSizeFactor: fontScale)); // merge(TextTheme()));

      _mediaQueryData = mediaQueryData.copyWith(textScaleFactor: fontScale);
    });
    // }
  }

  @override
  void didChangeDependencies() {
    _themeData = Theme.of(context);
    _mediaQueryData = MediaQuery.of(context);
    super.didChangeDependencies();
  }

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Theme(
          data: _themeData,
          child: MediaQuery(data: _mediaQueryData, child: widget.child)
          // The desired outcome is that all Text is resized
          ),
      onScaleStart: (ScaleStartDetails scaleStartDetails) {
        _baseFontScale = _fontScale;
      },
      onScaleUpdate: (ScaleUpdateDetails scaleUpdateDetails) {
        // don't update the UI if the scale didn't change
        if (scaleUpdateDetails.scale == 1.0) {
          return;
        }
        setState(() {
          double fontScale =
              (_baseFontScale * scaleUpdateDetails.scale).clamp(0.5, 5.0);
          _updateFontScale(fontScale);
          //SharedPreferences.getInstance().then((prefs) => prefs.setDouble('fontScale', fontScale));
        });
      },
    );
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            PinchToScaleFont(
                child: Column(
              children: [
                TextField(),
                Text('This will not resize, but it should '),
              ],
            )),
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}