สรุป 10 อันดับ Flutter Layout ที่คุณควรรู้ ฉบับรวบรัด

jengener
Goodwin Corporation
7 min readMay 24, 2020

บทความนี้เป็นบทความแรกเกี่ยวกับ Flutter ของทางกู๊ดวิน หลังที่ได้ลองใช้ Flutter มาใน 1 ปีกว่าๆที่ผ่านมา มาถึง ณ วันนี้ จากความที่ใช้ง่ายและพลังของชุมชน (รวมถึงพลังทีมผู้พัฒนาหลักอย่าง Google) ทำให้กระแส Flutter ค่อนข้างฮอตฮิตกันพอสมควร

เอาล่ะ บทความนี้เราตั้งใจจะเขียน Cheat Sheet หรือสรุปทั้งหมดทั้งมวลของ Flutter Layout แบบรวบรัดโดยหลักๆจะไม่พูดเยอะ (เจ็บคอ) แต่จะเน้นการยกตัวอย่างให้เห็นภาพเป็นหลัก เพื่อไว้เป็นทำความเข้าใจและเป็นตัวอย่างให้กับน้องๆภายในทีมและผู้ที่สนใจได้เข้าใจได้ง่ายและค้นหาได้ง่ายยิ่งขึ้น

เริ่มกันเลย!

1. Row and Column

MainAxisAlignment

การจัดในแกนหลัก เช่น ถ้าใช้ Row คือ การจัดในแนวนอน, ถ้าใช้ Column คือ การจัดในแนวตั้ง

Row /* or Column */(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
],
),
Row /* or Column */(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
],
),
Row /* or Column */(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
],
),
Row /* or Column */(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
],
),
Row /* or Column */(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
],
),
Row /* or Column */(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
],
),

CrossAxisAlignment

การจัดในแกนขวาง เช่น ถ้าใช้ Row คือ การจัดในแนวตั้ง, ถ้าใช้ Column คือ การจัดในแนวแนวนอน

Row /* or Column */(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 144),
Icon(Icons.motorcycle, size: 48),
],
),
Row /* or Column */(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 144),
Icon(Icons.motorcycle, size: 48),
],
),
Row /* or Column */(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 144),
Icon(Icons.motorcycle, size: 48),
],
),
Row /* or Column */(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 144),
Icon(Icons.motorcycle, size: 48),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,

children: <Widget>[
Text(
'BASELINE',
style: TextStyle(fontSize: 32),
),
Text('BASELINE'),
],
),

MainAxisSize

ขนาดตามแกนหลัก เช่น ถ้าใช้ Row คือ ขนาดในแนวนอน, ถ้าใช้ Column คือ ขนาดในแนวตั้ง

Row /* or Column */(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
],
),
Row /* or Column */(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
],
),

2. IntrinsicWidth and IntrinsicHeight

Intrinsic แปลว่า แท้จริง, ที่อยู่ภายใน ในกรณีที่ต้องการให้ความกว้าง หรือความสูง นั้น กว้างเท่ากับ widget ที่กว้างที่สุดใน Column หรือสูงเท่ากับ widget ที่สูงที่สุดใน Row เราไม่ต้องยุ่งยากไปใช้วิธีอื่นๆ (เคยใช้ fix ความสูงคงที่ปรากฎว่าเละ 555)

โอเค คุณสมบัติที่ว่ามานั้น เราสามารถใช้ widget class ที่ชื่อว่า IntrinsicWidth หรือ IntrinsicHeight ใช้ยังไง มาดูกัน!

จาก layout แบบนี้

Column(
children: <Widget>[
RaisedButton(
onPressed: () {},
child: Text('สั้นๆ'),
),
RaisedButton(
onPressed: () {},
child: Text('ยาวนิดๆ'),
),
RaisedButton(
onPressed: () {},
child: Text('ยาวนิดมากๆที่สุด'),
),
],
),

หากต้องการให้ปุ่มมีความยาวเท่าๆกับปุ่มที่ยาวที่สุดสามารถใช้ IntrinsicWidth ได้เลย โดยจะได้ผลลัพธ์เป็นดังนี้

IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
RaisedButton(
onPressed: () {},
child: Text('สั้นๆ'),
),
RaisedButton(
onPressed: () {},
child: Text('ยาวนิดๆ'),
),
RaisedButton(
onPressed: () {},
child: Text('ยาวนิดมากๆที่สุด'),
),
],
),
),

ในวิธีเดียวกันสามารถใช้ IntrinsicHeight เพื่อให้ความสูงเท่ากับ widget ที่สูงที่สุดใน Row ได้เช่นกัน

3. Stack

ใช้สำหรับการวาง layout แบบวาง widget ซ้อนทับกันนั่นเอง โดยใช้ List<Widget> widget ที่อยู่อันดับแรกสุดคืออยู่ล่างสุด widget ที่อยู่อันดับสุดท้ายคือตัวที่อยู่บนสุด

Stack(
children: <Widget>[
Text(
'1',
style: TextStyle(fontSize: 100),
),
Text(
'2',
style: TextStyle(fontSize: 80, color: Colors.yellow),
),
Text(
'3',
style: TextStyle(fontSize: 70, color: Colors.green),
),
Text(
'4',
style: TextStyle(fontSize: 60, color: Colors.blue),
),
],
),

สำหรับกรณีที่ต้องการจัดตำแหน่งสามารถใช้ Positioned ในการวางตำแหน่งตามที่ต้องการได้เลย โดยพื้นที่วางนั้นขึ้นอยู่กับขนาดของ widget แม่

Stack(
fit: StackFit.expand,
children: <Widget>[
Material(color: Colors.blueGrey),
Positioned(
top: 0,
left: 0,
child: Text(
'1',
style: TextStyle(fontSize: 100, color: Colors.yellow),
),
),
Positioned(
bottom: 0,
right: 0,
child: Text(
'2',
style: TextStyle(fontSize: 80, color: Colors.blue),
),
),
],
),

4. Expanded

Expanded จะใช้สำหรับกรณีที่ต้องการที่จะใช้พื้นที่ที่เหลือทั้งเหลือทั้งหมดของ widget แม่ หาก widget แม่นั้น มี Expanded มากกว่า 1 ตัวจะต้องทำการใส่สัดส่วนให้มันผ่านค่า flex เพิ่มเติมเข้าไปด้วย

Row(
children: <Widget>[
Container(
width: 100,
color: Color(0xFF3be2a5),
),
Expanded(
child: Container(color: Colors.yellow),
flex: 2,
),
Expanded(
child: Container(color: Colors.blue),
flex: 1,
),
],
),

5. Container

มาถึง Flutter class widget ที่ผมใช้บ่อยที่สุดในนี้ นั่นคือ Container นั่นเอง Container ทำอะไรได้บ้าง

Container ช่วยในการวาง layout ได้ง่ายขึ้น

กรณีที่ไม่มีการกำหนดขนาดความกว้างและความสูง ขนาดของ Container จะขึ้นอยู่กับ widget ลูกที่ใส่มา

Container(
color: Color(0xFF3be2a5),
child: Text('Hello World!'),
),

ในกรณีที่ต้องการให้ขนาด Container ยืดเต็ม widget แม่ สามารถกำหนด double.infinity ให้กับ height หรือ width ได้เลย

Container(
height: double.infinity,
width: double.infinity,
color: Color(0xFF3be2a5),
child: Text('Hello World!'),
),

Container สำหรับใช้ในการ Transform widget

กรณีที่ต้องการเปลี่ยนแปลง หรือ transform widget ไม่ว่าจะเป็นการหมุน เลื่อน เปลี่ยนรูปร่าง ในแนวนอน แนวตั้ง แนวลึก

Container(
height: double.infinity,
width: double.infinity,
transform: Matrix4.rotationZ(pi / 4),
color: Color(0xFF3be2a5),
child: Text(
'Hello World!',
textAlign: TextAlign.center,
),
),

6. BoxDecoration

เมื่อใช้ Container บ่อยแล้วสิ่งที่ต้องมาคู่กันคือ BoxDecoration สำหรับ Container ใช้กำหนด layout แล้ว ส่วน BoxDecoration ใช้สำหรับตกแต่งความสวยงามให้กับ Container อีกที

border: Border

Center(
child: Container(
height: 300,
width: 300,
decoration: BoxDecoration(
color: Color(0xFF3be2a5),
border: Border.all(width: 2, color: Colors.black),
),
),
),

borderRadius: BorderRadius

borderRadius สำหรับกำหนดมุมมีความโค้ง ตามแบบฉบับ UI สมัยใหม่

Center(
child: Container(
height: 300,
width: 300,
decoration: BoxDecoration(
color: Color(0xFF3be2a5),
borderRadius: BorderRadius.circular(16),
),
),
),

shape: BoxShape

shape ใช้สำหรับกำหนดรูปร่างของ Container สามารถกำหนดได้เป็นสี่เหลี่ยมหรือวงกลมก็ได้

Center(
child: Container(
height: 300,
width: 300,
decoration: BoxDecoration(
color: Color(0xFF3be2a5),
shape: BoxShape.circle,
),
),
),

image: DecorationImage

ใช้กำหนดรูปพื้นหลัง โดยรูปจะมีรูปร่างตาม Container สำหรับที่ผมจะใช้บ่อยๆ คือเอาไว้ใช้กำหนดรูปร่างของรูปได้อย่างอิสระ เช่น ต้องการให้รูปมีมุมโค้งสวยๆ

Center(
child: Container(
height: 300,
width: 300,
decoration: BoxDecoration(
color: Color(0xFF3be2a5),
borderRadius: BorderRadius.circular(16),
image: DecorationImage(
image: NetworkImage('https://miro.medium.com/max/3200/0*LjBPCQFGjmSJ6D46.png'),
fit: BoxFit.cover,
),
),
),
),

boxShadow: List<BoxShadow>

boxShadow ใช้สำหรับกำหนดเงาให้กับ Container

Center(
child: Container(
height: 300,
width: 300,
decoration: BoxDecoration(
color: Color(0xFF3be2a5),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(blurRadius: 6),
],

),
),
),

gradient

ใช้ตกแต่งลูกเล่นการไล่เฉดสีทำให้แอปดูล้ำๆขึ้น

Center(
child: Container(
height: 300,
width: 300,
decoration: BoxDecoration(
color: Color(0xFF3be2a5),
borderRadius: BorderRadius.circular(16),
gradient: LinearGradient(
colors: [
Color(0xFFB6F492),
Color(0xFF338B93),
],
),

),
),
),

7. FractionallySizedBox

class น้องใหม่ที่เพิ่งมาคือ FractionallySizedBox หากต้องการกำหนดขนาดของ widget ลูก สัมพันธ์เป็น % กับขนาดของ widget แม่ สามารถใช้ FractionallySizedBox เพื่อกำหนดความสัมพันธ์ขนาดได้ผ่าน widthFactor และ heightFactor เช่น widthFactor: 0.2 คือกำหนดขนาดให้เป็น 20% ของ widget แม่นั่นเอง (ปกติเมื่อก่อนใช้ MediaQuery.of(context).size ใช้กันมันมือเลย 555)

Center(
child: Container(
width: 300,
height: 100,
color: Colors.blueGrey,
child: FractionallySizedBox(
widthFactor: 0.2,
heightFactor: 0.2,
child: Container(
color: Color(0xFF3be2a5),
),
),
),
),

8. SizedBox

เป็นอีกหนึ่ง class ที่ผมใช้บ่อยเช่นกัน โดยเฉพาะกรณีนี้

ใช้ SizedBox เป็น Padding

Column(
children: <Widget>[
Icon(Icons.motorcycle, size: 48),
SizedBox(height: 48),
Icon(Icons.motorcycle, size: 48),
Icon(Icons.motorcycle, size: 48),
],
),

ส่วนกรณีอื่นๆสามารถลองไปศึกษาเพิ่มเติมได้ที่ https://www.youtube.com/watch?v=EHPu_DzRfqA&feature=emb_title

9. Align

กรณีที่ต้องจัดการตำแหน่งภายใน Container แบบง่ายๆและไม่ต้องยุ่งยากใช้ Stack สามารถใช้ Align ในการจัดการตำแหน่งได้ง่ายๆ โดยมีทั้งหมด 8 ทิศให้กำหนด คือ

  • centerLeft
  • topLeft
  • topCenter
  • topRight
  • centerRight
  • bottomRight
  • bottomCenter
  • bottomLeft
Center(
child: Container(
width: 300,
height: 300,
color: Color(0xFF3be2a5),
child: Align(
alignment: Alignment.bottomRight,
child: Text('Hello World!'),
),
),
),

10. SafeArea

ในความแตกต่างของแต่ละอุปกรณ์ เช่น บางอุปกรณ์แหว่งบน บางอุปกรณ์ไม่แหว่ง เราสามารถใช้ SafeArea เข้ามาช่วยแก้ปัญหาตรงนี้ได้ โดย SafeArea จะทำให้เกิด padding ของด้านบนและด้านล่าง เพื่อให้อยู่ในพื้นที่ที่สามารถมองเห็นได้ชัดเจน

@override
Widget build(BuildContext context) {
return Material(
color: Colors.blue,
child: SafeArea(
child: SizedBox.expand(
child: Card(color: Color(0xFF3be2a5)),
),
),
);
}

Flutter Layout ที่ว่าทั้งหมดเป็น ตัวที่ควรรู้ และค่อนข้างใช้บ่อยพอสมควร หากมีอะไรเพิ่มเติมสามารถแจ้งเข้ามาได้เลย

--

--