개요
LimitedBox는 자식이 무한 제약(unbounded constraints)을 받을 때만 크기 제한을 적용하는 위젯입니다.
이 위젯은 주로 ListView, Row, Column 등의 스크롤 가능한 위젯이나 무한 공간을 제공하는 위젯 내에서 자식이 무제한으로 커지는 것을 방지할 때 사용됩니다. 부모가 이미 유한한 제약을 제공하는 경우에는 LimitedBox가 아무 작업도 하지 않습니다.
참조: https://api.flutter.dev/flutter/widgets/LimitedBox-class.html
언제 사용하나요?
- ListView, Column, Row 등에서 무한 확장을 방지하고 싶을 때
- 스크롤 가능한 컨테이너 내에서 자식의 최대 크기를 제한하고 싶을 때
- 무한 제약 환경에서만 선택적으로 크기 제한을 적용하고 싶을 때
- 컨테이너가 너무 커지는 것을 방지하면서 유연성을 유지하고 싶을 때
- 반응형 레이아웃에서 안전한 최대 크기를 설정하고 싶을 때
기본 사용법
// 세로 스크롤 리스트 내에서 가로 크기 제한
ListView({
children: [
LimitedBox({
maxWidth: 300,
child: Container({
color: 'blue',
child: Text('너비가 300으로 제한됨')
})
})
]
})
// 가로 스크롤 리스트 내에서 세로 크기 제한
SingleChildScrollView({
scrollDirection: Axis.horizontal,
child: Row({
children: [
LimitedBox({
maxHeight: 200,
child: Container({
color: 'red',
child: Text('높이가 200으로 제한됨')
})
})
]
})
})
// 둘 다 제한
Column({
children: [
LimitedBox({
maxWidth: 400,
maxHeight: 300,
child: Container({
color: 'green',
child: Text('너비 400, 높이 300으로 제한')
})
})
]
})
Props
maxWidth
값: number (기본값: Infinity)
무한 너비 제약에서 적용할 최대 너비입니다.
부모가 이미 유한한 너비 제약을 제공하는 경우, 이 값은 무시됩니다.
LimitedBox({
maxWidth: 300, // 무한 너비 제약에서 최대 300
child: child
})
LimitedBox({
maxWidth: Infinity, // 너비 제한 없음 (기본값)
child: child
})
maxHeight
값: number (기본값: Infinity)
무한 높이 제약에서 적용할 최대 높이입니다.
부모가 이미 유한한 높이 제약을 제공하는 경우, 이 값은 무시됩니다.
LimitedBox({
maxHeight: 200, // 무한 높이 제약에서 최대 200
child: child
})
LimitedBox({
maxHeight: Infinity, // 높이 제한 없음 (기본값)
child: child
})
child
값: Widget | undefined
크기 제한이 적용될 자식 위젯입니다.
실제 사용 예제
예제 1: 리스트 뷰 내 카드 크기 제한
const LimitedCardList = ({ cards }) => {
return ListView({
padding: EdgeInsets.all(16),
children: cards.map(card =>
Container({
margin: EdgeInsets.only({ bottom: 16 }),
child: LimitedBox({
maxWidth: 600, // 카드 최대 너비 제한
child: Container({
padding: EdgeInsets.all(20),
decoration: BoxDecoration({
color: 'white',
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow({
color: 'rgba(0,0,0,0.1)',
blurRadius: 8,
offset: { x: 0, y: 2 }
})
]
}),
child: Column({
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(card.title, {
style: TextStyle({
fontSize: 18,
fontWeight: 'bold',
marginBottom: 8
})
}),
Text(card.description, {
style: TextStyle({
fontSize: 14,
color: '#666',
lineHeight: 1.4
})
}),
SizedBox({ height: 16 }),
Row({
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton({
onPressed: card.onAction,
child: Text('더보기')
})
]
})
]
})
})
})
})
)
});
};
예제 2: 가로 스크롤 이미지 갤러리
const HorizontalImageGallery = ({ images }) => {
return SingleChildScrollView({
scrollDirection: Axis.horizontal,
padding: EdgeInsets.all(16),
child: Row({
children: images.map((image, index) =>
Container({
margin: EdgeInsets.only({
right: index < images.length - 1 ? 16 : 0
}),
child: LimitedBox({
maxHeight: 300, // 이미지 최대 높이 제한
child: AspectRatio({
aspectRatio: image.aspectRatio || 1.0,
child: Container({
decoration: BoxDecoration({
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow({
color: 'rgba(0,0,0,0.2)',
blurRadius: 6,
offset: { x: 0, y: 2 }
})
]
}),
child: ClipRRect({
borderRadius: BorderRadius.circular(8),
child: Image({
src: image.url,
objectFit: 'cover'
})
})
})
})
})
})
)
})
});
};
예제 3: 반응형 텍스트 에디터
const ResponsiveTextEditor = ({ content, onChange }) => {
return Container({
padding: EdgeInsets.all(16),
child: Column({
children: [
// 도구모음
Container({
padding: EdgeInsets.all(12),
decoration: BoxDecoration({
color: '#F5F5F5',
borderRadius: BorderRadius.only({
topLeft: Radius.circular(8),
topRight: Radius.circular(8)
})
}),
child: Row({
children: [
IconButton({ icon: Icons.bold }),
IconButton({ icon: Icons.italic }),
IconButton({ icon: Icons.underline }),
Spacer(),
Text('${content.length}/1000', {
style: TextStyle({
fontSize: 12,
color: '#666'
})
})
]
})
}),
// 에디터 영역
Expanded({
child: LimitedBox({
maxWidth: 800, // 데스크톱에서 너무 넓어지지 않도록
maxHeight: 600, // 무한 높이에서 최대 높이 제한
child: Container({
width: double.infinity,
padding: EdgeInsets.all(16),
decoration: BoxDecoration({
color: 'white',
border: Border.all({ color: '#E0E0E0' }),
borderRadius: BorderRadius.only({
bottomLeft: Radius.circular(8),
bottomRight: Radius.circular(8)
})
}),
child: TextField({
value: content,
onChanged: onChange,
multiline: true,
maxLines: null,
decoration: InputDecoration({
border: InputBorder.none,
hintText: '여기에 내용을 입력하세요...'
})
})
})
})
})
]
})
});
};
예제 4: 무한 스크롤 피드
const InfiniteScrollFeed = ({ posts, onLoadMore }) => {
return NotificationListener({
onNotification: (notification) => {
if (notification.metrics.pixels >=
notification.metrics.maxScrollExtent - 200) {
onLoadMore();
}
return false;
},
child: ListView.builder({
itemCount: posts.length + 1,
itemBuilder: (context, index) => {
if (index >= posts.length) {
return Container({
padding: EdgeInsets.all(16),
child: Center({
child: CircularProgressIndicator()
})
});
}
const post = posts[index];
return Container({
margin: EdgeInsets.symmetric({ horizontal: 16, vertical: 8 }),
child: LimitedBox({
maxWidth: 700, // 포스트 최대 너비 제한
child: Container({
padding: EdgeInsets.all(16),
decoration: BoxDecoration({
color: 'white',
borderRadius: BorderRadius.circular(12),
border: Border.all({ color: '#E0E0E0' })
}),
child: Column({
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 헤더
Row({
children: [
CircleAvatar({
radius: 20,
backgroundImage: NetworkImage(post.author.avatar)
}),
SizedBox({ width: 12 }),
Expanded({
child: Column({
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(post.author.name, {
style: TextStyle({ fontWeight: 'bold' })
}),
Text(post.timestamp, {
style: TextStyle({
fontSize: 12,
color: '#666'
})
})
]
})
})
]
}),
SizedBox({ height: 12 }),
// 내용
Text(post.content),
if (post.image) ...[
SizedBox({ height: 12 }),
LimitedBox({
maxHeight: 400, // 이미지 최대 높이 제한
child: ClipRRect({
borderRadius: BorderRadius.circular(8),
child: Image({
src: post.image,
width: double.infinity,
objectFit: 'cover'
})
})
})
]
]
})
})
})
});
}
})
});
};
예제 5: 가변 그리드 레이아웃
const VariableGridLayout = ({ items }) => {
return SingleChildScrollView({
child: Wrap({
spacing: 16,
runSpacing: 16,
children: items.map((item, index) =>
LimitedBox({
maxWidth: 300, // 각 그리드 아이템 최대 너비
maxHeight: 400, // 각 그리드 아이템 최대 높이
child: Container({
width: double.infinity,
padding: EdgeInsets.all(16),
decoration: BoxDecoration({
gradient: LinearGradient({
colors: item.colors,
begin: Alignment.topLeft,
end: Alignment.bottomRight
}),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow({
color: 'rgba(0,0,0,0.1)',
blurRadius: 10,
offset: { x: 0, y: 4 }
})
]
}),
child: Column({
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon({
icon: item.icon,
size: 48,
color: 'white'
}),
SizedBox({ height: 16 }),
Text(item.title, {
style: TextStyle({
fontSize: 18,
fontWeight: 'bold',
color: 'white'
})
}),
SizedBox({ height: 8 }),
Expanded({
child: Text(item.description, {
style: TextStyle({
fontSize: 14,
color: 'rgba(255,255,255,0.8)',
lineHeight: 1.4
})
})
}),
SizedBox({ height: 16 }),
Align({
alignment: Alignment.centerRight,
child: ElevatedButton({
onPressed: item.onTap,
style: ElevatedButton.styleFrom({
backgroundColor: 'rgba(255,255,255,0.2)',
foregroundColor: 'white'
}),
child: Text('보기')
})
})
]
})
})
})
)
})
});
};
제약 조건 동작 이해
유한 제약 vs 무한 제약
// 유한 제약 환경 (LimitedBox 효과 없음)
Container({
width: 400, // 명확한 너비 제약
height: 300, // 명확한 높이 제약
child: LimitedBox({
maxWidth: 200, // 이 값은 무시됨 (부모가 이미 유한 제약)
maxHeight: 150, // 이 값은 무시됨 (부모가 이미 유한 제약)
child: Container({ color: 'blue' })
})
})
// 무한 제약 환경 (LimitedBox 효과 있음)
Column({ // Column은 세로로 무한 공간 제공
children: [
LimitedBox({
maxHeight: 200, // 이 값이 적용됨
child: Container({ color: 'red' })
})
]
})
언제 효과가 있는가?
// ✅ 효과 있는 경우들
ListView({ // 세로 무한 공간
children: [
LimitedBox({ maxHeight: 300, child: child })
]
})
Row({ // 가로 무한 공간
children: [
LimitedBox({ maxWidth: 200, child: child })
]
})
SingleChildScrollView({ // 스크롤 방향으로 무한 공간
child: LimitedBox({ maxHeight: 400, child: child })
})
// ❌ 효과 없는 경우들
Container({
width: 300, // 이미 유한 제약
child: LimitedBox({ maxWidth: 200, child: child }) // 무시됨
})
SizedBox({
height: 200, // 이미 유한 제약
child: LimitedBox({ maxHeight: 100, child: child }) // 무시됨
})
주의사항
- LimitedBox는 무한 제약에서만 작동합니다
- 부모가 이미 유한한 제약을 제공하면 아무 효과가 없습니다
- ConstrainedBox와는 다르게 조건부로만 작동합니다
- 음수 값은 허용되지 않습니다
- Infinity 값을 사용하면 제한이 없어집니다
관련 위젯
- ConstrainedBox: 항상 제약 조건을 적용하는 위젯
- SizedBox: 고정 크기를 지정하는 위젯
- UnconstrainedBox: 제약을 완전히 제거하는 위젯
- OverflowBox: 부모 크기를 무시하고 자식 크기를 허용하는 위젯
- FractionallySizedBox: 부모 크기의 비율로 자식 크기를 설정하는 위젯