A Deep Dive Into Stack and IndexedStack in Flutter

A Deep Dive Into Stack and IndexedStack in Flutter

二月 22, 2019

Introduction

A Stack widget allows us to make multiple widgets overlay each other.This
not only allows brilliant custom designs but also some really cool animations.

There are two main types of Stacks:

  1. Stack
  2. IndexedStack

Let’s explore the use of Stacks.

Stack

The Stack widget allows us to put up multiple layers of widgets onto the screen.

The widget takes multiple children and orders them from bottom to top.So the first
item is the bottommost and the last is the topmost.

1
2
3
4
5
6
7
Stack(
children: <Widget>[
BottomWidget(),
MiddleWidget(),
TopWidget(),
]
)

A visual representation of the layers of the Stack:



This size of the Stack is the size of the largest member in the layar,So if the
bottom layer covers the complete screen then the size of the Stack is the
complete screen.

Each member in the stack needs to be positioned or aligned,or else it ends up
in the top left corner bu default.

As an example,let’s take three containers of shrinking size:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Deep Dive Flutter'),
),
body: Stack(
children: <Widget>[
Container(
color: Colors.green,
),
Container(
color: Colors.blue,
height: 300.0,
width: 300.0,
),
Container(
color: Colors.pink,
height: 150.0,
width: 150.0,
)
],
));
}

This gives us:



If you notice,the containers which are smaller have a lot more area to go to
and hence default to top left.To change this,you can align or position your
widget using the Align or Positioned widget.

An Align widget usually takes widget to extreme positions.So for example,if
we enter top-right,we need to add extra padding to keep it neat and tidy.A
Positioned widget combines these two things and lets us keep one Postioned
widget instead of an Align and Padding.We will see how in a while.

We’ll alter our example to use Align and Postitioned.Let’s simply wrap our
containers in an Align and then a Positioned widget.

Note: Positioned has to be a child of a Stack.There cannot be another
widget in between the stack and the widget.

To simply align a widget:

1
2
3
4
5
6
7
8
Align(
alignment: Alignment.topRight,
child: Container(
color: Colors.pink,
height: 150.0,
width: 150.0,
)
)



Here we align it to the top right.Usually,this isn’t what we actually want since
we need some padding to the container from the edges.We can either use padding
with Align or Positioned for more fine-grained control.

Positioned has multiple types,we’ll focus on the default.
It accepts values from all four sides telling the child how far it should be away
from that respective side.If no values is given,it shrinks to the lowest possible
size.

Here,we give a value to the top and right.In effect,what this means is that
the child will be 40.0 away from top and right and no other constraints on the
other sides.Hence,it is also aligned to the top and right sides by definition.

1
2
3
4
5
6
7
8
9
Positioned(
right: 40.0,
top: 40.0,
child: Container(
color: Colors.pink,
height: 150.0,
width: 150.0,
),
)

This gives us:



Hence,Positioned is a better widget to use in a Stack than Align + Padding
but there’s no real harm in either.

There are also other types of Positioned.A few of them are:

Positioned.fill()

Sets top,right,bottom,left to 0.0 by default unless override.Hence,it fills
the screen by default since distance from all four sides is 0.0.

Positioned.formRect()

Creates a Positioned object from a given Rect.

Before we move on to creating somthing with Stack,let’s take a look at the
second type.

IndexedStack

An IndexedStack is a stack where only one element is displyed at one time by
its index.

1
2
3
4
5
6
7
8
IndexedStack(
index:1,
children: <widget>[
BottomWidget(),
MidWidget(),
TopWidget(),
]
)

It takes children like a usual Stack but in contrast to it,only displays one
child at one time.In away,it’s not a stack and more of a way to easily switch
between children when you need to.

Let’s see an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Deep Dive Flutter'),
),
body: IndexedStack(
index: 0,
children: <Widget>[
Container(
color: Colors.green,
),
Container(
alignment: Alignment.bottomLeft,
color: Colors.blue,
height: 300.0,
width: 300.0,
),
Container(
color: Colors.pink,
height: 150.0,
width: 150.0,
)
],
));
}

When we wrap the same elements with IndexedStack and give it the index
0,it shows the bottommost child.



The upper two children are not shown as the index is 0.

Note: The size is still the size of the largest element.

IndexedStack also directly takes an alignment parameter and aligns all
children accordingly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Deep Dive Flutter'),
),
body: IndexedStack(
index: 1,
children: <Widget>[
Container(
color: Colors.green,
),
Container(
alignment: Alignment.bottomLeft,
color: Colors.blue,
height: 300.0,
width: 300.0,
),
Container(
color: Colors.pink,
height: 150.0,
width: 150.0,
)
],
alignment: Alignment.topRight,
));
}

Let’s make something with Stack



This is a relatively straightforward example of where we can use a Stack.

An image here is overlaid by a card which is positioned at the bottom center.

Step 1) Add a Stack

1
2
3
4
Stack(
children: <Widget>[
],
)

Step 2) Add a background

Here,I’m simply using a container with an image inside it

1
2
3
4
5
6
7
8
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/new_york.jpg'),
fit: BoxFit.fitHeight,
)
)
)

Step 3) Add a card on top with the details

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Card(
elevation: 8.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: Text('New York',
style: TextStyle(
fontSize: 20.0, fontWeight: FontWeight.bold)),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Lorem Ipsum is simply dummy text of the printing and'
"typesetting industry.Lorem Ipsum has been the industry's standard"
'dummy text ever since the 1500s,when an unknown printer took a '
'galley of type and scrambled it to make a type sspecimen book,'),
)
],
),
)

Step 4) Position the card correctly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Positioned(
bottom: 48.0,
left: 10.0,
right: 10.0,
child: Card(
elevation: 8.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: Text('New York',
style: TextStyle(
fontSize: 20.0, fontWeight: FontWeight.bold)),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Lorem Ipsum is simply dummy text of the printing and'
"typesetting industry.Lorem Ipsum has been the industry's standard"
'dummy text ever since the 1500s,when an unknown printer took a '
'galley of type and scrambled it to make a type sspecimen book,'),
)
],
),
),
)

And we’re done!

The final code is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import 'package:flutter/material.dart';

class DeepDiveHome extends StatefulWidget {
_DeepDiveHomeState createState() => _DeepDiveHomeState();
}

class _DeepDiveHomeState extends State<DeepDiveHome> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/new_york.jpg'),
fit: BoxFit.fitHeight)),
),
Positioned(
bottom: 48.0,
left: 10.0,
right: 10.0,
child: Card(
elevation: 8.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: Text('New York',
style: TextStyle(
fontSize: 20.0, fontWeight: FontWeight.bold)),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Lorem Ipsum is simply dummy text of the printing and'
"typesetting industry.Lorem Ipsum has been the industry's standard"
'dummy text ever since the 1500s,when an unknown printer took a '
'galley of type and scrambled it to make a type sspecimen book,'),
)
],
),
),
)
],
),
);
}
}