📱 Responsive Design
1. MediaQuery
Phần tiêu đề “1. MediaQuery”Lấy thông tin về screen:
@overrideWidget 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
Phần tiêu đề “Các properties hữu ích”final mediaQuery = MediaQuery.of(context);
mediaQuery.size.width; // Chiều rộngmediaQuery.size.height; // Chiều caomediaQuery.padding.top; // Safe area top (status bar)mediaQuery.padding.bottom; // Safe area bottom (home indicator)mediaQuery.orientation; // Portrait / LandscapemediaQuery.platformBrightness; // Light / Dark modemediaQuery.textScaleFactor; // Text scale2. LayoutBuilder
Phần tiêu đề “2. 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
Phần tiêu đề “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ụngResponsiveBuilder( mobile: MobileLayout(), tablet: TabletLayout(), desktop: DesktopLayout(),)4. OrientationBuilder
Phần tiêu đề “4. OrientationBuilder”OrientationBuilder( builder: (context, orientation) { if (orientation == Orientation.portrait) { return PortraitLayout(); } else { return LandscapeLayout(); } },)5. FractionallySizedBox
Phần tiêu đề “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
Phần tiêu đề “6. AspectRatio”Giữ tỷ lệ cố định:
AspectRatio( aspectRatio: 16 / 9, child: Container(color: Colors.blue),)7. Flexible Wrap
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “Với TextScaleFactor”// Respect user's text size preferencefinal scaleFactor = MediaQuery.of(context).textScaleFactor;final fontSize = 16 * scaleFactor;📝 Tóm tắt
Phần tiêu đề “📝 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 |