Stack เป็น widget layout ที่มีประโยชน์มาก ๆ เพราะในการใช้งานจัด UI มันไม่ได้มีแค่การเอา widget ไปเรียงต่อกันไปเรื่อย ๆ แต่มันมีแบบเอามาซ้อนทับกันด้วย ตัว Stack จะช่วยในเรื่องการเอา widget ที่ต้องการมาเรียงซ้อน ๆ กันได้ตามต้องการ
constructor มีรายละเอียดดังนี้
const Stack({
Key? key,
AlignmentGeometry alignment = AlignmentDirectional.topStart,
TextDirection? textDirection,
StackFit fit = StackFit.loose,
Clip clipBehavior = Clip.hardEdge,
List<Widget> children = const <Widget>[],
})
ตัว widgets ที่จะเอามาเรียงซ้อนกันให้กำหนดใน children
ในการระบุตำแหน่งและขนาดของ widget ที่จะเอามาเรียงจะให้ Positioned เพื่อกำหนด left right top bottom width height ที่ต้องการ
การวาด Stack ต้องเข้าใจว่าตัวมันจะแค่ช่วยเรียง widget แบบ layer ที่ใช้ในโปรแกรมแต่งภาพ widget ตัวแรกจะอยู่ล่างสุด ตัวสุดท้ายจะอยู่บนสุด ตัวอย่าง จะลองวาดกล่อง 3 ขนาด จะเห็นว่าตัว 150x150 จะถูกทับด้วยกล่อง 80x80 และ 60x60 ทุกกล่องจะอยู่ที่มุมบนซ้ายมือ
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
bgColor() => Color(Random().nextInt(0xFFFFFF)).withOpacity(0.5);
box(double w, double h) => Container(
width: w,
height: h,
color: bgColor(),
child: Center(child: Text('$w x $h')));
var stack = Container(
width: 200,
height: 200,
color: Colors.green,
child: Stack(children: [box(150, 150), box(80, 80), box(60, 60)]));
runApp(MaterialApp(
home: Scaffold(
body: stack,
),
));
}
ตัวอย่างการใช้ Stack
ลองเลื่อนกล่อง 60x60 มาอยู่ชิดซ้ายขอบล่างสุด โดยใช้ Positioned ดู
var stack = Container(
width: 200,
height: 200,
color: Colors.green,
child: Stack(children: [
box(150, 150),
box(80, 80),
Positioned(bottom: 0, child: box(60, 60))
]));
ค่าเริ่มต้นในการวาง widgets คือ AlignmentDirectional.topStart ผู้ใช้งานสามารถกำหนดเป็นค่าอื่นได้ดังนี้
หาก widgets ตัวไหนไม่ได้มีการใช้ Positioned ครอบเอาไว้ ก็จะใช้ค่าจาก alignment
ในการวางตำแหน่ง ตัวอย่างลองวางไว้ตรงกลาง ตัวที่กำหนด Positioned ไว้แค่แกนเดียว (ในตัวอย่างคือ bottom) จึงมีผลทำให้มันเลื่อนมาอยู่ตรงกลางในแนวนอนด้วย
var stack = Container(
width: 200,
height: 200,
color: Colors.green,
child: Stack(alignment: AlignmentDirectional.center, children: [
box(150, 150),
box(80, 80),
Positioned(bottom: 0, child: box(60, 60))
]));
ตัวอย่างการใช้ alignment
นอกจากนี้ยังสามารถใช้งาน Alignment และ FractionalOffset ก็ได้เช่นกัน ลองดูวิธีใช้ใน Align ตัวจัดตำแหน่ง สามารถระบุขอบ หรือระยะ offset ก็ได้
ค่า fit
จะเป็นการส่งค่า constraints ไปให้ตัว widgets ที่อยู่ข้างในว่าจะให้มีพฤติกรรมในการกำหนดขนาดของตัวมันอย่างไร ค่าเริ่มต้นจะเป็น StackFit.loose จะเป็นการไม่ระบุค่า minimum width และ minimum height ตัวอย่าง StackFit.loose
var stack = Container(
width: 200,
height: 200,
color: Colors.green,
child: Stack(fit: StackFit.loose, children: [
box(150, 150),
box(80, 80),
Positioned(bottom: 0, child: box(60, 60))
]));
กำหนด fit: StackFit.loose
ถ้ากำหนดค่าเป็น StackFit.expand ตัว Stack จะส่งค่า constraints เป็นค่า max ของแต่ละด้านไปแทน บังคับให้ widgets มีขนาดขยายจนเต็มพื้นที่ Parent ตามที่กำหนด(ถ้าทำได้) ผลจากการใช้จะทำให้ตัว box() ที่ไม่ได้ครอบด้วย Positioned ขยายใหญ่จนเต็ม
กำหนด fit: StackFit.expand
อันสุดท้ายคือ StackFit.passthrough คือส่งค่า constraints จาก Parent ไปยัง widgets โดยตรง
ค่าเริ่มต้นหากมี widgets แสดงผลเกินกว่าพื้นที่ Stack คือ clipBehavior = Clip.hardEdge
คือ ตัดส่วนที่เกินทิ้ง โดยไม่มีการทำ anti-aliasing (ลดความหยักของขอบที่โดนตัด) ตัวอย่าง ลองกำหนดค่าให้ widget ทะลุตัว Stack ออกไป
var stack = Container(
width: 200,
height: 200,
color: Colors.green,
child: Stack(children: [Positioned(top: 0, child: box(300, 60))]));
ตัวอย่างการ Clip.hardEdge
หากไม่ต้องการให้มีการตัดส่วนของ widgets ที่เกินออกไปให้ใช้ค่า clipBehavior: Clip.none
var stack = Container(
width: 200,
height: 200,
color: Colors.green,
child: Stack(
clipBehavior: Clip.none,
children: [Positioned(top: 0, child: box(300, 60))]));
ตัวอย่างการ Clip.none
ค่าอื่น ๆ ของ Clip
ผู้ใช้สามารถวาด widgets ให้ซ้อนกันได้ เพื่อใช้ในงานตกแต่งต่าง ๆ ได้
import 'package:flutter/material.dart';
void main() {
var bgColor = Colors.white;
var stack = SizedBox.square(
dimension: 200,
child: Stack(alignment: Alignment.center, children: [
Padding(
padding: EdgeInsets.all(10.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
border: Border.all(color: Colors.black45)))),
Positioned(
left: 24.0,
top: 0,
child: Container(
color: bgColor,
padding: EdgeInsets.only(left: 4, right: 4),
child: Text('Title here'))),
Positioned(
right: 24,
top: 3,
child: Container(
color: bgColor,
child: Icon(Icons.close, size: 16, color: Colors.red),
)),
FlutterLogo(size: 120)
]));
runApp(MaterialApp(
home: Scaffold(
backgroundColor: bgColor,
body: stack,
),
));
}
ผลที่ได้จากตัวอย่าง