Responsive Design trong Flutter
1. MediaQuery
Lấy thông tin về screen:
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final width = size.width;
final height = size.height;
return Text('Screen: ${width}x$height');
}Các properties hữu ích
final mediaQuery = MediaQuery.of(context);
mediaQuery.size.width; // Chiều rộng
mediaQuery.size.height; // Chiều cao
mediaQuery.padding.top; // Safe area top (status bar)
mediaQuery.padding.bottom; // Safe area bottom (home indicator)
mediaQuery.orientation; // Portrait / Landscape
mediaQuery.platformBrightness; // Light / Dark mode
mediaQuery.textScaleFactor; // Text scale2. LayoutBuilder
Responsive dựa trên available space (không phải screen size):
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return WideLayout();
} else {
return NarrowLayout();
}
},
)3. Responsive Breakpoints
class Breakpoints {
static const mobile = 600;
static const tablet = 900;
static const desktop = 1200;
}
class ResponsiveBuilder extends StatelessWidget {
final Widget mobile;
final Widget? tablet;
final Widget? desktop;
const ResponsiveBuilder({
super.key,
required this.mobile,
this.tablet,
this.desktop,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= Breakpoints.desktop) {
return desktop ?? tablet ?? mobile;
}
if (constraints.maxWidth >= Breakpoints.tablet) {
return tablet ?? mobile;
}
return mobile;
},
);
}
}
// Sử dụng
ResponsiveBuilder(
mobile: MobileLayout(),
tablet: TabletLayout(),
desktop: DesktopLayout(),
)4. OrientationBuilder
OrientationBuilder(
builder: (context, orientation) {
if (orientation == Orientation.portrait) {
return PortraitLayout();
} else {
return LandscapeLayout();
}
},
)5. FractionallySizedBox
Kích thước theo % của parent:
FractionallySizedBox(
widthFactor: 0.8, // 80% width
heightFactor: 0.5, // 50% height
child: Container(color: Colors.blue),
)6. AspectRatio
Giữ tỷ lệ cố định:
AspectRatio(
aspectRatio: 16 / 9,
child: Container(color: Colors.blue),
)7. Flexible Wrap
Wrap(
spacing: 8,
runSpacing: 8,
children: items.map((item) {
return SizedBox(
width: _getItemWidth(context),
child: ItemCard(item),
);
}).toList(),
)
double _getItemWidth(BuildContext context) {
final width = MediaQuery.of(context).size.width;
if (width > 900) return (width - 48) / 4; // 4 columns
if (width > 600) return (width - 32) / 2; // 2 columns
return width - 32; // 1 column
}8. Responsive Grid
GridView.builder(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 300, // Tối đa 300px mỗi item
mainAxisSpacing: 16,
crossAxisSpacing: 16,
childAspectRatio: 1.0,
),
itemCount: items.length,
itemBuilder: (context, index) => ItemCard(items[index]),
)9. Responsive Navigation
class ResponsiveScaffold extends StatelessWidget {
final Widget body;
final int selectedIndex;
final Function(int) onIndexChanged;
const ResponsiveScaffold({
super.key,
required this.body,
required this.selectedIndex,
required this.onIndexChanged,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
// Tablet/Desktop: NavigationRail
return Scaffold(
body: Row(
children: [
NavigationRail(
selectedIndex: selectedIndex,
onDestinationSelected: onIndexChanged,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.search),
label: Text('Search'),
),
NavigationRailDestination(
icon: Icon(Icons.person),
label: Text('Profile'),
),
],
),
Expanded(child: body),
],
),
);
} else {
// Mobile: BottomNavigationBar
return Scaffold(
body: body,
bottomNavigationBar: BottomNavigationBar(
currentIndex: selectedIndex,
onTap: onIndexChanged,
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
);
}
},
);
}
}10. Responsive Text
Text(
'Hello',
style: TextStyle(
fontSize: _getResponsiveFontSize(context),
),
)
double _getResponsiveFontSize(BuildContext context) {
final width = MediaQuery.of(context).size.width;
if (width > 1200) return 24;
if (width > 600) return 20;
return 16;
}Với TextScaleFactor
// Respect user's text size preference
final scaleFactor = MediaQuery.of(context).textScaleFactor;
final fontSize = 16 * scaleFactor;📝 Tóm tắt
| Widget/Class | Mục đích |
|---|---|
MediaQuery | Screen size, padding, orientation |
LayoutBuilder | Available constraints |
OrientationBuilder | Portrait / Landscape |
FractionallySizedBox | Size theo % |
AspectRatio | Giữ tỷ lệ |
| Breakpoint | Width |
|---|---|
| Mobile | < 600px |
| Tablet | 600-900px |
| Desktop | > 900px |
Last updated on