stacker

A collection of 3 widgets that stack their children, displaying a single child at a time, transitioning between children when a different child is displayed.

Stacker can be used modularly as a component, to drive single-page apps, or to control multi-step flows within a widget or page.

Usage

import 'package:stacker/stacker.dart';

StackSwapper

StackSwapper is the simplest of the three widgets. It accepts a single child and implicity transitions to displaying a new child when its child is updated.

/// The child being displayed by the [StackSwapper].
var child = Container(child: Text('A'));

/// Transitions [StackSwapper] to displaying a different widget.
void updateChild() {
  child = Container(child: Text('B'));
  setState(() {});
}

/// Build the [StackSwapper].
@override
Widget build(BuildContext context) {
  return StackSwapper(child);
}

StackSwitcher

StackSwitcher accepts a list of children and the index of the child that should be displayed. When the index is changed it will transition to displaying the child at the new index.

/// The children contained in [StackSwitcher]'s stack.
final children = <Widget>[
  Container(child: Text('A')),
  Container(child: Text('B')),
  Container(child: Text('C')),
  Container(child: Text('D')),
  Container(child: Text('E')),
];

/// The index of the child in [children] currently being
/// dispalyed by the [StackSwitcher].
var currentChild = 0;

/// Transitions [StackSwitcher] to displaying the next child in the list.
void nextChild() {
  currentChild = (currentChild + 1) % children.length;
  setState(() {});
}

/// Build the [StackSwitcher].
@override
Widget build(BuildContext context) {
  return StackSwitcher(children, child: currentChild);
}

Maintaining States

The states of every child in the stack can be maintained when they're not visible by setting the maintainStates, maintainAnimations, or maintainSizes parameters to true.

maintainStates will maintain the state of the child.

maintainAnimations will keep the animation ticker providers active while the children are hidden. Setting this to true will automatically set maintainStates to true.

maintainSizes will maintain the space the hidden children would occupy if they were visible. Setting this to true will automatically set maintainAnimations and maintainStates to true.

/// A [StackSwitcher] where every child's state will
/// be maintained when they're hidden.
StackSwitcher(
  children,
  child: currentChild,
  maintainStates: true,
);

An individual child's state/animation/size can be maintained by wrapping the child in a MaintainState widget.

MaintainState has 2 parameters, maintainAnimation and maintainSize. Setting maintainSize to true will automatically set maintainAnimation to true.

/// A list of children whose states will be maintained, or not,
/// independently of one another.
final children = <Widget>[
  // The state of this widget will be maintained when it is hidden.
  MaintainState(MyStatefulWidget())),

  // The state of this child will not be maintained when it is hidden.
  MyStatefulWidget(),

  // The state and animation tickers of this widget will be
  // maintained when it is hidden.
  MaintainState(
    MyStatefulWidget(),
    maintainAnimation: true,
  ),

  // The state of this child will not be maintained when it is hidden.
  MyStatefulWidget(),

  // The state, animation tickers, and size of this widget will be
  // maintained when it is hidden.
  MaintainState(
    MyStatefulWidget(),
    maintainSize: true,
  ),
];

Stacker

Stacker accepts a single child, which acts as the root of a linear history of widgets, which can be navigated (like a browser's back and forward buttons.)

New children can be built, inserted into, or removed from the stack on the fly.

There a number of direct methods for controlling and navigating the stack, which are accessed from the Stacker's instance.

All of Stacker's navigation/build methods have an optional onComplete parameter, which can be used to provide a callback that will be called a single time when the transition triggered by that method completes.

/// An instance of a [Stacker]. By storing the [Stacker] as an instance,
/// its direct methods can be used to control it.
final myStacker = Stacker(MyRootWidget());

/// Builds and transitions to a new [child].
void build(Widget child, {VoidCallback onComplete}) {
  myStacker.build(child, onComplete: onComplete);
}

/// Transitions to the child before the child currently being displayed.
void back({VoidCallback onComplete}) {
  myStacker.back(onComplete: onComplete);
}

/// Transitions to the child after the child currently being displayed.
void forward({VoidCallback onComplete}) {
  myStacker.forward(onComplete: onComplete);
}

/// Transitions to the child at [index].
void open(int index, {VoidCallback onComplete}) {
  myStacker.open(index, onComplete: onComplete);
}

/// Transitions to the child before the child currently being displayed,
/// and removing the current child and every child after it from the history.
void pop({VoidCallback onComplete}) {
  myStacker.pop(onComplete: onComplete);
}

/// Transitions to the root child.
void root({VoidCallback onComplete}) {
  myStacker.root(onComplete: onComplete);
}

Those same methods are also accessible from any of the Stacker's children via a BuildContext extension by calling context within a StatelessWidget's build method, or anywhere within a State. Note: These have the same optional parameters as their respective equivalent methods listed above and in the section below.

// Builds and transitions to a new child.
context.stacker.build(child);

// Transitions to the previous child.
context.stacker.back();

// Transitions to the next child.
context.stacker.forward();

// Transitions to the child at [index].
context.stacker.open(index);

// Transitions the previous child and clears the history.
context.stacker.pop();

// Transitions to the root child.
context.stacker.root();

Note: If a Stacker is built directly by its parent's build method, only its children will be able to access the methods to control the stacker, which may be all you need depending on the use-case.

/// The [Stacker] built here can only be controlled by
/// [MyRootWidget] or any other children added to it.
@override
Widget build(BuildContext context) {
  return Stacker(MyRootWidget());
}

Managing the History

The history can be cleared by calling clearHistory. The history is also cleared when building or appending a widget to the history, unless the build or append methods' clearHistory parameter is changed to false, or when navigating to the root child by setting the clearHistory parameter to true.

/// Builds and transitions to a new [child], clearing the history
/// by default.
///
/// If [clearHistory] is set to `false`, every child in the forward
/// history will be pushed back when the new [child] is built.
void build(Widget child, {bool clearHistory = true}) {
  myStacker.build(child, clearHistory: clearHistory);
}

/// Transitions to the root widget in the stack, clearing the history
/// if [clearHistory] is set to `true`.
void root({bool clearHistory = false}) {
  myStacker.root(clearHistory: clearHistory);
}

Children can be inserted into or removed from the history by any of the below methods.

/// Inserts a [child] into the stack before the one currently
/// being displayed.
void prepend(Widget child) {
  myStacker.prepend(child);
}

/// Inserts a [child] into the stack after the one currently
/// being displayed, clearing the history by default.
void append(Widget child, {bool clearHistory = true}) {
  myStacker.append(child, clearHistory: clearHistory);
}

/// Inserts a [child] into the stack at [index], pushing
/// every child occuring on or after [index] back.
void insert(int index, Widget child) {
  myStacker.insert(index, child);
}

/// Removes the first instance of [child] from the stack.
void remove(Widget child) {
  myStacker.remove(child);
}

/// Removes the child from the stack at [index].
void removeAt(int index) {
  myStacker.removeAt(index);
}

/// Removes every widget from stack after the one currently
/// being diplayed, clearing the forward history.
///
/// If [skip] is `> 0`, that number of widgets after the one currently
/// being displayed will be retained in the history.
void clearHistory([int skip = 0]) {
  myStacker.clearHistory(skip);
}

All of the above methods are also accessible to a Stacker's children via the BuildContext extension. Note: These methods have the same optional parameters as their respective equivalents listed above.

// Inserts a child into the stack before the one currently
// being displayed.
context.stacker.prepend(child);

// Inserts a child into the stack after the one currently
// being displayed, clearing the history by default.
context.stacker.append(child);

// Inserts a [child] into the stack at [index], pushing
// every child occuring on or after [index] back.
context.stacker.insert(index, child);

// Removes the first instance of [child] from the stack.
context.stacker.remove(child);

// Removes the child from the stack at [index].
context.stacker.removeAt(index);

// Removes every widget from stack after the one currently
// being diplayed
context.stacker.clearHistory();

Android Back Button

The backButton parameter can be set to true to have the Stacker intercept the Android back button and navigate backwards in the Stacker's history when it's pressed.

/// This [Stacker] can be navigated with Android's back button.
Stacker(
  MyRootWidget(),
  backButton: true,
);

If the root child is being displayed, the back button will defer to the default behavior.

Maintaining States

A Stacker's childrens' states can be maintained in the same way as a StackSwitchers', either by setting the maintainStates, maintainAnimations, or maintainSizes parameters to true, or by wrapping individual children in a MaintainState widget.

Parameters

Each of the widgets have a number of shared parameters used to provide callbacks or to customize their transition animation.

Transitions

The transitions between children are handled by a FadeAndTranslate widget, which fades each child in/out while translating their positional offsets.

There are several parameters used to specify the duration, offset, and additional behavior of the transitions.

/// A [StackSwapper] built with the default parameters..
StackSwapper(
  myWidget,
  // The duration of the transition animation.
  transitionDuration: Duration(milliseconds: 240),
  // The offset the children translate to during the transition.
  transitionTranslation: Offset(0.0, -24.0),
  // If `true`, the children will translate in opposite directions,
  // if `false`, they'll translate in the same direction.
  invertTranslations: true,
  // If `true`, the first child built will transition from being hidden
  // to being visible, if `false`, it will be built as visible.
  transitionFirstChild: false,
),

Note: StackSwitcher and Stacker have the same default values for their equivalent parameters as the STackSwapper above.

Callbacks

Each of the widgets also accepts two callbacks, onSwitchStart and onSwitchComplete, which are called each time a transition starts and completes, respectively.

Stacker(
  myWidget,
  // A callback called every time a transition starts.
  onSwitchStart: () => print('Transition starting.'),
  // A callback called every time a transition has completed.
  onSwitchComplete: () => print('Transition completed.'),
);

Libraries

stacker
A collection of 3 widgets that stack their children, displaying a single child at a time, transitioning between children when a different child is displayed. [...]