Overview
AspectRatio is a widget that sizes its child to a specific aspect ratio (width-to-height ratio). It maintains a consistent ratio between width and height while displaying the child at the largest possible size.
Reference: https://api.flutter.dev/flutter/widgets/AspectRatio-class.html
When to Use
- When maintaining original aspect ratios of images or videos
- When creating elements with consistent ratios in responsive design
- When maintaining consistent appearance of cards or tiles
- When fixing aspect ratios of charts or graphs
- When preserving logo or icon proportions
Basic Usage
// 16:9 ratio
AspectRatio({
aspectRatio: 16 / 9,
child: Container({
color: 'blue',
child: Center({
child: Text('16:9')
})
})
})
// Square (1:1 ratio)
AspectRatio({
aspectRatio: 1.0,
child: Image({
src: '/assets/profile.jpg',
objectFit: 'cover'
})
})
// Golden ratio
AspectRatio({
aspectRatio: 1.618,
child: Card({
child: Center({
child: Text('Golden Ratio')
})
})
})
Props
aspectRatio (required)
Type: number
The ratio of width to height. Must be greater than 0 and finite.
Common aspect ratios:
1.0
: Square (1:1)16/9
(~1.78): Widescreen video4/3
(~1.33): Traditional TV/monitor3/2
(1.5): 35mm film1.618
: Golden ratio9/16
(~0.56): Portrait mobile screen
// Various aspect ratio examples
AspectRatio({
aspectRatio: 16 / 9, // Widescreen
child: VideoPlayer({ url: 'video.mp4' })
})
AspectRatio({
aspectRatio: 1.0, // Square
child: ProfileImage({ src: 'avatar.jpg' })
})
AspectRatio({
aspectRatio: 3 / 4, // Portrait
child: ProductCard({ product })
})
child (optional)
Type: Widget | undefined
The child widget to which the aspect ratio will be applied. Even without a child, AspectRatio will occupy space with the specified ratio.
Size Calculation
AspectRatio determines its size in the following order:
- If constraints are tight, use those dimensions
- If max width is finite, calculate height based on width
- If width is infinite, calculate width based on max height
- Adjust if calculated size exceeds constraints
- Finally maintain ratio within min/max constraints
Practical Examples
Example 1: Video Player
const VideoContainer = ({ videoUrl, title }) => {
return Column({
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AspectRatio({
aspectRatio: 16 / 9,
child: Container({
color: 'black',
child: Stack({
children: [
VideoPlayer({ url: videoUrl }),
Positioned({
bottom: 0,
left: 0,
right: 0,
child: Container({
padding: EdgeInsets.all(8),
decoration: BoxDecoration({
gradient: LinearGradient({
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: ['transparent', 'rgba(0,0,0,0.7)']
})
}),
child: Text(title, {
style: TextStyle({
color: 'white',
fontSize: 16
})
})
})
})
]
})
})
})
]
});
};
Example 2: Responsive Card Grid
const ResponsiveCardGrid = ({ items }) => {
return GridView({
crossAxisCount: 3,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
children: items.map(item =>
AspectRatio({
aspectRatio: 3 / 4,
child: Container({
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({
children: [
Expanded({
flex: 3,
child: ClipRRect({
borderRadius: BorderRadius.vertical({
top: Radius.circular(12)
}),
child: Image({
src: item.imageUrl,
objectFit: 'cover',
width: double.infinity,
height: double.infinity
})
})
}),
Expanded({
flex: 2,
child: Padding({
padding: EdgeInsets.all(12),
child: Column({
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.title, {
style: TextStyle({
fontSize: 16,
fontWeight: 'bold'
}),
maxLines: 2
}),
Spacer(),
Text(item.price, {
style: TextStyle({
fontSize: 18,
color: '#FF5733',
fontWeight: 'bold'
})
})
]
})
})
})
]
})
})
})
)
});
};
Example 3: Profile Image
const ProfileAvatar = ({ imageUrl, name, size = 120 }) => {
return Container({
width: size,
child: AspectRatio({
aspectRatio: 1.0, // Square
child: Container({
decoration: BoxDecoration({
shape: BoxShape.circle,
border: Border.all({
color: '#E0E0E0',
width: 3
})
}),
child: ClipOval({
child: imageUrl ?
Image({
src: imageUrl,
objectFit: 'cover',
width: double.infinity,
height: double.infinity
}) :
Container({
color: '#F0F0F0',
child: Center({
child: Text(
name.split(' ').map(n => n[0]).join('').toUpperCase(),
{
style: TextStyle({
fontSize: size / 3,
fontWeight: 'bold',
color: '#666'
})
}
)
})
})
})
})
})
});
};
Example 4: Chart Container
const ChartContainer = ({ chartData, title }) => {
return Card({
child: Padding({
padding: EdgeInsets.all(16),
child: Column({
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(title, {
style: TextStyle({
fontSize: 20,
fontWeight: 'bold'
})
}),
SizedBox({ height: 16 }),
AspectRatio({
aspectRatio: 2.0, // Width is twice the height
child: Container({
decoration: BoxDecoration({
border: Border.all({
color: '#E0E0E0'
}),
borderRadius: BorderRadius.circular(8)
}),
child: LineChart({
data: chartData,
padding: EdgeInsets.all(20)
})
})
})
]
})
})
});
};
Example 5: Banner Image
const PromotionBanner = ({ imageUrl, title, subtitle, onTap }) => {
return GestureDetector({
onTap: onTap,
child: Container({
margin: EdgeInsets.symmetric({ horizontal: 16 }),
child: AspectRatio({
aspectRatio: 21 / 9, // Ultra-wide banner
child: Container({
decoration: BoxDecoration({
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow({
color: 'rgba(0,0,0,0.15)',
blurRadius: 10,
offset: { x: 0, y: 4 }
})
]
}),
clipBehavior: Clip.hardEdge,
child: Stack({
fit: StackFit.expand,
children: [
Image({
src: imageUrl,
objectFit: 'cover'
}),
Container({
decoration: BoxDecoration({
gradient: LinearGradient({
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
'rgba(0,0,0,0.7)',
'transparent'
]
})
})
}),
Padding({
padding: EdgeInsets.all(24),
child: Column({
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, {
style: TextStyle({
color: 'white',
fontSize: 28,
fontWeight: 'bold'
})
}),
SizedBox({ height: 8 }),
Text(subtitle, {
style: TextStyle({
color: 'rgba(255,255,255,0.9)',
fontSize: 16
})
})
]
})
})
]
})
})
})
})
});
};
Important Notes
- aspectRatio must be greater than 0 and finite
- May not maintain desired ratio if parent constraints are insufficient
- Size is determined based on child size with infinite constraints
- Can impact performance during intrinsic size calculations
- Proper constraints must be provided when used within Stack
Related Widgets
- FractionallySizedBox: Sizes based on parent’s dimensions
- SizedBox: Specifies fixed dimensions
- Container: Can control ratio with constraints
- FittedBox: Scales child to fit parent
- Align: Sizes using widthFactor and heightFactor