สรุป 10 อันดับ Flutter Layout ที่คุณควรรู้ ฉบับรวบรัด
บทความนี้เป็นบทความแรกเกี่ยวกับ 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 ที่ว่าทั้งหมดเป็น ตัวที่ควรรู้ และค่อนข้างใช้บ่อยพอสมควร หากมีอะไรเพิ่มเติมสามารถแจ้งเข้ามาได้เลย