Resizing

TransformableBox is used to resize its content by default. Transformable Box utilizes Box Transform to create a fully-featured Flutter implementation of the Box Transform API.

Resizable widget
  Stack(
    children: [
      TransformableBox(
        rect: rect,
        flip: flip,
        onChanged: (event) {
          setState(() {
            this.rect = event.rect;
            this.flip = event.flip;
          });
        },
        contentBuilder: (context, rect, flip) => Image.asset( // your widget goes here.
          'assets/images/landscape.jpg',
          width: rect.width,
          height: rect.height,
          fit: BoxFit.fill,
        ),
      ),
    ],
  );
The onChanged callback is a general callback that is called whenever a change is made to the box. To listen to specific changes, you can use onResized and onMoved callbacks for resizing and moving respectively. There are also more granular callbacks if desired.

By default, resizing allows flipping the rect horizontally and vertically. Flipping is done by using the Transform.scale widget with negative scale values.

Resizing is freeform by default, meaning that the user can resize the box in any direction and in any size.

Controlling Resize Modes#

The resizeModeResolver is a callback that is called whenever a resize operation is about to be performed on a given Transformable Box. This allows you to define the resize behavior at the time of the resize operation. This is useful when you want to change the resize behavior based on the state of the application.

The most common use case for this is keyboard shortcuts. A defaultResizeModeResolver function is used by default when resizing, and it's job is to listen to keyboard meta keys to change the ResizeMode.

Default Resize Mode:#

  1. ResizeMode.freeform: Default mode. When no keys are pressed.
  2. ResizeMode.scale: When Shift key is pressed.
  3. ResizeMode.symmetric: When Alt or Option key is pressed.
  4. ResizeMode.symmetricScale: When both Shift and Alt/Option keys are pressed.

See ResizeModes page for more information on the different resize modes.

Override this default behavior by providing resizeModeResolver in TransformableBox constructor to customize resizing to your liking. This can also be used to allow only certain resize modes.

Resizing with preseving aspect ratio
  TransformableBox(
    rect: rect,
    flip: flip,
    resizeModeResolver: () => ResizeMode.scale,
    onChanged: (event) {...},
    contentBuilder: (context, rect, flip) {...},
  );

This will resize the box such that the aspect ratio is preserved at all times.

Flipping#

By default, resizing allows flipping the rect horizontally and vertically. Flipping is done by using the Transform.scale widget with negative scale values internally. There are two kinds of flipping involved in resizing:

Flipping the rect while resizing#

While resizing, the rect can be flipped horizontally and vertically whenever the user drags a handle beyond the opposite side of the rect. This allows more freedom when resizing a box. However, this is not responsible for flipping the content of the box.

This behavior can be disabled by setting flipRectWhileResizing to false.

Disable flipping the rect while resizing
  TransformableBox(
    rect: rect,
    flip: flip,
    flipRectWhileResizing: false,
    onChanged: (event) {...},
    contentBuilder: (context, rect, flip) {...},
  );

Flipping the content of the box#

This is done by using the Transform.scale widget with negative scale values. This is done to allow the content to be flipped horizontally and vertically.

This behavior can be disabled by setting allowContentFlipping to false.

Disable flipping the child/content of the box
  TransformableBox(
    rect: rect,
    flip: flip,
    allowContentFlipping: false,
    onChanged: (event) {...},
    contentBuilder: (context, rect, flip) {...},
  );

You can set both flipRectWhileResizing and allowContentFlipping to false to disable flipping completely. The result will be a box that stops shrinking once it reaches a size of zero, as if it hit a wall.

Constraints#

Resizing can be constrained by providing any desirable combination of minHeight, minWidth, maxHeight and maxWidth using the constraints property.

Resizable widget with constrained resizing
  TransformableBox(
    rect: rect,
    flip: flip,
    constraints: BoxConstraints(
      minWidth: 100,
      minHeight: 100,
      maxWidth: 500,
      maxHeight: 500,
    ),
    onChanged: (event) {...},
    contentBuilder: (context, rect, flip) {...},
  );

This will disallow the box from growing or shrinking beyond 100x100 and 500x500 pixels.

Constraint callbacks#

You can listen to callbacks to listen to when the box reaches the minimum or maximum size. This is useful when you want to react to these events to show some UI indication like showing a message or changing border color.

You can listen to the onTerminalSizeReached callback to listen to when the box reaches some terminal size, vertically or horizontally to a minimum or maximum size. This is useful when you want to show a message to the user that the box has reached some resizing limit.

A terminal size is a size that is either the minimum or maximum size of the box on either axis. This can happen when the user tries to resize the box beyond the given constraints or the size reaches the clampingRect size.

There are more granular terminal callback functions if desired. You can use onMinWidthReached, onMaxWidthReached, onMinHeightReached and onMaxHeightReached callbacks to listen to the individual terminal size events. You can also use onTerminalWidthReached and onTerminalHeightReached to listen to combined terminal width and height events respectively.

onTerminalSizeReached callback
  TransformableBox(
    rect: rect,
    flip: flip,
    constraints: BoxConstraints(
      minWidth: 100,
      minHeight: 100,
      maxWidth: 500,
      maxHeight: 500,
    ),
    onTerminalSizeReached: (
        bool reachedMinWidth,
        bool reachedMaxWidth,
        bool reachedMinHeight,
        bool reachedMaxHeight,
      ) {
        // do something here.
    },
    onChanged: (event) {...},
    contentBuilder: (context, rect, flip) {...},
  );

Handle visibility#

Resizing can be disabled completely by setting the resizable property to false.

Disable resizing with the resizable bool
  TransformableBox(
    rect: rect,
    flip: flip,
    resizable: false,
    onChanged: (event) {...},
    contentBuilder: (context, rect, flip) {...},
  );

You can alternatively selectively hide handles by providing your own visibleHandles set.

Using the visibleHandles set to hide handles
  TransformableBox(
    rect: rect,
    flip: flip,
    visibleHandles: {HandlePosition.right, HandlePosition.bottom, HandlePosition.bottomRight},
    onChanged: (event) {...},
    contentBuilder: (context, rect, flip) {...},
  );

Handle Interaction#

You can selectively disable handles but keep them visible at the same time by providing your own enabledHandles set.

Using the enabledHandles set to disable handles
  TransformableBox(
    rect: rect,
    flip: flip,
    enabledHandles: {HandlePosition.right, HandlePosition.bottom, HandlePosition.bottomRight},
    onChanged: (event) {...},
    contentBuilder: (context, rect, flip) {...},
  );
You can provide an empty set to disable all the handles but keep them visible and vice versa.