개요
IntrinsicWidth는 자식 위젯의 고유(intrinsic) 너비로 크기를 조정하는 위젯입니다.
IntrinsicWidth는 자식 위젯이 원하는 고유 너비를 가질 수 있도록 강제합니다. 이는 Column 내에서 여러 자식들의 너비를 일치시키거나, 자식의 내용에 따라 동적으로 너비를 조정해야 할 때 유용합니다. IntrinsicHeight와 마찬가지로 성능 비용이 높으므로 신중하게 사용해야 합니다.
언제 사용하나요?
- Column의 자식들이 모두 같은 너비를 가져야 할 때
- 자식 위젯의 내용에 따라 너비가 결정되어야 할 때
- 다양한 너비의 위젯들을 일치시켜야 할 때
- 버튼이나 칩의 너비를 내용에 맞춰야 할 때
- CrossAxisAlignment.stretch가 작동하지 않을 때
기본 사용법
IntrinsicWidth({
child: Column({
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container({
color: 'blue',
height: 50,
child: Text('짧은')
}),
Container({
color: 'red',
height: 50,
child: Text('이것은 훨씬 더 긴 텍스트입니다')
})
]
})
})
Props
child
값: Widget | undefined
고유 너비로 크기가 조정될 자식 위젯입니다.
IntrinsicWidth({
child: Container({
color: 'blue',
child: Text('내용에 맞춰 너비가 조정됩니다')
})
})
작동 원리
IntrinsicWidth는 다음과 같이 작동합니다:
- 자식 위젯에게 “주어진 높이에서 당신의 최소 너비는 얼마입니까?”라고 묻습니다.
- 자식이 응답한 너비를 사용하여 제약 조건을 만듭니다.
- 이 제약 조건으로 자식을 레이아웃합니다.
이 과정은 추가적인 레이아웃 패스가 필요하므로 성능 비용이 발생합니다.
실제 사용 예제
예제 1: 동일한 너비의 버튼들
const UniformWidthButtons = () => {
return IntrinsicWidth({
child: Column({
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ElevatedButton({
onPressed: () => console.log('저장'),
child: Text('저장')
}),
SizedBox({ height: 8 }),
ElevatedButton({
onPressed: () => console.log('저장하고 계속하기'),
child: Text('저장하고 계속하기')
}),
SizedBox({ height: 8 }),
ElevatedButton({
onPressed: () => console.log('취소'),
child: Text('취소')
})
]
})
});
};
예제 2: 동적 너비의 배지
const DynamicBadge = ({ text, color }) => {
return IntrinsicWidth({
child: Container({
padding: EdgeInsets.symmetric({ horizontal: 12, vertical: 6 }),
decoration: BoxDecoration({
color: color || 'blue',
borderRadius: BorderRadius.circular(16)
}),
constraints: Constraints({ minWidth: 32 }),
child: Center({
child: Text(text, {
style: TextStyle({
color: 'white',
fontSize: 14,
fontWeight: 'bold'
})
})
})
})
});
};
// 사용 예시
Row({
children: [
DynamicBadge({ text: '3', color: 'red' }),
SizedBox({ width: 8 }),
DynamicBadge({ text: '99+', color: 'orange' }),
SizedBox({ width: 8 }),
DynamicBadge({ text: 'NEW', color: 'green' })
]
})
예제 3: 드롭다운 메뉴
const DropdownMenu = ({ items, selectedIndex }) => {
return IntrinsicWidth({
child: Container({
decoration: BoxDecoration({
border: Border.all({ color: '#E0E0E0' }),
borderRadius: BorderRadius.circular(8)
}),
child: Column({
crossAxisAlignment: CrossAxisAlignment.stretch,
children: items.map((item, index) =>
GestureDetector({
onTap: () => console.log(`선택: ${item}`),
child: Container({
padding: EdgeInsets.symmetric({ horizontal: 16, vertical: 12 }),
decoration: BoxDecoration({
color: index === selectedIndex ? '#E3F2FD' : 'white',
border: index > 0 ? Border({
top: BorderSide({ color: '#E0E0E0' })
}) : null
}),
child: Row({
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(item, {
style: TextStyle({
color: index === selectedIndex ? '#2196F3' : '#333',
fontWeight: index === selectedIndex ? 'bold' : 'normal'
})
}),
if (index === selectedIndex) Icon({
icon: Icons.check,
size: 18,
color: '#2196F3'
})
]
})
})
})
)
})
})
});
};
예제 4: 툴팁 컨테이너
const TooltipContainer = ({ title, description }) => {
return IntrinsicWidth({
child: Container({
padding: EdgeInsets.all(12),
decoration: BoxDecoration({
color: '#333333',
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow({
color: 'rgba(0, 0, 0, 0.2)',
blurRadius: 8,
offset: { x: 0, y: 4 }
})
]
}),
constraints: Constraints({ maxWidth: 250 }),
child: Column({
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, {
style: TextStyle({
color: 'white',
fontSize: 14,
fontWeight: 'bold'
})
}),
if (description) ...[
SizedBox({ height: 4 }),
Text(description, {
style: TextStyle({
color: 'rgba(255, 255, 255, 0.8)',
fontSize: 12
})
})
]
]
})
})
});
};
예제 5: 가격 테이블 헤더
const PriceTableHeader = ({ plans }) => {
return Row({
children: plans.map((plan, index) =>
Expanded({
child: IntrinsicWidth({
child: Container({
margin: EdgeInsets.symmetric({ horizontal: 4 }),
padding: EdgeInsets.all(16),
decoration: BoxDecoration({
color: plan.featured ? '#2196F3' : '#F5F5F5',
borderRadius: BorderRadius.circular(8),
border: plan.featured ? null : Border.all({ color: '#E0E0E0' })
}),
child: Column({
children: [
if (plan.featured) Container({
padding: EdgeInsets.symmetric({ horizontal: 8, vertical: 4 }),
margin: EdgeInsets.only({ bottom: 8 }),
decoration: BoxDecoration({
color: '#FF9800',
borderRadius: BorderRadius.circular(4)
}),
child: Text('인기', {
style: TextStyle({
color: 'white',
fontSize: 12,
fontWeight: 'bold'
})
})
}),
Text(plan.name, {
style: TextStyle({
fontSize: 20,
fontWeight: 'bold',
color: plan.featured ? 'white' : '#333'
})
}),
SizedBox({ height: 8 }),
Text(`₩${plan.price.toLocaleString()}`, {
style: TextStyle({
fontSize: 28,
fontWeight: 'bold',
color: plan.featured ? 'white' : '#2196F3'
})
}),
Text('/월', {
style: TextStyle({
fontSize: 14,
color: plan.featured ? 'rgba(255, 255, 255, 0.8)' : '#666'
})
})
]
})
})
})
})
)
});
};
성능 고려사항
IntrinsicWidth는 IntrinsicHeight와 동일한 성능 특성을 가집니다:
- O(N²) 복잡도: 깊이 중첩된 IntrinsicWidth는 지수적으로 느려집니다.
- 레이아웃 패스: 추가적인 측정 단계가 필요합니다.
- 스크롤 뷰에서 주의: 많은 아이템이 있는 리스트에서는 피하세요.
대안
성능이 중요한 경우 다음 대안을 고려하세요:
- 고정 너비 사용: 가능하면 명시적인 너비를 지정
- ConstrainedBox: 최소/최대 너비 제약만 필요한 경우
- Wrap 위젯: 동적 레이아웃이 필요한 경우
- LayoutBuilder: 부모 크기에 따라 조정이 필요한 경우
주의사항
- IntrinsicWidth는 성능 비용이 높으므로 꼭 필요한 경우에만 사용하세요.
- 깊이 중첩된 IntrinsicWidth는 피하세요.
- 무한 너비 제약 조건에서는 작동하지 않습니다.
- Row 내에서 Expanded와 함께 사용할 때는 주의가 필요합니다.
관련 위젯
- IntrinsicHeight: 자식의 고유 높이로 크기 조정
- Wrap: 자동 줄바꿈이 가능한 유연한 레이아웃
- FittedBox: 자식을 사용 가능한 공간에 맞춤
- ConstrainedBox: 크기 제약을 적용