I'm trying to use a GestureDetector
to allow the user to change the font size by pinching:
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));
});
},
);
}
I can get the following code to adjust the scale of a TextField
, but it won't resize any Text
widgets.
_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()));
});
// }
}
It's strange. In the code below I can use the saved fontScale to initialise the font size for the whole app the next time it's loaded, but why won't the code above, which seems to be accessing the same theme property give the same results?
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 😀'),
You can copy paste run full code below
Because textScaleFactor
of Text
reference MediaQueryData.textScaleFactor
source code of 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;
You can wrap widget.child
with MediaQuery
and set mediaQueryData.copyWith(textScaleFactor: fontScale)
code snippet
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)
),
working demo
full code
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),
),
);
}
}