Displaying images with rounded corners or even as a circle is a pretty common design requirement. Rectangular images can look rather harsh and sometimes don’t fit into a modern design.
You can’t always control the image source, especially when you are working with APIs, so premature image editing is not always an option.
That’s where runtime clipping comes into play.
Let’s see how we can create a widget that displays all kinds of images with rounded corners.
Image as a rounded rectangle
Let’s look at images with rounded corners first.
Keeping the original shape
If the rectangle is not square, then you might want to keep its shape instead of making it a square.
In this case, you can wrap your Image
widget with a ClipRRect
widget.
There are different types of Clip
widgets like ClipRect
, ClipOval
and the aforementioned ClipRRect
. The base principle is always the same: the Clip
defines boundaries. The child
widget may exceed these boundaries and if it does, then this part of the child
widget is not displayed.
An interesting example usage of Clip
(in this case ClipPath
) is cutting “holes” in widgets like it was explained in this tutorial.
ClipRect
also has an additional argument that lets us define the border radius of the corners:
1ClipRRect(
2 borderRadius: BorderRadius.circular(24),
3 child: Image.network(
4 'https://my.image.url',
5 ),
6)
Making the image square
A common use case for normalized images that have a square shape are e. g. lists that all have a thumbnail image. Independent of the actual size of the image and its proportions, you might want to display the image as a square shape with rounded borders.
Here’s how that’s done:
1ClipRRect(
2 borderRadius: BorderRadius.circular(24),
3 child: SizedBox.fromSize(
4 size: const Size.fromRadius(144),
5 child: Image.network(
6 'https://my.image.url',
7 fit: BoxFit.cover,
8 ),
9 ),
10),
With border
In addition to the rounded corners, you might want to add a border to draw the line between the image and the background.
To achieve this, you have to wrap your ClipRRect
widget with a Container
. The padding of the Container
determines the thickness of the border.
1final borderRadius = BorderRadius.circular(20);
2
3Container(
4 padding: EdgeInsets.all(8),
5 decoration: BoxDecoration(color: Colors.red, borderRadius: borderRadius),
6 child: ClipRRect(
7 borderRadius: borderRadius,
8 child: SizedBox.fromSize(
9 size: Size.fromRadius(48),
10 child: Image.network('https://my.image.url', fit: BoxFit.cover),
11 ),
12 ),
13)
Image as a circle
When you want the image to be displayed as a circle like e. g. when displaying profile images, you have basically two easy-to-use options:
- Using a
ClipOval
- Using a
CircleAvatar
The ClipOval
is just the equivalent of ClipRRect
for circles. CircleAvatar
is explicitly built for displaying profile images and has numerous options to support this whereas ClipOval
has no opinion about what’s being rendered inside the child
widget.
ClipOval
The usage of ClipOval
is as simple as ClipRRect
: most of the time, the only parameter you need to use is the child
parameter, which expects a Widget
that is being turned into a circle.
1ClipOval(
2 child: SizedBox.fromSize(
3 size: const Size.fromRadius(144),
4 child: Image.network(
5 'https://my.image.url',
6 fit: BoxFit.cover,
7 ),
8 ),
9)
CircleAvatar
With CircleAvatar
, you can achieve the very same effect but have a few more options.
foregroundImage
or the backgroundImage
option. It doesn’t matter which one to use if you don’t plan to use a fallback image.
So in comparison to the Clip
widgets, you should not use the child
property for images. If you do so, the image won’t be clipped and look like this:If you use an image source that has a loading time before being displayed, the content will be displayed in the following ordered cascade:
foregroundImage
child
backgroundImage
Meaning that it will look for foregroundImage
first. If it’s not there (or still loading), it will display child
. Below this it will display backgroundImage
if provided and loaded.
There’s also the backgroundColor
property which defaults to the primary color of the defined theme.
So if you just want to display an image, you should use the foregroundImage
property to point to your image and set the backgroundColor
to Colors.transparent
to prevent your primary color being shown while the image is being loaded:
1SizedBox.fromSize(
2 size: const Size.fromRadius(144),
3 child: CircleAvatar(
4 backgroundColor: Colors.transparent,
5 foregroundImage: Image.network(
6 'https://my.image.url',
7 ).image,
8 ),
9)
If you want to have a textual fallback for your image (such as the user’s initials), you can use the child
property to define this fallback like this:
1SizedBox.fromSize(
2 size: const Size.fromRadius(144),
3 child: CircleAvatar(
4 backgroundColor: Colors.red,
5 foregroundImage: Image.network(
6 'https://my.image.url',
7 ).image,
8 child: const Text('XX', style: TextStyle(fontSize: 32)),
9 ),
10)
Conclusion
Flutter comes up with a decent utility to display any image with rounded corners: it’s called Clip
. A Clip
widget renders only parts of the widget you provide. With the predefined Clip
widgets ClipRect
and ClipOval
, you can easily show an image with rounded corners or as a circle.
For circles, you also have the option to use the CircleAvatar
widget. It allows you to defined fallbacks to be display if the loading of the image fails or is still pending.
It depends on the desired effect, which widget is suitable for your needs. Clip
is a more generic solution whereas CircleAvatar
aims - as the name suggests - for use cases in which an avatar (user’s image or initials) are displayed.
Comment this 🤌