개요
AnimatedFractionallySizedBox는 부모 위젯의 크기에 비례하여 자식 위젯의 크기를 설정하고, widthFactor나 heightFactor가 변경될 때 자동으로 애니메이션을 적용하는 위젯입니다. alignment가 변경될 때도 위치가 애니메이션됩니다. Flutter의 AnimatedFractionallySizedBox 위젯에서 영감을 받아 구현되었습니다.
Animated version of FractionallySizedBox which automatically transitions the child’s size over a given duration whenever the given widthFactor or heightFactor changes, as well as the position whenever the given alignment changes.
Flutter 참조: https://api.flutter.dev/flutter/widgets/AnimatedFractionallySizedBox-class.html
언제 사용하나요?
- 부모 크기에 비례하여 위젯 크기를 동적으로 조절할 때
- 반응형 레이아웃에서 크기를 부드럽게 전환할 때
- 화면 크기에 따라 콘텐츠의 크기를 애니메이션으로 조정할 때
- 확대/축소 효과를 부드럽게 구현할 때
- 모달이나 다이얼로그의 크기를 단계적으로 전환할 때
기본 사용법
import { AnimatedFractionallySizedBox, Container, StatefulWidget } from '@meursyphus/flitter';
class SizeAnimationExample extends StatefulWidget {
createState() {
return new SizeAnimationExampleState();
}
}
class SizeAnimationExampleState extends State<SizeAnimationExample> {
isExpanded = false;
build() {
return Container({
width: 300,
height: 300,
color: "lightgray",
child: GestureDetector({
onTap: () => {
this.setState(() => {
this.isExpanded = !this.isExpanded;
});
},
child: AnimatedFractionallySizedBox({
widthFactor: this.isExpanded ? 0.8 : 0.4,
heightFactor: this.isExpanded ? 0.8 : 0.4,
duration: 1000, // 1초
child: Container({
color: "blue",
}),
}),
}),
});
}
}
Props
duration (필수)
값: number
애니메이션 지속 시간을 밀리초 단위로 지정합니다.
widthFactor (선택)
값: number | undefined
부모 위젯 너비에 대한 비율로 자식 위젯의 너비를 설정합니다.
- 0.5 = 부모 너비의 50%
- 1.0 = 부모 너비의 100%
- 지정하지 않으면 자식의 고유 너비 사용
heightFactor (선택)
값: number | undefined
부모 위젯 높이에 대한 비율로 자식 위젯의 높이를 설정합니다.
- 0.5 = 부모 높이의 50%
- 1.0 = 부모 높이의 100%
- 지정하지 않으면 자식의 고유 높이 사용
alignment (선택)
값: Alignment (기본값: Alignment.center)
자식 위젯의 정렬 위치를 지정합니다. 사전 정의된 값들:
Alignment.topLeft
: 좌상단Alignment.topCenter
: 상단 중앙Alignment.topRight
: 우상단Alignment.centerLeft
: 좌측 중앙Alignment.center
: 중앙Alignment.centerRight
: 우측 중앙Alignment.bottomLeft
: 좌하단Alignment.bottomCenter
: 하단 중앙Alignment.bottomRight
: 우하단
curve (선택)
값: Curve (기본값: Curves.linear)
애니메이션의 진행 곡선을 지정합니다. 사용 가능한 곡선:
Curves.linear
: 일정한 속도Curves.easeIn
: 천천히 시작Curves.easeOut
: 천천히 종료Curves.easeInOut
: 천천히 시작하고 종료Curves.circIn
: 원형 가속 시작Curves.circOut
: 원형 감속 종료Curves.circInOut
: 원형 가속/감속Curves.backIn
: 뒤로 갔다가 시작Curves.backOut
: 목표를 지나쳤다가 돌아옴Curves.backInOut
: backIn + backOutCurves.anticipate
: 예비 동작 후 진행Curves.bounceIn
: 바운스하며 시작Curves.bounceOut
: 바운스하며 종료Curves.bounceInOut
: 바운스 시작/종료
child (선택)
값: Widget | undefined
크기가 조절될 자식 위젯입니다.
key (선택)
값: any
위젯의 고유 식별자입니다.
실제 사용 예제
예제 1: 반응형 카드 확대/축소
import { AnimatedFractionallySizedBox, Container, Card, Curves } from '@meursyphus/flitter';
class ResponsiveCard extends StatefulWidget {
createState() {
return new ResponsiveCardState();
}
}
class ResponsiveCardState extends State<ResponsiveCard> {
isExpanded = false;
build() {
return Container({
width: 400,
height: 600,
color: "#f0f0f0",
child: Center({
child: GestureDetector({
onTap: () => {
this.setState(() => {
this.isExpanded = !this.isExpanded;
});
},
child: AnimatedFractionallySizedBox({
widthFactor: this.isExpanded ? 0.9 : 0.6,
heightFactor: this.isExpanded ? 0.7 : 0.4,
duration: 600,
curve: Curves.easeInOut,
alignment: Alignment.center,
child: Card({
elevation: 4,
child: Container({
decoration: BoxDecoration({
gradient: LinearGradient({
colors: ["#4A90E2", "#7B68EE"],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
}),
}),
child: Center({
child: Column({
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon({
icon: this.isExpanded ? Icons.unfold_less : Icons.unfold_more,
size: 48,
color: "white",
}),
SizedBox({ height: 16 }),
Text(
this.isExpanded ? "클릭하여 축소" : "클릭하여 확대",
{ style: TextStyle({ color: "white", fontSize: 18 }) }
),
],
}),
}),
}),
}),
}),
}),
}),
});
}
}
예제 2: 다단계 크기 전환
import { AnimatedFractionallySizedBox, Container, Row, Curves } from '@meursyphus/flitter';
class MultiSizeTransition extends StatefulWidget {
createState() {
return new MultiSizeTransitionState();
}
}
class MultiSizeTransitionState extends State<MultiSizeTransition> {
sizeLevel = 0; // 0: small, 1: medium, 2: large
sizes = [
{ width: 0.3, height: 0.3 },
{ width: 0.6, height: 0.5 },
{ width: 0.9, height: 0.8 },
];
build() {
const currentSize = this.sizes[this.sizeLevel];
return Column({
children: [
// 컨트롤 버튼들
Row({
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton({
onPressed: () => this.changeSize(0),
child: Text("작게"),
style: ButtonStyle({
backgroundColor: this.sizeLevel === 0 ? "blue" : "gray",
}),
}),
SizedBox({ width: 8 }),
ElevatedButton({
onPressed: () => this.changeSize(1),
child: Text("보통"),
style: ButtonStyle({
backgroundColor: this.sizeLevel === 1 ? "blue" : "gray",
}),
}),
SizedBox({ width: 8 }),
ElevatedButton({
onPressed: () => this.changeSize(2),
child: Text("크게"),
style: ButtonStyle({
backgroundColor: this.sizeLevel === 2 ? "blue" : "gray",
}),
}),
],
}),
SizedBox({ height: 20 }),
// 애니메이션 영역
Expanded({
child: Container({
color: "#e0e0e0",
child: AnimatedFractionallySizedBox({
widthFactor: currentSize.width,
heightFactor: currentSize.height,
duration: 800,
curve: Curves.anticipate,
child: Container({
decoration: BoxDecoration({
color: "deepPurple",
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow({
color: "rgba(0,0,0,0.3)",
blurRadius: 10,
offset: Offset({ x: 0, y: 5 }),
}),
],
}),
child: Center({
child: Text(
`${Math.round(currentSize.width * 100)}% x ${Math.round(currentSize.height * 100)}%`,
{ style: TextStyle({ color: "white", fontSize: 24, fontWeight: "bold" }) }
),
}),
}),
}),
}),
}),
],
});
}
changeSize(level: number) {
this.setState(() => {
this.sizeLevel = level;
});
}
}
예제 3: 정렬 변경과 함께 크기 애니메이션
import { AnimatedFractionallySizedBox, Container, Alignment, Curves } from '@meursyphus/flitter';
class AlignmentAndSizeAnimation extends StatefulWidget {
createState() {
return new AlignmentAndSizeAnimationState();
}
}
class AlignmentAndSizeAnimationState extends State<AlignmentAndSizeAnimation> {
currentIndex = 0;
configs = [
{ alignment: Alignment.topLeft, widthFactor: 0.3, heightFactor: 0.3 },
{ alignment: Alignment.topRight, widthFactor: 0.5, heightFactor: 0.2 },
{ alignment: Alignment.center, widthFactor: 0.8, heightFactor: 0.8 },
{ alignment: Alignment.bottomLeft, widthFactor: 0.4, heightFactor: 0.6 },
{ alignment: Alignment.bottomRight, widthFactor: 0.6, heightFactor: 0.4 },
];
build() {
const config = this.configs[this.currentIndex];
return GestureDetector({
onTap: () => this.nextConfiguration(),
child: Container({
width: double.infinity,
height: double.infinity,
color: "lightblue",
child: AnimatedFractionallySizedBox({
alignment: config.alignment,
widthFactor: config.widthFactor,
heightFactor: config.heightFactor,
duration: 1000,
curve: Curves.easeInOut,
child: Container({
decoration: BoxDecoration({
color: "orange",
borderRadius: BorderRadius.circular(20),
border: Border.all({ color: "darkorange", width: 3 }),
}),
child: Center({
child: Column({
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
`위치 ${this.currentIndex + 1}/5`,
{ style: TextStyle({ fontSize: 20, fontWeight: "bold" }) }
),
SizedBox({ height: 8 }),
Text(
"탭하여 다음",
{ style: TextStyle({ fontSize: 16 }) }
),
],
}),
}),
}),
}),
}),
});
}
nextConfiguration() {
this.setState(() => {
this.currentIndex = (this.currentIndex + 1) % this.configs.length;
});
}
}
예제 4: 비율 유지 이미지 컨테이너
import { AnimatedFractionallySizedBox, Container, Image, Curves } from '@meursyphus/flitter';
class AspectRatioImageContainer extends StatefulWidget {
createState() {
return new AspectRatioImageContainerState();
}
}
class AspectRatioImageContainerState extends State<AspectRatioImageContainer> {
isFullscreen = false;
build() {
return Container({
width: double.infinity,
height: double.infinity,
color: "black",
child: GestureDetector({
onTap: () => {
this.setState(() => {
this.isFullscreen = !this.isFullscreen;
});
},
child: AnimatedFractionallySizedBox({
widthFactor: this.isFullscreen ? 1.0 : 0.7,
heightFactor: this.isFullscreen ? 1.0 : 0.5,
duration: 500,
curve: Curves.easeOut,
alignment: Alignment.center,
child: Container({
decoration: BoxDecoration({
boxShadow: this.isFullscreen ? [] : [
BoxShadow({
color: "rgba(0,0,0,0.5)",
blurRadius: 20,
spreadRadius: 5,
}),
],
}),
child: ClipRRect({
borderRadius: BorderRadius.circular(this.isFullscreen ? 0 : 16),
child: Image({
src: "https://example.com/landscape.jpg",
fit: "cover",
}),
}),
}),
}),
}),
});
}
}
주의사항
- widthFactor와 heightFactor는 0보다 크거나 같아야 합니다
- widthFactor와 heightFactor가 모두 undefined일 경우, 자식 위젯의 고유 크기가 사용됩니다
- 부모 위젯의 크기가 변경되면 AnimatedFractionallySizedBox의 실제 크기도 자동으로 조정됩니다
- alignment와 factor 값이 동시에 변경되면 모두 함께 애니메이션됩니다
- 성능을 고려하여 너무 빈번한 크기 변경은 피하는 것이 좋습니다
관련 위젯
- FractionallySizedBox: 애니메이션 없이 부모 크기에 비례하여 크기를 설정하는 기본 위젯
- AnimatedContainer: 여러 속성을 동시에 애니메이션으로 전환
- AnimatedAlign: 정렬만 애니메이션으로 전환
- SizedBox: 고정 크기를 설정하는 위젯