Flutter Transform with challenges
In this story, you will find some examples to learn about the Transform
widget and 2 challenges to try at the end. We leave Rotation for the next story.
When it comes to transforming a container and its child widget tree, we can choose between:
- using the container’s
transform
property - wrapping the container with a
Transform
widget
I prefer the second option: wrapping the Container
widget in a Transform
widget because this way we take advance of theTransform
widget properties like origin. We can choose an origin for our transformation.
We will see here the four transforms we can do using the Transform widget:
- Scale
- Skew
- Translate
Rotate Transform will be discussed in the next story.
Table of Contents
Examples and theory
- Definition
- Constructor vs. transform property
- Scale
•Transform.scale
constructor
• scale argument
• alignment argument
• Scale down (0.5) with the origin in the top-left corner
• Scale down (0.5) with the origin in the bottom-right corner
• Scale down (0.5) with the origin in the center
• Scale-up (1.7) with the origin in the top-left corner - Skew
• There is not aTransform.skew
constructor
• alignment argument
• Before transforming…
• Skew along the X-axis with positive alpha
• Skew along the X-axis with negative alpha
• Changing the origin of the skew
• Skewing along the Y-axis - Translate
•Transform.translate
constructor
• scale argument
• Before translating…
• Translate in the X-axis
• Translate in the Y-axis
• Translate in both X and Y axes
• Translate and compose
Challenges
Rotate Transform will be discussed in the next story.
Read the following theory and examples to get background and ideas to solve the challenges you will find later.
EXAMPLES AND THEORY
Definition
Transform is a widget that applies a transformation before painting its child, which means the transformation is not taken into account when calculating how much space this widget’s child (and thus this widget) consumes.
Constructor vs. transform property
The Transform
widget has a transform
property with which we can tell the operation’s type we want: Scale, Skew, Translate, Rotate, or composition of some of them, and also describe how this transform operation will be.
However, instead of using the transform
property of the Transform
widget, we will try to use the specific constructor if it exists. I mean:
Transform.scale
Transform.translate
Transform.rotate
(we will discuss it in the next story)
There is not a constructor for skew, so we will use the Transform
widget with the transform
property: Transform.skewX(double alpha)
or Transform.skewY(double alpha)
or Transform.skewZ(double alpha).
We will see how to use it throughout the examples in this story.
Scale
Transform.scale constructor
As we have already said, we can use the constructor Transform.scale
instead of transform
property of Transform widget.
scale argument
The
scale
argument must not be null. It gives the scalar by which to multiply thex
andy
axes.
alignment argument
The
alignment
controls the origin of the scale; by default, this is the center of the box.
The origin of the scale is the point that maintains the same in the scale operation, is the point towards and from which the scale is done.
Let’s see some examples.
Before transforming…
Scale down (0.5) with the origin in the top-left corner
Transform.scale(
alignment: Alignment.topLeft,
scale: 0.50,
child: Container(
color: Colors.teal,
width: 180,
height: 180,
),
)
Transform.scale
is a widget. Now we put this widget in the widget tree of our App and, in the simplest way, we set it as the body
property of our App’s Scaffold
.
MaterialApp(
title: 'MyApp',
home: Scaffold(
appBar: AppBar(
title: Text('Translate'),
),
body: Transform.scale(
alignment: Alignment.topLeft,
scale: 0.50,
child: Container(
color: Colors.teal,
width: 180,
height: 180,
),
),
),
)
Scale down (0.5) with the origin in the bottom-right corner
Transform.scale(
alignment: Alignment.bottomRight,
scale: 0.50,
child: Container(
color: Colors.teal,
width: 180,
height: 180,
),
)
Scale down (0.5) with the origin in the center
Transform.scale(
alignment: Alignment.center,
scale: 0.50,
child: Container(
color: Colors.teal,
width: 180,
height: 180,
),
)
Scale-up (1.7) with the origin in the top-left corner
Transform.scale(
alignment: Alignment.topLeft,
scale: 1.70,
child: Container(
color: Colors.teal,
width: 180,
height: 180,
),
)
Skew
There is not a Transform.skew constructor.
So we will use the Transform
widget with the transform
property: Transform.skewX(double alpha)
or Transform.skewY(double alpha)
or Transform.skewZ(double alpha).
alignment argument
The
alignment
controls the origin of the skew; by default, this is the top-left corner (unlike the origin of the scale which remember that was the center)
Before transforming…
Before seeing how skew transformation works, we are going first to prepare the scene. Before skewing the blue container, we will put it inside another one, a grey-background container, which will be used as a background to see clearer the skewed blue container. We will also use this grey-background container to define the margins to the screen top and left sides. We do not want to place the margin definition in the blue-container because, in that case, the skew operation would affect the margins too.
Container( //grey-background container
margin: EdgeInsets.only(top: 60, left: 60),
color: Color(0xFFCCCCCC), //grey
width: double.infinity,
height: 240,
alignment: Alignment.topLeft, //align its child (blue-container)
child: Container( // blue container
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(10.0),
),
width: 220,
height: 120,
),
)
The blue container will be the target of the skew transformation. Let’s see it!
Skew along the X-axis with positive alpha
Container( //grey-background container
margin: EdgeInsets.only(top: 60, left: 60),
color: Color(0xFFCCCCCC), //grey
width: double.infinity,
height: 240,
alignment: Alignment.topLeft, //align its child (blue-container)
child: Transform(
transform: Matrix4.skewX(0.3),
child: Container(
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(10.0),
),
width: 220,
height: 120,
),
),
)
If we do not give value for the alignment
property of the Transform
widget, then the skew transformation’s origin will get the default value (top-left corner).
Skew along the X-axis with negative alpha
Container( //grey-background container
margin: EdgeInsets.only(top: 60, left: 60),
color: Color(0xFFCCCCCC), //grey
width: double.infinity,
height: 240,
alignment: Alignment.topLeft, //align its child (blue-container)
child: Transform(
transform: Matrix4.skewX(-0.3),
child: Container(
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(10.0),
),
width: 220,
height: 120,
),
),
)
Changing the origin of the skew
Now we will apply a skew transformation but changing the origin from the default value (top-left corner) to the bottom-left corner (in this case).
We can do this by setting the alignment
property of the Transform
widget to Alignment.bottomLeft
value.
Container( //grey-background container
margin: EdgeInsets.only(top: 60, left: 60),
color: Color(0xFFCCCCCC),
width: double.infinity,
height: 240,
alignment: Alignment.topLeft, //align its child (blue-container) child: Transform(
transform: Matrix4.skewX(-0.3),
alignment: Alignment.bottomLeft, //changing the origin
child: Container(
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(10.0),
),
width: 220,
height: 120,
),
),
)
Skewing along the Y-axis
Container( //grey-background container
margin: EdgeInsets.only(top: 60, left: 60),
color: Color(0xFFCCCCCC),
width: double.infinity,
height: 240,
alignment: Alignment.topLeft, //align its child (blue-container)
child: Transform(
transform: Matrix4.skewY(0.3),
child: Container(
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(10.0),
),
width: 220,
height: 120,
),
),
)
Translate
Transform.translate constructor
We will use the constructor Transform.translate
instead of transform
property of Transform widget.
offset argument
It must not be null. It specifies the translation.
Before translating…
The design below is the initial container before being translated.
Container(
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(15.0),
),
width: 100,
height: 100,
)
Translate in the X-axis
Transform.translate(
offset: Offset(100, 0),
child: Container(
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(15.0),
),
width: 100,
height: 100,
),
)
Translate in the Y-axis
Transform.translate(
offset: Offset(0, 100),
child: Container(
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(15.0),
),
width: 100,
height: 100,
),
)
Translate in both X and Y axes
Transform.translate(
offset: Offset(100, 100),
child: Container(
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(15.0),
),
width: 100,
height: 100,
),
)
Translate and compose
We are going now to compose several containers, some of them being translated.
Every child widget is a translated version of its parent.
Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(15.0),
),
width: 100,
height: 100,
child: Transform.translate(
offset: Offset(0, 60),
child: Container(
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(15.0),
),
width: 100,
height: 100,
child: Transform.translate(
offset: Offset(0, 60),
child: Container(
decoration: BoxDecoration(
color: Colors.amber,
borderRadius: BorderRadius.circular(15.0),
),
width: 100,
height: 100,
),
),
),
),
)
CHALLENGES
Now, it is time to put into practice what you have just learned. Enjoy the following challenges and take them seriously. They will boost your level !!
The first part of the challenge
Could you achieve the right design from the left design, in the screenshot below, by scaling the text up (not by increasing the text’s size)?
Left-design code:
Center(
child: Text(
'Scale',
style: TextStyle(
fontSize: 36,
color: Colors.black,
),
),
)
And now, stop reading and try to do it by yourself before seeing my solution.
Good luck!
My solution
This is not the only possible solution; it is just my solution…
Center(
child: Transform.scale(
alignment: Alignment.center,
scale: 24,
child: Text(
'Scale',
style: TextStyle(
fontSize: 36,
color: Colors.black,
),
),
),
)
The second part of the challenge
Once you got the first part of the challenge, now you must use the previous fragment of code and apply another Transform.scale
over the existing Transform.scale
to go back to the left design (original size).
Try to do it by yourself before seeing my solution.
Good luck!
My solution
This is not the only possible solution; it is just my solution…
Center(
child: Transform.scale(
alignment: Alignment.center,
scale: (1 / 24).toDouble(),
child: Transform.scale(
alignment: Alignment.center,
scale: 24,
child: Text(
'Scale',
style: TextStyle(
fontSize: 36,
color: Colors.black,
),
),
),
),
)
The challenge
Considering the following colors:
const colors = [
Color(0xFF2257A3),
Color(0xFF2E22A3),
Color(0xFF6E22A3),
Color(0xFFA32296),
Color(0xFFA32357),
Color(0xFFA32D22)
];
The challenge consists of creating the design below by composing containers and using translation.
Try to do it the way you want, and once you get it done, I encourage you to do it again by using a recursive method to get the widget tree (if this method was not your first choice).
Try to do it by yourself before seeing my solution.
Good luck!
My solution
This is not the only possible solution; it is just my solution…
I have created a separate widget for this exercise. To do it more reusable, it receives a list of colors as a parameter in the constructor.
class Challenge extends StatelessWidget {
final List<Color> colors;
Challenge(this.colors); @override
Widget build(BuildContext context) {
return getWidget(0);
}
Widget getWidget(int index) {
if (index < colors.length) {
return Transform.translate(
offset: (index == 0) ? Offset(0, 0) : Offset(40, 40),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: colors[index],
),
width: 100,
height: 100,
child: getWidget(index + 1),
),
);
}
else
return null;
}
}
We can call it as follows:
const kColors = [
Color(0xFF2257A3),
Color(0xFF2E22A3),
Color(0xFF6E22A3),
Color(0xFFA32296),
Color(0xFFA32357),
Color(0xFFA32D22)
];void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Challenge'),
),
body: Challenge(kColors),
),
),
);
}
You can do it even more reusable if you pass the circles' size and the translation’s offset as parameters.
If you have found this article enjoyable or it has helped you in some way, give me many 👏.Thanks for reading me!