개요
Align은 자식 위젯을 자신 내부에서 정렬하고, 선택적으로 자식의 크기에 따라 자신의 크기를 조정하는 위젯입니다. 정규화된 좌표 시스템을 사용하여 자식을 유연하게 배치할 수 있습니다.
참조: https://api.flutter.dev/flutter/widgets/Align-class.html
언제 사용하나요?
- 자식 위젯을 부모 내에서 특정 위치에 정렬할 때
- 자식 위젯 주변에 특정 비율의 여백을 만들 때
- 대화 상자나 팝업의 내용을 중앙 정렬할 때
- 버튼이나 아이콘을 특정 위치에 배치할 때
- 자식 위젯의 크기에 비례하여 부모 크기를 조정할 때
기본 사용법
// 중앙 정렬 (기본값)
Align({
child: Text('중앙 정렬된 텍스트')
})
// 오른쪽 상단 정렬
Align({
alignment: Alignment.topRight,
child: Icon(Icons.close)
})
// 크기 요소 사용
Align({
widthFactor: 2.0,
heightFactor: 1.5,
child: Container({
width: 100,
height: 100,
color: 'blue'
})
})
Props
child (선택)
값: Widget | undefined
정렬할 자식 위젯입니다.
alignment (선택)
값: Alignment (기본값: Alignment.center)
자식 위젯의 정렬 위치를 지정합니다. Alignment는 x와 y 값으로 구성되며, 각각 -1.0에서 1.0 사이의 값을 가집니다.
주요 Alignment 상수:
Alignment.topLeft
: (-1.0, -1.0)Alignment.topCenter
: (0.0, -1.0)Alignment.topRight
: (1.0, -1.0)Alignment.centerLeft
: (-1.0, 0.0)Alignment.center
: (0.0, 0.0)Alignment.centerRight
: (1.0, 0.0)Alignment.bottomLeft
: (-1.0, 1.0)Alignment.bottomCenter
: (0.0, 1.0)Alignment.bottomRight
: (1.0, 1.0)
// 사용자 정의 정렬
Align({
alignment: Alignment.of({ x: 0.5, y: -0.5 }), // 오른쪽 상단 쪽
child: Text('커스텀 정렬')
})
widthFactor (선택)
값: number | undefined
이 위젯의 너비를 자식 위젯 너비의 배수로 설정합니다. null이면 가능한 너비로 확장됩니다.
Align({
widthFactor: 1.5, // 자식 너비의 1.5배
child: Container({
width: 100,
height: 50,
color: 'red'
})
})
// Align의 너비는 150이 됩니다
heightFactor (선택)
값: number | undefined
이 위젯의 높이를 자식 위젯 높이의 배수로 설정합니다. null이면 가능한 높이로 확장됩니다.
Align({
heightFactor: 2.0, // 자식 높이의 2배
child: Container({
width: 100,
height: 50,
color: 'green'
})
})
// Align의 높이는 100이 됩니다
레이아웃 동작
크기 제약 처리
-
제한된 제약 조건 하에서:
- widthFactor와 heightFactor가 null이면 가능한 크기로 확장
- 자식을 느슨한 제약 조건으로 레이아웃
- alignment에 따라 자식 위치 지정
-
무제한 제약 조건 하에서:
- widthFactor와 heightFactor가 null이면 자식 크기로 축소
- 지정된 factor가 있으면 자식 크기 × factor로 크기 결정
-
크기 요소(factor) 사용 시:
- 항상 자식 크기 × factor로 크기 결정
- 부모의 제약 조건과 무관하게 동작
실제 사용 예제
예제 1: 닫기 버튼 정렬
const DialogWithCloseButton = ({ title, content, onClose }) => {
return Container({
padding: EdgeInsets.all(20),
decoration: BoxDecoration({
color: 'white',
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow({
color: 'rgba(0, 0, 0, 0.1)',
blurRadius: 10,
offset: { x: 0, y: 4 }
})
]
}),
child: Stack({
children: [
Column({
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, {
style: TextStyle({
fontSize: 20,
fontWeight: 'bold'
})
}),
SizedBox({ height: 12 }),
Text(content)
]
}),
Align({
alignment: Alignment.topRight,
child: GestureDetector({
onTap: onClose,
child: Icon(Icons.close, { size: 24 })
})
})
]
})
});
};
예제 2: 로딩 인디케이터 중앙 정렬
const LoadingOverlay = ({ isLoading, child }) => {
return Stack({
children: [
child,
if (isLoading) Container({
color: 'rgba(0, 0, 0, 0.5)',
child: Align({
alignment: Alignment.center,
child: Container({
padding: EdgeInsets.all(20),
decoration: BoxDecoration({
color: 'white',
borderRadius: BorderRadius.circular(8)
}),
child: Column({
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox({ height: 16 }),
Text('로딩 중...')
]
})
})
})
})
]
});
};
예제 3: 배지 위치 지정
const IconWithBadge = ({ icon, badgeCount }) => {
return Container({
width: 48,
height: 48,
child: Stack({
children: [
Center({
child: Icon(icon, { size: 32 })
}),
if (badgeCount > 0) Align({
alignment: Alignment.topRight,
child: Container({
padding: EdgeInsets.all(4),
decoration: BoxDecoration({
color: 'red',
shape: BoxShape.circle
}),
constraints: BoxConstraints({
minWidth: 20,
minHeight: 20
}),
child: Center({
child: Text(badgeCount.toString(), {
style: TextStyle({
color: 'white',
fontSize: 12,
fontWeight: 'bold'
})
})
})
})
})
]
})
});
};
예제 4: 툴팁 정렬
class TooltipWidget extends StatefulWidget {
message: string;
child: Widget;
alignment: Alignment;
constructor({ message, child, alignment = Alignment.topCenter }) {
super();
this.message = message;
this.child = child;
this.alignment = alignment;
}
createState(): State<TooltipWidget> {
return new TooltipWidgetState();
}
}
class TooltipWidgetState extends State<TooltipWidget> {
isVisible = false;
build(): Widget {
return Stack({
clipBehavior: Clip.none,
children: [
GestureDetector({
onMouseEnter: () => {
this.setState(() => {
this.isVisible = true;
});
},
onMouseLeave: () => {
this.setState(() => {
this.isVisible = false;
});
},
child: this.widget.child
}),
if (this.isVisible) Align({
alignment: this.widget.alignment,
widthFactor: 1.0,
child: Transform.translate({
offset: { x: 0, y: this.widget.alignment.y < 0 ? -30 : 30 },
child: Container({
padding: EdgeInsets.symmetric({ horizontal: 12, vertical: 6 }),
decoration: BoxDecoration({
color: 'rgba(0, 0, 0, 0.8)',
borderRadius: BorderRadius.circular(4)
}),
child: Text(this.widget.message, {
style: TextStyle({
color: 'white',
fontSize: 14
})
})
})
})
})
]
});
}
}
예제 5: 반응형 카드 레이아웃
const ResponsiveCard = ({ title, subtitle, action }) => {
return Container({
padding: EdgeInsets.all(16),
decoration: BoxDecoration({
border: Border.all({ color: '#E0E0E0' }),
borderRadius: BorderRadius.circular(8)
}),
child: Row({
children: [
Expanded({
child: Column({
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, {
style: TextStyle({
fontSize: 18,
fontWeight: 'bold'
})
}),
SizedBox({ height: 4 }),
Text(subtitle, {
style: TextStyle({
color: '#666',
fontSize: 14
})
})
]
})
}),
Align({
alignment: Alignment.centerRight,
widthFactor: 1.2,
child: action
})
]
})
});
};
주의사항
- widthFactor와 heightFactor는 0보다 커야 합니다
- alignment의 x, y 값은 -1.0에서 1.0 사이여야 합니다
- 자식이 없을 때 widthFactor나 heightFactor가 null이면 크기가 0이 될 수 있습니다
- Stack 내에서 Align을 사용할 때는 Positioned 위젯과의 차이점을 이해해야 합니다
- 크기 요소를 사용하면 레이아웃 성능에 영향을 줄 수 있습니다
- 제약 조건을 느슨하게 만들어 자식이 원하는 크기를 가질 수 있게 합니다
관련 위젯
- Center: Align의 특수한 경우로, 항상 중앙 정렬
- Positioned: Stack 내에서 절대 위치 지정
- Container: alignment 속성으로 자식 정렬 가능
- FractionallySizedBox: 부모 크기의 비율로 크기 지정
- AnimatedAlign: 정렬 위치 간 애니메이션 전환