This guide starts with the most straightforward and effective strategies to help you get quick results. As you progress, you'll find more complex solutions that, while less impactful overall, are still valuable for tackling particular scenarios.
Limit possible extent of text scaling
In your MaterialApp, you have the option to define minimum and maximum scale factors, ensuring that all text adjusts within the set parameters. Setting tighter limits helps with maintaining readability and visual appeal with minimal effort. However, the boundaries you choose should be tailored to your audience. For instance, if your app caters to elderly users, consider implementing looser boundaries to better meet their requirements.
MaterialApp(
...
builder: (_, child) => MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: MediaQuery.of(context)
.textScaler
.clamp(minScaleFactor: 0.8, maxScaleFactor: 1.6),
),
child: child!,
),
);
Don’t use fixed height for elements that contain text
Take a look at this piece of code:
//DON'T
SizedBox(
height: 100,
child: Card(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Title", style: TextStyle(fontSize: 30), maxLines: 1),
Text("Subtitle", maxLines: 1),
],
),
),
),
),
What could possibly go wrong?
As you probably anticipated, making the text larger can lead to a SizedBox occupying excessive space.
An improved approach is to determine the item's height according to the content height and padding. Furthermore, you can apply a ConstrainedBox
to establish a minimum height.
ConstrainedBox(
constraints: const BoxConstraints(minHeight: 100),
child: const Card(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Title", style: TextStyle(fontSize: 30), maxLines: 1),
Text("Subtitle", maxLines: 1),
],
),
),
),
),
Consequently, we achieve an identical layout at a 100% scale and a valid layout at 160%.
The same goes for ListViews. If you’re utilizing itemExtent
, think about either computing it with font scale or offering a prototypeItem
. You can find more details in this article.
Let’s move on. Imagine having this layout:
Item A comes from the earlier example. Item B includes some padding and is designed to accommodate larger text scaling effectively. There's ample room at the bottom of the display. So, what could potentially go awry?
Remember to consider phones with smaller screens. Additionally, text length may differ when the language changes.
Make sure the content is scrollable
First, we need to get rid of any overflow so that our users can view all the content. Using a straightforward SingleChildScrollView
will address this problem.
Consider using adaptive values for margins and paddings
This idea might be contentious, but picture yourself as a user who requires bigger fonts. Would you rather deal with plenty of empty space or easily read the text?
To display text, let's base the values on the number of logical pixels. You have the flexibility to set the smallScreenThreshold
to any value that best suits your application's needs.
class Dimens {
static const smallScreenThreshold = 300;
static bool isSmallWidth(BuildContext context) {
return MediaQuery.of(context).size.width /
MediaQuery.textScalerOf(context).scale(1) <
smallScreenThreshold;
}
static double small(BuildContext context) => isSmallWidth(context) ? 4 : 8;
static double medium(BuildContext context) => isSmallWidth(context) ? 8 : 16;
static double large(BuildContext context) => isSmallWidth(context) ? 16 : 32;
}
Keep in mind that to adhere to Human Interface Guidelines and Material Design, these values need to be divisible by 4.
Given these Dimens
, we are able to create a class for insets:
class Insets {
static EdgeInsets small(BuildContext context) =>
EdgeInsets.all(Dimens.small(context));
static EdgeInsets medium(BuildContext context) =>
EdgeInsets.all(Dimens.medium(context));
static EdgeInsets large(BuildContext context) =>
EdgeInsets.all(Dimens.large(context));
}
And in the code we replace it like that:
//padding: const EdgeInsets.all(16),
padding: Insets.medium(context),
//SizedBox(height: 16),
SizedBox(height: Dimens.medium(context)),
Consequently, we've gained a little extra room for displaying the text on the screen:
Limit how large text size can expand for titles
The primary aim of enlarging font sizes is to ensure that content remains legible for individuals with impaired vision. However, certain sections of the app, like titles, may already be easy to read due to their naturally larger font sizes. To manage this effectively, we can restrict how much the text can increase in size. One approach to accomplishing this is by designing a custom widget specifically for titles:.
class TitleText extends StatelessWidget {
final String text;
final TextStyle style;
const TitleText(this.text, {required this.style, super.key});
static const double maxRealFontSize = 30;
@override
Widget build(BuildContext context) {
if (MediaQuery.textScalerOf(context).scale(style.fontSize!) >
maxRealFontSize) {
return Text(
text,
style: style.copyWith(
fontSize: maxRealFontSize / MediaQuery.textScalerOf(context).scale(1),
),
);
}
return Text(text, style: style);
}
}
By doing so, we can gain additional space while maintaining readability. You have the flexibility to adjust the maxRealFontSize
to any value that better fits your application's needs.
Specify maximum amount of lines and text overflow
Keep in mind that while some texts may appear fine on a large screen with normal text scaling, they can end up consuming a lot more vertical space in different conditions. However, it's not always necessary to display the entire content, such as in subtitles. To manage this, simply add a maxLines
value to your Text widget.
Appears great with maxLines
adjusted to 1. The key details remain clear and visible.
Use alternative versions of strings
However, there are times when shortening a string may result in the loss of important information. Furthermore, the order of words can differ across languages. For instance, the first word of a sentence in English might appear at the end of the sentence in a different language. To illustrate this point, let’s examine the following example of internationalization (i18n) strings:
"tasksDone": {
"one": "You have done $completed of $n tasks",
"other": "You have done $completed of $n tasks"
},
"tasksDoneShort": {
"one": "$completed/$n tasks done",
"other": "$completed/$n tasks done"
},
The most significant element is the section displaying numbers. In the abbreviated version, we placed it at the start and condensed the entire string. You can integrate it into your code like this:
Text(
Dimens.isSmallWidth(context)
? t.tasksDoneShort(n: 10, completed: 5)
: t.tasksDone(n: 10, completed: 5),
maxLines: 1,
)
As shown in the screenshot, using a shortened version allows us to display the necessary information more effectively.