skip to Main Content

I am having a Stack with two child elements. The second element is some Text and the first element is a Container with some blue background color. I want to have the height of the Container match the height of the Stack given by the height of the Text-Widget. And the width of my Container should be set dynamically with a percentage of the width of the Stack which also comes from the Text widget.

In other words: I want to build a progress indicator, that I can set dynamically. Altough the width of my parent is not known before build-time. And this width should be givene by the text-child-widget.

How can I achieve that?

enter image description here

Stack(
  alignment: Alignment.centerLeft,
  clipBehavior: Clip.none,
  children: [
    Container(
        width: 50,
        height: 20,
        color: const Color(0xffa0a4fc),
    ),
    Padding(
      padding: const EdgeInsets.symmetric(horizontal: 25),
      child: Text(
        "some text",
        style: const TextStyle(
          color: Colors.black,
          fontWeight: FontWeight.bold,
          fontSize: 22,
        ),
      ),
    ),
)

2

Answers


  1. enter image description here

    you need to calculate text width :

    final TextPainter myTextPainter = TextPainter(
          text:
              TextSpan(text: myText, style: Theme.of(context).textTheme.labelLarge),
          textDirection: TextDirection.ltr,
        )..layout(maxWidth: MediaQuery.of(context).size.height, minWidth: 0.0);
    
        final myTextSize = myTextPainter.size;
    

    full code for your reference :

    import 'package:flutter/material.dart';
    
    class Example extends StatefulWidget {
      const Example({super.key});
    
      @override
      State<Example> createState() => _ExampleState();
    }
    
    class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
      late AnimationController _animationController;
      String myText = 'loading.. please wait';
      @override
      void initState() {
        super.initState();
        _animationController =
            AnimationController(vsync: this, duration: const Duration(seconds: 3));
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        final TextPainter myTextPainter = TextPainter(
          text:
              TextSpan(text: myText, style: Theme.of(context).textTheme.labelLarge),
          textDirection: TextDirection.ltr,
        )..layout(maxWidth: MediaQuery.of(context).size.height, minWidth: 0.0);
    
        final myTextSize = myTextPainter.size;
    
        return Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
                onPressed: () {
                  if (!_animationController.isAnimating) {
                    setState(() {
                      myText = 'loading.. please wait';
                    });
                    _animationController.reset();
                    _animationController.forward().then((value) {
                      setState(() {
                        myText = 'loading.. completed';
                      });
                    });
                  }
                },
                child: const Text("Start Animating")),
            SizedBox(
              width: MediaQuery.of(context).size.width,
              height: 10.0,
            ),
            Container(
              decoration: BoxDecoration(border: Border.all(color: Colors.blue)),
              child: Padding(
                padding: const EdgeInsets.all(10.0),
                child: Stack(
                  fit: StackFit.loose,
                  children: [
                    AnimatedBuilder(
                        animation: _animationController,
                        builder: (context, _) {
                          return Container(
                            color: Colors.blue,
                            height: myTextSize.height,
                            width: (_animationController.value * myTextSize.width),
                          );
                        }),
                    Text(
                      myText,
                      style: Theme.of(context).textTheme.labelLarge,
                    )
                  ],
                ),
              ),
            )
          ],
        );
      }
    }
    
    Login or Signup to reply.
  2. Per the Stack documentation:

    Each child of a Stack widget is either positioned or non-positioned (…). The stack sizes itself to contain all the non-positioned children, (…). The positioned children are then placed relative to the stack according to their top, right, bottom, and left properties.

    So, using FractionallySizedBox inside Positioned and then having the Text determine the Stack size would do the trick:

    Stack(
      children: [
        Positioned.fill(
          child: FractionallySizedBox(
            alignment: Alignment.centerLeft,
            widthFactor: factor,
            child: ...,
          ),
        ),
        Text(...),
      ],
    )
    

    See a demo on dartpad.dev

    Using a TextPainter to calculate size of Stack‘s contents is also correct solution for your case but it has some drawbacks:

    • It won’t work for more advanced cases where the content of the loader is something other than just single Text (as in the demo above).
    • It’s slightly less performant because it needs to calculate Text‘s size twice.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search