Layout: Row & Column
โมเดล layout ของ Flutter ก็คือ flexbox เพียงแต่สะกดด้วยคำต่างกันเท่านั้น Row คือ display: flex; flex-direction: row Column คือ display: flex; flex-direction: column Expanded คือ flex: 1 MainAxisAlignment แมปไปยัง justify-content ส่วน CrossAxisAlignment แมปไปยัง align-items
พอเห็นการแมปนี้แล้ว layout ของ Flutter จะคุ้นเคยขึ้นมาทันที จุดที่ต้องระวังคือ main axis จะสลับ กันระหว่าง Row กับ Column ส่วนใน CSS นั้น justify-content หมายถึง axis ในทิศที่คุณตั้งไว้เสมอ
เปรียบเทียบแบบเคียงข้างกัน
หัวข้อที่มีชื่อว่า “เปรียบเทียบแบบเคียงข้างกัน”/* React / CSS flexbox */
/* Row layout */.row { display: flex; flex-direction: row; justify-content: space-between; /* main axis */ align-items: center; /* cross axis */ gap: 8px;}
/* Column layout */.col { display: flex; flex-direction: column; justify-content: center; align-items: flex-start; gap: 12px;}
/* Flex child that grows */.grow { flex: 1; }
// JSXfunction NavBar() { return ( <div className="row"> <span className="grow">My App</span> <button>Login</button> <button>Sign Up</button> </div> );}// Flutter Row / Column
// Row layoutRow( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, spacing: 8, // Flutter 3.27+ gap shorthand children: [/* ... */],)
// Column layoutColumn( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, spacing: 12, children: [/* ... */],)
// Flex child that grows (flex: 1)Expanded(child: someWidget)
// Widgetclass NavBar extends StatelessWidget { const NavBar({super.key});
@override Widget build(BuildContext context) { return Row( children: [ const Expanded(child: Text('My App', style: TextStyle(fontWeight: FontWeight.bold))), TextButton(onPressed: () {}, child: const Text('Login')), ElevatedButton(onPressed: () {}, child: const Text('Sign Up')), ], ); }}ตัวอย่างที่รันได้
หัวข้อที่มีชื่อว่า “ตัวอย่างที่รันได้”import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Row & Column Demo')),
body: const Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 16,
children: [
_SectionLabel('Row — spaceAround'),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_Chip(label: 'Flutter', color: Colors.blue),
_Chip(label: 'Dart', color: Colors.teal),
_Chip(label: 'Material', color: Colors.purple),
],
),
_SectionLabel('Row with Expanded'),
Row(
spacing: 8,
children: [
Expanded(child: _Chip(label: 'Grows to fill', color: Colors.orange)),
_Chip(label: 'Fixed', color: Colors.red),
],
),
_SectionLabel('Column — center + start'),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 6,
children: [
_Chip(label: 'Item 1', color: Colors.indigo),
_Chip(label: 'Item 2', color: Colors.indigo),
_Chip(label: 'Item 3', color: Colors.indigo),
],
),
],
),
),
),
);
}
}
class _SectionLabel extends StatelessWidget {
final String text;
const _SectionLabel(this.text);
@override
Widget build(BuildContext context) => Text(text,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13,
color: Colors.grey));
}
class _Chip extends StatelessWidget {
final String label;
final Color color;
const _Chip({required this.label, required this.color});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(color: color.withOpacity(0.15),
border: Border.all(color: color),
borderRadius: BorderRadius.circular(20)),
child: Text(label, style: TextStyle(color: color, fontWeight: FontWeight.w600)),
);
}
}