RTL 布局与方向处理
使用 Directionality 构建布局,使其能够正确镜像显示从右到左的语言。
RTL 布局与方向处理 是 CoddyKit 上的免费 Flutter Mobile Development 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Flutter Mobile Development 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Flutter Mobile Development 课程共包含 4 节课。
本课时的部分内容尚未翻译,以英文显示。
Why RTL Matters
Languages like Arabic, Hebrew, Persian, and Urdu read right-to-left (RTL). A UI built only for left-to-right (LTR) feels broken to those users: back arrows point the wrong way, text aligns to the wrong edge, and rows appear mirrored.
- Goal: the entire layout should mirror when the locale is RTL.
- Flutter handles most of this automatically through a concept called directionality.
- Your job is to write direction-aware code instead of hard-coding left and right.
In this lesson you will learn how Flutter resolves direction and how to keep your widgets mirroring correctly.
The TextDirection Enum
At the core of RTL support is the TextDirection enum from dart:ui. It has exactly two values: ltr and rtl.
- Every text-rendering and direction-aware widget needs a
TextDirectionto lay out. - In a real app it is normally derived from the device locale, but it is just a plain enum you can reason about in pure Dart.
Here is a tiny pure-Dart model that mirrors how Flutter picks a start edge from a direction.
enum TextDirection { ltr, rtl }
String startEdge(TextDirection dir) {
return dir == TextDirection.rtl ? 'right' : 'left';
}
String endEdge(TextDirection dir) {
return dir == TextDirection.rtl ? 'left' : 'right';
}
void main() {
for (final dir in TextDirection.values) {
print('$dir -> start=${startEdge(dir)}, end=${endEdge(dir)}');
}
}The Directionality Widget
In Flutter, the ambient text direction is provided by the Directionality widget. It sits high in the tree (usually inside MaterialApp) and exposes a direction to all descendants.
- Widgets read it with
Directionality.of(context). MaterialAppautomatically wraps your app in aDirectionalitybased on the current locale.- You rarely create one yourself, except in tests or to force a subtree's direction.
Forcing a subtree to RTL is as simple as wrapping it.
Widget buildArabicSection() {
return Directionality(
textDirection: TextDirection.rtl,
child: Row(
children: const [
Icon(Icons.star),
SizedBox(width: 8),
Text('مرحبا'),
],
),
);
}Start and End, Not Left and Right
The single most important rule for RTL-safe layouts: think in start/end, not left/right.
- start = leading edge (left in LTR, right in RTL)
- end = trailing edge (right in LTR, left in RTL)
Flutter gives you direction-aware versions of common APIs that resolve automatically:
EdgeInsetsDirectionalinstead ofEdgeInsetsAlignmentDirectionalinstead ofAlignmentBorderRadiusDirectionalinstead ofBorderRadius
If you use these, your padding and alignment flip automatically when the direction changes.
Widget buildCard() {
return Container(
// 16 on the start edge mirrors to the right in RTL automatically
padding: const EdgeInsetsDirectional.only(
start: 16,
end: 8,
top: 12,
bottom: 12,
),
alignment: AlignmentDirectional.centerStart,
child: const Text('Direction-aware padding'),
);
}Resolving Directional Insets in Pure Dart
To build intuition, here is how a directional inset resolves to physical left/right depending on direction. Flutter does this internally; modeling it in plain Dart makes the rule concrete.
- In LTR:
start -> left,end -> right. - In RTL:
start -> right,end -> left.
Run this to see the same logical insets produce mirrored physical insets.
enum TextDirection { ltr, rtl }
class DirectionalInsets {
final double start;
final double end;
const DirectionalInsets(this.start, this.end);
Map<String, double> resolve(TextDirection dir) {
if (dir == TextDirection.rtl) {
return {'left': end, 'right': start};
}
return {'left': start, 'right': end};
}
}
void main() {
const insets = DirectionalInsets(16, 4);
print('LTR: ${insets.resolve(TextDirection.ltr)}');
print('RTL: ${insets.resolve(TextDirection.rtl)}');
}Rows Mirror Automatically
A plain Row is already direction-aware. Its children are laid out from start to end, so the visual order flips in RTL without any extra code.
- In LTR a Row of [A, B, C] shows A on the left.
- In RTL the same Row shows A on the right.
This is why you should let widgets like Row, ListTile, and AppBar do the mirroring for you instead of manually positioning children.
Below, the leading icon and trailing chevron of a ListTile swap sides automatically.
Widget buildSettingsTile() {
return const ListTile(
leading: Icon(Icons.person), // start edge
title: Text('Profile'),
trailing: Icon(Icons.chevron_right), // end edge
);
}Direction-Aware Icons
Some icons are inherently directional: back arrows, chevrons, and the send arrow should point toward the end of the reading direction.
- Material provides mirrored variants, e.g.
Icons.arrow_backhasIcons.arrow_back_iosand the auto-mirroringIcons.arrow_backbehaves well inBackButton. - For custom directional icons, wrap them so they flip in RTL using
Transform.flipbased on the resolved direction.
Reading the ambient direction lets you decide whether to mirror.
Widget buildSendIcon(BuildContext context) {
final isRtl = Directionality.of(context) == TextDirection.rtl;
return Transform(
alignment: Alignment.center,
transform: isRtl
? Matrix4.rotationY(3.1415926) // flip horizontally
: Matrix4.identity(),
child: const Icon(Icons.send),
);
}Text Alignment Follows Direction
For text, prefer TextAlign.start and TextAlign.end over TextAlign.left and TextAlign.right.
TextAlign.startaligns to the left in LTR and to the right in RTL.- A single mixed-direction string (Arabic with embedded English) is handled by the text engine's bidi algorithm, but the paragraph's base direction comes from the ambient
Directionality.
Using start means your form labels and body text align to the correct edge in every locale.
Widget buildLabel() {
return const Text(
'Email address',
textAlign: TextAlign.start, // mirrors with direction
style: TextStyle(fontSize: 16),
);
}Testing Both Directions
The cleanest way to verify mirroring is to render the same subtree twice under each direction. Wrapping a widget in Directionality overrides the ambient value for that subtree only.
- Great for widget tests and for a debug preview screen.
- You can also flip the whole app by setting
localeto an RTL locale likeLocale('ar').
This helper builds an RTL preview of any child.
Widget rtlPreview(Widget child) {
return Directionality(
textDirection: TextDirection.rtl,
child: child,
);
}
Widget buildPreviewRow(Widget child) {
return Row(
children: [
Expanded(child: child), // ambient direction
Expanded(child: rtlPreview(child)), // forced RTL
],
);
}Common RTL Mistakes
Most RTL bugs come from hard-coded sides. Watch for these:
EdgeInsets.only(left: 16)stays on the left even in RTL. UseEdgeInsetsDirectional.only(start: 16).Alignment.centerLeftdoes not flip. UseAlignmentDirectional.centerStart.Positioned(left: 0)in aStackdoes not mirror. UsePositionedDirectional(start: 0).- Hard-coded
TextAlign.leftfor body text.
Rule of thumb: if an API has a Directional sibling, prefer it.
// Mirrors correctly in RTL:
Widget buildBadge() {
return Stack(
children: const [
Placeholder(),
PositionedDirectional(
top: 4,
start: 4, // right side in RTL
child: Icon(Icons.new_releases),
),
],
);
}Reading Direction in Logic
Sometimes your business logic needs the direction too, for example to choose a swipe gesture or an animation slide offset. Read it once and branch on a clean enum.
- Compute a sign:
+1for LTR,-1for RTL, then multiply offsets. - Keep this logic pure so it is easy to unit test.
This pure-Dart helper shows the pattern you would call with the resolved direction from Directionality.of(context).
enum TextDirection { ltr, rtl }
double slideOffset(TextDirection dir, double distance) {
final sign = dir == TextDirection.rtl ? -1.0 : 1.0;
return sign * distance;
}
void main() {
const distance = 120.0;
print('LTR slide: ${slideOffset(TextDirection.ltr, distance)}');
print('RTL slide: ${slideOffset(TextDirection.rtl, distance)}');
}Quick Check
You need a left padding in LTR that becomes right padding in RTL, automatically.
Recap
You learned how Flutter handles right-to-left layouts:
- TextDirection (ltr/rtl) drives everything; Directionality provides it to the tree, and
Directionality.of(context)reads it. - Think in start/end, not left/right. Prefer
EdgeInsetsDirectional,AlignmentDirectional,BorderRadiusDirectional, andPositionedDirectional. Row,ListTile, and most Material widgets mirror automatically; let them.- Use
TextAlign.startfor text, and mirror inherently directional custom icons. - Verify by wrapping subtrees in a forced
Directionalityor running under an RTL locale likeLocale('ar').
Avoid hard-coded sides and your UI will feel native to RTL users with almost no extra work.
常见问题解答
「RTL 布局与方向处理」课时是免费的吗?
是的 — 「RTL 布局与方向处理」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Flutter Mobile Development 课程的其余内容,请升级到 CoddyKit PRO。 Flutter Mobile Development 课程共包含 4 节课。
「RTL 布局与方向处理」这节课中我会学到什么?
使用 Directionality 构建布局,使其能够正确镜像显示从右到左的语言。 你通过在浏览器中直接运行的动手代码来练习 Flutter Mobile Development,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Flutter Mobile Development 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Flutter Mobile Development 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「RTL 布局与方向处理」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Flutter Mobile Development 课中编写并运行代码吗?
能。每节 Flutter Mobile Development 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。