Skip to content

Feature request: Add a widget to relatively position a child to its parent #27547

@letsar

Description

@letsar

In some cases it can be useful to relatively position a child to its parent.
For example let's say we want a container positioned at 10% from the left edge of its parent, 20% from the top, 30% from the right and 40% from the bottom:

capture d ecran 2019-02-05 a 16 23 19

As discussed in #24475, we can use a combination of Align and FractionallySizedBox for this, but this is not straightforward:

import 'package:flutter/material.dart';

void main() => runApp(MyDemoWidget());

class MyDemoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // The child's left edge should be indented at 10% of the parent's width.
    const double leftFactor = 0.10;
    // The child's top edge should be indented at 20% of the parent's height.
    const double topFactor = 0.20;
    // The child should have a width of 60% (100 - 10 - 30) of the parent's width.
    const double widthFactor = 0.60;
    // The child should have a height of 40% (100 - 20 - 40) of the parent's height.
    const double heightFactor = 0.40;

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('DEMO'),
        ),
        body: Center(
          child: Container(
            height: 100.0,
            width: 100.0,
            color: Colors.orange,
            child: Align(
              alignment: FractionalOffset(
                leftFactor / (1.0 - widthFactor),
                topFactor / (1.0 - heightFactor),
              ),
              child: FractionallySizedBox(
                widthFactor: widthFactor,
                heightFactor: heightFactor,
                child: Container(
                  color: Colors.yellow,
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

As @goderbauer suggested, we could encapsulate this into a widget.

I ended up with this base of work (in order to have the same kind of API than the Positioned widget):

import 'package:flutter/widgets.dart';

class FractionallyAlignedSizedBox extends StatelessWidget {
  FractionallyAlignedSizedBox({
    Key key,
    @required this.child,
    this.leftFactor,
    this.topFactor,
    this.rightFactor,
    this.bottomFactor,
    this.widthFactor,
    this.heightFactor,
  })  : assert(
            leftFactor == null || rightFactor == null || widthFactor == null),
        assert(
            topFactor == null || bottomFactor == null || heightFactor == null),
        assert(widthFactor == null || widthFactor >= 0.0),
        assert(heightFactor == null || heightFactor >= 0.0),
        super(key: key);

  final double leftFactor;
  final double topFactor;
  final double rightFactor;
  final double bottomFactor;
  final double widthFactor;
  final double heightFactor;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    double dx = 0;
    double dy = 0;
    double width = widthFactor;
    double height = heightFactor;

    if (widthFactor == null) {
      final left = leftFactor ?? 0;
      final right = rightFactor ?? 0;
      width = 1 - left - right;

      if (width != 1) {
        dx = left / (1.0 - width);
      }
    }

    if (heightFactor == null) {
      final top = topFactor ?? 0;
      final bottom = bottomFactor ?? 0;
      height = 1 - top - bottom;
      if (height != 1) {
        dy = top / (1.0 - height);
      }
    }

    if (widthFactor != null && widthFactor != 1) {
      if (leftFactor != null) {
        dx = leftFactor / (1 - widthFactor);
      } else if (leftFactor == null && rightFactor != null) {
        dx = (1 - widthFactor - rightFactor) / (1 - widthFactor);
      }
    }

    if (heightFactor != null && heightFactor != 1) {
      if (topFactor != null) {
        dy = topFactor / (1 - heightFactor);
      } else if (topFactor == null && bottomFactor != null) {
        dy = (1 - heightFactor - bottomFactor) / (1 - heightFactor);
      }
    }

    return Align(
      alignment: FractionalOffset(
        dx,
        dy,
      ),
      child: FractionallySizedBox(
        widthFactor: width,
        heightFactor: height,
        child: child,
      ),
    );
  }
}

So that the previous example could be written like this:

import 'package:flutter/material.dart';

void main() => runApp(MyDemoWidget());

class MyDemoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('DEMO'),
        ),
        body: Center(
          child: Container(
            height: 100.0,
            width: 100.0,
            color: Colors.orange,
            child: FractionallyAlignedSizedBox(
              leftFactor: 0.10,
              topFactor: 0.20,
              rightFactor: 0.30,
              bottomFactor: 0.40,
              child: Container(
                color: Colors.yellow,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Concerning the name FractionallyAlignedSizedBox I'm not sure if people who look for positioning a widget relative to the size of its parent would find it easily 😕.

What do you think about this?

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projectc: new featureNothing broken; request for a new capabilityframeworkflutter/packages/flutter repository. See also f: labels.team-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions