เนื้อหานี้เป็นตอนที่สุดท้ายเกี่ยวกับการทบทวน Layout Widget
ตอนที่แล้วเราดูเกี่ยวกับ Multi child Layout หรือก็คือ Widget
ที่รองรับ widget ลูกหลายๆ ตัวพร้อมกัน จะใช้ชื่อรายการ widget นี้ว่า
children ในขณะที่ถ้าเป็น Single Child นี้ ซึ่งรองรับ widget ลูกแค่
รายการเดียว จะใช้คำว่า child สำหรับกำหนด widget ลูกภายใน
เนื้อหาตอนที่แล้ว http://niik.in/1103
เนื้อหานี้ใช้โค้ดตัวอย่างเริ่มต้น จากบทความ ตามลิ้งค์นี้ http://niik.in/961
โดยใช้ โค้ดตัวอย่างจากส่วน เพิ่มเติมเนื้อหา ครั้งที่ 2
หน้าที่ของ Widget และ delegate ใน Single-child Layout Widget
1. Align
เป็น widget ที่ใช้จัดตำแหน่ง widget ลูกภายในตามที่กำหนด เช่น จัดให้อยู่กึ่งกลางซ้าย, กึ่งกลาง
ขวา, หรือมุมใด ๆ ของพื้นที่ที่กำหนด มักใช้ในกรณีที่ต้องการควบคุมตำแหน่งของ widget ภายในอย่าง
เฉพาะเจาะจง
ตัวอย่าง
Container( color: Colors.grey[300], // สีพื้นหลังของ Container height: 200, width: 200, child: Align( alignment: Alignment.topLeft, // จัดตำแหน่งไปที่มุมซ้ายบน child: Container( color: Colors.blue, width: 50, height: 50, ), ), ),
ผลลัพธ์
2. AspectRatio
เป็น widget ที่ใช้รักษาอัตราส่วนกว้างยาวของ widget ลูกให้คงที่ มักใช้ในกรณีที่ต้องการให้ widget มีขนาดสัมพันธ์กับพื้นที่ภายใน โดยคงอัตราส่วนเช่น 16:9 หรือ 4:3
ตัวอย่าง
Container( color: Colors.grey[300], // สีพื้นหลังของ Container child: AspectRatio( aspectRatio: 16 / 9, // กำหนดอัตราส่วน 16:9 child: Container( color: Colors.blue, // สีพื้นหลังของวิดเจ็ตภายใน child: Center( child: Text( '16:9 Aspect Ratio', style: TextStyle(color: Colors.white), ), ), ), ), ),
ผลลัพธ์
3. Baseline
เป็น widget ที่จัด widget ลูกตามเส้นฐานที่กำหนดไว้ มักใช้ในกรณีที่ต้องการจัดเรียง widget
ให้ตรงกับเส้นฐานของฟอนต์ เช่น การจัดข้อความให้อยู่บนเส้นฐานเดียวกัน
ตัวอย่าง
Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ไม่มีการใช้ Baseline Row( children: [ Container( color: Colors.blueAccent, // สีพื้นหลังสำหรับข้อความ Hello child: Row( children: [ Icon(Icons.star, size: 30, color: Colors.white), SizedBox(width: 5), Text('Hello', style: TextStyle(fontSize: 30, color: Colors.white)), ], ), ), SizedBox(width: 10), Container( color: Colors.greenAccent, // สีพื้นหลังสำหรับข้อความ World child: Row( children: [ Icon(Icons.star, size: 20, color: Colors.white), SizedBox(width: 5), Text('World', style: TextStyle(fontSize: 20, color: Colors.white)), ], ), ), ], ), SizedBox(height: 20), // ระยะห่างระหว่างสอง Row // มีการใช้ Baseline Row( children: [ Baseline( baseline: 50, baselineType: TextBaseline.alphabetic, child: Container( color: Colors.blueAccent, // สีพื้นหลังสำหรับข้อความ Hello child: Row( children: [ Icon(Icons.star, size: 30, color: Colors.white), SizedBox(width: 5), Text('Hello', style: TextStyle(fontSize: 30, color: Colors.white)), ], ), ), ), SizedBox(width: 10), Baseline( baseline: 50, baselineType: TextBaseline.alphabetic, child: Container( color: Colors.greenAccent, // สีพื้นหลังสำหรับข้อความ World child: Row( children: [ Icon(Icons.star, size: 20, color: Colors.white), SizedBox(width: 5), Text('World', style: TextStyle(fontSize: 20, color: Colors.white)), ], ), ), ), ], ), ], )
ผลลัพธ์
4. Center
เป็น widget ที่จัดเรียง widget ลูกให้อยู่กึ่งกลางของพื้นที่ที่กำหนด มักใช้ในกรณีที่ต้องการให้
widget อยู่ตรงกลางของหน้าจอหรือพื้นที่ที่มีการกำหนดไว้
*ดูตัวอย่างจากโค้ดด้านบน
5. ConstrainedBox
เป็น widget ที่ใช้จำกัดขนาดของ widget ลูกภายในตามข้อจำกัดที่กำหนด มักใช้ในกรณีที่ต้อง
การบังคับขนาดของ widget ให้เป็นไปตามเงื่อนไขที่ตั้งไว้ เช่น ความกว้างสูงสุดหรือขั้นต่ำ
ตัวอย่าง
Container( color: Colors.grey[300], // สีพื้นหลังของ Container หลัก child: ConstrainedBox( constraints: BoxConstraints( minWidth: 100, minHeight: 100, maxWidth: 200, maxHeight: 200, ), child: Container( color: Colors.blue, // สีพื้นหลังของ Container ภายใน width: 50, height: 50, ), ), ),
ผลลัพธ์
Container ภายใน จะถูกขยายให้มีขนาด 100x100 (ตามขนาดขั้นต่ำที่กำหนด) แทนที่จะเป็นขนาด
ตั้งต้นที่ 50x50
ถ้าขนาดตั้งต้นของ Container เป็น 150x150 จะไม่มีการเปลี่ยนแปลงใดๆ เพราะมันอยู่ภายในขอบ
เขตที่กำหนด
ถ้าขนาดตั้งต้นของ Container เป็น 250x250 จะถูกบีบลงให้เหลือ 200x200 ตามขนาดสูงสุดที่
กำหนด
6. Container
เป็น widget ที่ใช้กำหนดการจัดวาง, ขนาด, สีพื้นหลัง, เส้นขอบ และการตกแต่งอื่น ๆ ของ widget
ลูกภายใน มักใช้ในกรณีที่ต้องการควบคุมลักษณะการแสดงผลของ widget อย่างละเอียด
*ดูตัวอย่างจากโค้ดด้านบน
7. CustomSingleChildLayout
เป็น widget ที่ใช้จัดวาง widget ลูกเพียงตัวเดียวตามการจัดวางที่กำหนดเองผ่าน delegate
มักใช้ในกรณีที่ต้องการการจัดวาง widget แบบกำหนดเองที่ซับซ้อน
ตัวอย่าง
class _HomeState extends State<Home> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home'), leading: IconButton( icon: Icon(Icons.menu), onPressed: () { Scaffold.of(context).openDrawer(); }, ), ), body: Center( child: CustomSingleChildLayout( delegate: MyCustomLayoutDelegate(), child: Container( color: Colors.red, width: 100, height: 100, ), ), ), ); } } class MyCustomLayoutDelegate extends SingleChildLayoutDelegate { @override Size getSize(BoxConstraints constraints) { // กำหนดขนาดของพื้นที่สำหรับการจัดวาง widget ลูก return Size(200, 200); } @override Offset getPositionForChild(Size size, Size childSize) { // กำหนดตำแหน่งของ widget ลูก return Offset((size.width - childSize.width) / 2, (size.height - childSize.height) / 2); } @override bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate) { // กำหนดว่าจะต้องทำการจัดเรียงใหม่หรือไม่ return false; } }
ผลลัพธ์
จะคล้ายๆ กับ ConstrainedBox แต่ สามารถกำหนดได้มากกว่า และจัดวางตำแหน่งได้
8. Expanded
เป็น widget ที่ขยาย widget ลูกให้ใช้พื้นที่ว่างที่เหลือทั้งหมดในแนวแกนหลัก เช่น แนวตั้งหรือ
แนวนอน มักใช้ในกรณีที่ต้องการให้ widget ลูกขยายเต็มพื้นที่ที่มีอยู่
ตัาอย่าง
body: Column( children: [ Container( color: Colors.red, height: 100, child: Center(child: Text('Top')), ), Expanded( child: Container( color: Colors.green, child: Center(child: Text('Expanded')), ), ), Container( color: Colors.blue, height: 100, child: Center(child: Text('Bottom')), ), ], ),
ผลลัพธ์
9. FittedBox
เป็น widget ที่ปรับขนาด widget ลูกให้พอดีกับขอบเขตที่กำหนดไว้ โดยรักษาสัดส่วนตามที่
กำหนด มักใช้ในกรณีที่ต้องการให้ widget ปรับขนาดตามพื้นที่ที่มีอยู่โดยไม่ทำให้สัดส่วนเสียไป
ตัวอย่าง
body: SingleChildScrollView( child: Column( children: [ // ตัวอย่างที่ 1: FittedBox with BoxFit.cover Container( color: Colors.grey[300], width: 200, height: 200, child: FittedBox( fit: BoxFit.cover, // ขยายให้เต็มพื้นที่และอาจทำให้บางส่วนถูกตัดออก child: Container( color: Colors.blue, width: 100, height: 100, child: Center(child: Text('BoxFit.cover')), ), ), ), SizedBox(height: 20), // เว้นระยะห่างระหว่างตัวอย่าง // ตัวอย่างที่ 2: FittedBox with BoxFit.contain Container( color: Colors.grey[300], width: 200, height: 200, child: FittedBox( fit: BoxFit.contain, // ขยายให้พอดีกับพื้นที่โดยไม่ตัดออก child: Container( color: Colors.blue, width: 150, height: 150, child: Center(child: Text('BoxFit.contain')), ), ), ), SizedBox(height: 20), // เว้นระยะห่างระหว่างตัวอย่าง // ตัวอย่างที่ 3: FittedBox with BoxFit.scaleDown Container( color: Colors.grey[300], width: 200, height: 200, child: FittedBox( fit: BoxFit.scaleDown, // ย่อขนาดเพื่อไม่ให้เกินขนาดพื้นที่ child: Container( color: Colors.blue, width: 300, height: 300, child: Center(child: Text('BoxFit.scaleDown')), ), ), ), ], ), ),
ผลลัพธ์
สังเกตว่า child ที่อยู่ภายในมีการปรับขนาดอัตโนมัติ
10. FractionallySizedBox
เป็น widget ที่ปรับขนาด widget ลูกตามสัดส่วนที่กำหนดของพื้นที่ที่มีอยู่ มักใช้ในกรณีที่
ต้องการให้ widget ใช้พื้นที่เป็นสัดส่วน เช่น 50% ของความกว้างของหน้าจอ
ตัวอย่าง
body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // ตัวอย่างที่ 1: FractionallySizedBox ขยายให้ใช้ 50% ของความกว้าง Container( color: Colors.grey[300], width: 300, height: 100, child: FractionallySizedBox( widthFactor: 0.5, // ใช้ 50% ของความกว้าง child: Container( color: Colors.blue, child: Center(child: Text('50% Width')), ), ), ), SizedBox(height: 20), // เว้นระยะห่างระหว่างตัวอย่าง // ตัวอย่างที่ 2: FractionallySizedBox ขยายให้ใช้ 75% ของความสูง Container( color: Colors.grey[300], width: 100, height: 300, child: FractionallySizedBox( heightFactor: 0.75, // ใช้ 75% ของความสูง child: Container( color: Colors.green, child: Center(child: Text('75% Height')), ), ), ), SizedBox(height: 20), // เว้นระยะห่างระหว่างตัวอย่าง // ตัวอย่างที่ 3: FractionallySizedBox ขยายให้ใช้ 50% ของทั้งความกว้างและความสูง Container( color: Colors.grey[300], width: 200, height: 200, child: FractionallySizedBox( widthFactor: 0.5, // ใช้ 50% ของความกว้าง heightFactor: 0.5, // ใช้ 50% ของความสูง child: Container( color: Colors.red, child: Center(child: Text('50% Width & Height')), ), ), ), ], ),
ผลลัพธ์
11. IntrinsicHeight
เป็น widget ที่ปรับความสูงของ widget ลูกให้เท่ากับความสูงภายในที่แท้จริงของ widget ลูก
ภายใน มักใช้ในกรณีที่ต้องการให้ความสูงของ widget ถูกปรับตามเนื้อหาภายในอย่างแม่นยำ
ตัวอย่าง
body: Column( children: [ // ตัวอย่างที่ 1: IntrinsicHeight กับหลาย widget IntrinsicHeight( child: Row( children: [ Container( color: Colors.red, width: 100, height: 50, // ความสูงจะถูกขยายเพื่อให้เท่ากับ widget อื่น child: Center(child: Text('Red')), ), Container( color: Colors.green, width: 100, height: 100, // ความสูงสูงที่สุด child: Center(child: Text('Green')), ), Container( color: Colors.blue, width: 100, height: 75, // ความสูงกลาง child: Center(child: Text('Blue')), ), ], ), ), SizedBox(height: 20), // เว้นระยะห่างระหว่างตัวอย่าง // ตัวอย่างที่ 2: IntrinsicHeight กับ widget ภายใน Column IntrinsicHeight( child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, // ทำให้ widget ลูกขยายเต็มความสูง children: [ Container( color: Colors.orange, width: 100, child: Center(child: Text('Orange')), ), Container( color: Colors.purple, width: 100, child: Center(child: Text('Purple')), ), Container( color: Colors.yellow, width: 100, child: Center(child: Text('Yellow')), ), ], ), ), ], ),
ผลลัพธ์
สังเกตกรณีแถวแรกขนาดของ container จริงๆ แล้วจะขยายขนาดสูงเท่ากับ 100 แต่
เนื่องจากภายใน มี child ที่ขนาดต่างๆ จึงทำให้เห็นสีในลักษณะที่ขนาด container ไม่เท่ากัน
แต่ในตัวอย่างแถวที่สอง เราเพิ่ม CrossAxisAlignment.stretch, เข้าไป ทำให้ขนาด
ขยายความเท่ากันชัดเจน
12. IntrinsicWidth
เป็น widget ที่ปรับความกว้างของ widget ลูกให้เท่ากับความกว้างภายในที่แท้จริงของ widget
ลูกภายใน มักใช้ในกรณีที่ต้องการให้ความกว้างของ widget ถูกปรับตามเนื้อหาภายในอย่างแม่นยำ
ตัวอย่าง
body: Column( children: [ // ตัวอย่างการใช้ IntrinsicWidth IntrinsicWidth( child: Row( children: [ Container( color: Colors.red, width: 50, // ความกว้างที่กำหนด height: 100, child: Center(child: Text('Red')), ), Container( color: Colors.green, width: 100, // ความกว้างสูงที่สุด height: 100, child: Center(child: Text('Green')), ), Container( color: Colors.blue, width: 75, // ความกว้างกลาง height: 100, child: Center(child: Text('Blue')), ), ], ), ), SizedBox(height: 20), // เว้นระยะห่างระหว่างตัวอย่าง // ตัวอย่างการใช้ Row โดยไม่ใช้ IntrinsicWidth Row( children: [ Container( color: Colors.orange, width: 50, // ความกว้างที่กำหนด height: 100, child: Center(child: Text('Orange')), ), Container( color: Colors.purple, width: 100, // ความกว้างสูงที่สุด height: 100, child: Center(child: Text('Purple')), ), Container( color: Colors.yellow, width: 75, // ความกว้างกลาง height: 100, child: Center(child: Text('Yellow')), ), ], ), ], ),
ผลลัพธ์
ในตัวอย่างแรก ที่ใช้งาน IntrinsicWidth จะทำให้ขนาดของ container จะถูกแบ่งออกเท่าๆ กัน
13. LimitedBox
เป็น widget ที่กำหนดขนาดสูงสุดของ widget ลูกเมื่ออยู่ภายในพื้นที่จำกัด มักใช้ในกรณีที่
widget ถูกกำหนดขนาดไม่จำกัด แต่ต้องการจำกัดขนาดสูงสุดไว้
ตัวอย่าง
body: Column( children: [ // ตัวอย่างการใช้ LimitedBox ภายใน Column ที่มีข้อจำกัดด้านความกว้าง Row( children: [ LimitedBox( maxWidth: 100, child: Container( color: Colors.red, height: 100, child: Center( child: Text( 'LimitedBox 100 width', textAlign: TextAlign.center, style: TextStyle(color: Colors.white), ), ), ), ), Container( color: Colors.green, width: 150, height: 100, child: Center( child: Text( 'Normal Container 150 width', textAlign: TextAlign.center, style: TextStyle(color: Colors.white), ), ), ), ], ), SizedBox(height: 20), // ตัวอย่างการใช้ LimitedBox ในบริบทที่ไม่มีข้อจำกัด LimitedBox( maxHeight: 50, child: Container( color: Colors.blue, width: double.infinity, height: 200, // LimitedBox ไม่มีผลกับขนาดนี้เพราะไม่มีข้อจำกัด child: Center( child: Text( 'This height will be 200', textAlign: TextAlign.center, style: TextStyle(color: Colors.white), ), ), ), ), ], ),
ผลลัพธ์
14. Offstage
เป็น widget ที่ซ่อน widget ลูกจากการแสดงผลโดยไม่ทำลาย widget นั้น มักใช้ในกรณีที่
ต้องการควบคุมการแสดงผลของ widget โดยสามารถทำให้แสดงผลหรือซ่อนไว้ได้ตามเงื่อนไข
ตัวอย่าง
class _HomeState extends State<Home> { bool _isOffstage = true; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home'), leading: IconButton( icon: Icon(Icons.menu), onPressed: () { Scaffold.of(context).openDrawer(); }, ), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // ปุ่มสำหรับสลับการแสดงผลของ Container ที่ซ่อนอยู่ ElevatedButton( onPressed: () { setState(() { _isOffstage = !_isOffstage; }); }, child: Text(_isOffstage ? 'Show Container' : 'Hide Container'), ), SizedBox(height: 20), // การใช้ Offstage ในการซ่อน Container Offstage( offstage: _isOffstage, child: Container( color: Colors.blue, height: 100, width: 100, child: Center( child: Text( 'I am Offstage', textAlign: TextAlign.center, style: TextStyle(color: Colors.white), ), ), ), ), // Widget อื่นที่ยังคงถูกแสดงผล Container( color: Colors.green, height: 100, width: 100, child: Center( child: Text( 'I am always visible', textAlign: TextAlign.center, style: TextStyle(color: Colors.white), ), ), ), ], ), ); } }
ผลลัพธ์
15. OverflowBox
เป็น widget ที่สามารถแสดง widget ลูกที่มีขนาดใหญ่กว่าขอบเขตที่กำหนด มักใช้ในกรณีที่
ต้องการให้ widget ลูกออกมานอกขอบเขตปกติ
ตัวอย่าง
body: Center( child: Container( width: 100, height: 100, color: Colors.red, child: OverflowBox( minWidth: 0.0, maxWidth: 200.0, minHeight: 0.0, maxHeight: 200.0, child: Container( width: 150, height: 150, color: Colors.blue.withOpacity(0.5), ), ), ), ),
ผลลัพธ์
16. Padding
เป็น widget ที่เพิ่มพื้นที่ว่างรอบ widget ลูกภายใน มักใช้ในกรณีที่ต้องการให้มีระยะห่างระหว่าง
widget และขอบเขตภายนอก
*พบเห็นได้หลายๆ ครั้งในการใช้งานที่ผ่านๆ มา
17. SizedBox
เป็น widget ที่ใช้กำหนดขนาดของ widget ลูกภายใน มักใช้ในกรณีที่ต้องการกำหนดขนาด
ที่แน่นอนให้กับ widget โดยเฉพาะ
*พบเห็นได้หลายๆ ครั้งในการใช้งานที่ผ่านๆ มา
18. SizedOverflowBox
เป็น widget ที่ปรับขนาด widget ลูกตามขนาดที่กำหนดไว้แต่สามารถให้เนื้อหาใน widget ลูก
เกินขอบเขตได้ มักใช้ในกรณีที่ต้องการแสดงผล widget ที่มีขนาดแตกต่างจากขนาดพื้นที่ที่กำหนด
ตัวอย่าง
body: Center( child: Container( width: 200, height: 200, color: Colors.red, child: SizedOverflowBox( size: Size(100, 100), alignment: Alignment.center, child: Container( width: 150, height: 150, color: Colors.blue.withOpacity(0.7), ), ), ), ),
ผลลัพธ์
19. Transform
เป็น widget ที่ใช้แปลง widget ลูกภายใน เช่น หมุน, ย่อขยาย, หรือเลื่อนตำแหน่ง มักใช้ในกรณี
ที่ต้องการสร้างเอฟเฟกต์พิเศษให้กับ widget เช่น การหมุนรูปภาพหรือการย่อขยายปุ่ม
ตัวอย่าง
body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // การแปลงแบบ Rotate Transform.rotate( angle: 0.5, // หมุนไป 0.5 เรเดียน (ประมาณ 28.65 องศา) child: Container( width: 100, height: 100, color: Colors.blue, child: Icon(Icons.star, color: Colors.white), ), ), SizedBox(height: 20), // การแปลงแบบ Scale Transform.scale( scale: 1.5, // ขยายขนาด 1.5 เท่า child: Container( width: 100, height: 100, color: Colors.green, child: Icon(Icons.star, color: Colors.white), ), ), SizedBox(height: 20), // การแปลงแบบ Translate Transform.translate( offset: Offset(50, -30), // เลื่อนในแนวนอน 50 พิกเซลและในแนวตั้ง -30 พิกเซล child: Container( width: 100, height: 100, color: Colors.red, child: Icon(Icons.star, color: Colors.white), ), ), ], ), ),
ผลลัพธ์
ตัวอย่างแนวทางโค้ดข้างต้นนี้ เป็นเหมือนตัวช่วยให้เรากลับมาทบทวนหรือมาเลือกว่า เราสามารถ
จะนำ widget ไหนไปใช้งานหรือไปแก้ไขปัญหาเกี่ยวกับการจัดการ layout ของเราได้ และไม่
จำเป็นว่า เราจะต้องใช้ทุกอัน เพราะจริงๆ แล้ว เราสามารถประยุกต์จาก widget บางตัวได้อยู่แล้ว