4

I need to make a bottom bar "shopping cart" icon and display a number of items it has. The problem is that I can't position a number label high enough. It gets clipped by it's parent constraints. The screenshots demonstrate it

enter image description here

The button itself is a Column widget having Frare animation "wave" actor on top of it and a stack below it (under the green line) And here's what I get if I move the label upper:

enter image description here

The label itself gets clipped and the wave disappears somehow. I tried to wrap the stack in a LimitedBox, OverflowBox, SizedBox but none of the solves the problem.

Here is what I want (ideally):

enter image description here

Leaving the parent with its original position and size but display a child partially beyond it

Here is the complete code of the button class (the counter is created in a _getCounter() method):

class BottomBarButton extends StatefulWidget {

  BottomBarMenuItemModel itemModel;
  final double barHeight;


  BottomBarButton(this.itemModel, this.barHeight);

  @override
  _BottomBarButtonState createState() => _BottomBarButtonState();
}

class _BottomBarButtonState extends State<BottomBarButton> with SingleTickerProviderStateMixin {

  AnimationController _scaleController;
  Animation<double> _scaleTween;
  Animation<Color> _colorTween;
  Animation<Color> _reversedColorTween;
  StreamSubscription<String> _streamSubscription;
  StreamSubscription<String> _counterSubscription;
  String _inactiveAnimation = 'idle';

  @override
  void initState() {
    _streamSubscription = Dispatch().onChangeBottomBar.stream.listen((menuId) {
      if (widget.itemModel.id == menuId) {
        return;
      }
      setState(() {});
    });

    _scaleController = AnimationController(
        vsync: this,
        duration: const Duration(milliseconds: 250)
    );
    _scaleTween = Tween(begin: 1.0, end: 1.2).animate(
        CurvedAnimation(
            parent: _scaleController,
            curve: Curves.bounceInOut
        )
    );
    _colorTween = ColorTween(begin: pizzaBottomBarIconNormalColor, end: pizzaYellow).animate(
        CurvedAnimation(parent: _scaleController, curve: Curves.bounceInOut)
    );
    _reversedColorTween = ColorTween(begin: pizzaYellow, end: pizzaBottomBarIconNormalColor).animate(
        CurvedAnimation(parent: _scaleController, curve: Curves.bounceInOut)
    );
    DataStore().onCartDataChange.stream.listen((newCounter) {
      setState(() {});
    });

    super.initState();
  }
  @override
  void dispose() {
    _scaleController?.dispose();
    _streamSubscription.cancel();
    _counterSubscription.cancel();
    super.dispose();
  }

  Widget _getCounter() {
    if (widget.itemModel.id == Dispatch.CartMenu) {
      var itemsInCart = DataStore().cartData.length;
      if (itemsInCart > 0) {
        return AnimatedBuilder(
          animation: _scaleTween,
          builder: (c, w) {
            return Positioned(
              right: 25 / _scaleTween.value,
              top: -15 + (20 / _scaleTween.value),
              child: AnimatedBuilder(
                animation: _colorTween,
                builder: (c, w) {
                  return Container(
                    width: 20,
                    height: 20,
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      color: _reversedColorTween.value,
                    ),
                    child: Text(
                      itemsInCart.toString(),
                      textAlign: TextAlign.center,
                      style: TextStyle(
                        color: pizzaBottomBarColor,
                        fontFamily: "OpenSans",
                        fontWeight: FontWeight.w500
                      ),
                    ),
                  );
                },
              ),
            );
          },
        );
      }
    }
    return Container();
  }

  @override
  Widget build(BuildContext context) {
    widget.itemModel.isActive
      ? _scaleController.forward()
      : _scaleController.reverse();
    var animationName = widget.itemModel.isActive ? 'jump_in' : _inactiveAnimation;

    return Expanded(
      child: GestureDetector(
        onTap: () {
          setState(() {
            // нужно чтобы при первом рендере не запускать все анимации, а врубить айдл
            _inactiveAnimation = 'jump_out';
            animationName = widget.itemModel.isActive ? 'jump_in' : _inactiveAnimation;
            Dispatch().selectMenu(widget.itemModel.id);
//            print(widget.itemModel.id);
          });
        },
        child: AbsorbPointer(
          child: Column(
            children: <Widget>[
              Stack(
                children: <Widget>[
                  Container( // выпрыгивающаяя волна сверху
                    // анимацию надо сдвинуть на 20 пиеселей вверх,
                    // чтобы учесть пространство
                    // для выпрыгивающей части
                    transform: Matrix4.translationValues(0, -20, 0),
                    alignment: Alignment.bottomCenter,
                    height: 20,
                    width: double.infinity,
                    child: FlareActor(
                      "assets/anim/bottom_bar_jumpy_button.flr",
                      alignment: Alignment.bottomCenter,
                      fit: BoxFit.fitWidth,
                      animation: animationName,
                      shouldClip: false,
                    )
                  ),
                  ScaleTransition(
                    scale: _scaleTween,
                    child: Container(
                      // неанимированная иконка
                      alignment: Alignment.center,
                      child: AnimatedBuilder(
                        animation: _colorTween,
                        builder: (context, w) {
                          return Icon(
                            widget.itemModel.iconData,
                            size: 20,
                            color: _colorTween.value,
                          );
                        },
                      ),
                      height: widget.barHeight - 22,
                    ),
                  ),
                  _getCounter(),
                ],
              ),
              ScaleTransition(
                // текст кнопок в нижнем баре
                scale: _scaleTween,
                alignment: FractionalOffset.topCenter, // пивот по верхней кромке
                child: Transform(
                  transform: Matrix4.translationValues(0, -6, 0),
                  child: AnimatedBuilder(
                    animation: _colorTween,
                    builder: (context, w) {
                      return Padding(
                        padding: const EdgeInsets.only(top: 7.0),
                        child: Text(widget.itemModel.title,
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                          textAlign: TextAlign.start,
                          style: TextStyle(
                            fontFamily: 'OpenSans',
                            fontWeight: FontWeight.w500,
                            fontSize: 13,
                            height: 1,
                            color: _colorTween.value
                          ),
                        ),
                      );
                    },
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}
Konstantin
  • 1,150
  • 13
  • 31

3 Answers3

6

Don't use overflow, it is deprecated now. Use clipBehavior instead.

Stack(
  clipBehavior: Clip.none, // This is what you need. 
  children: [],
)
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
  • Ok, thats for this suggestion. But this question was asked almost a year and a half ago, and it wasn't about overflow – Konstantin Nov 11 '20 at 07:29
  • 1
    @Konstantin Sorry, I overlooked the content part and answered on the basis of your question's title. I hope someone else may benefit from it. – CopsOnRoad Nov 11 '20 at 10:10
1

Some magic happens. I've been struggling with this for a few hour but came up with a solution in a few minutes after I had created this topic. So it can be resolved very easily by using a Transform widget. Just like this:

Widget _getCounter() {
    if (widget.itemModel.id == Dispatch.CartMenu) {
      var itemsInCart = DataStore().cartData.length;
      if (itemsInCart > 0) {
        return AnimatedBuilder(
          animation: _scaleTween,
          builder: (c, w) {
            return Transform(
              transform: Matrix4.translationValues(
                  45 + (5 * _scaleTween.value), 
                  6 - (6 * _scaleTween.value), 0
              ),
              child: AnimatedBuilder(
                animation: _colorTween,
                builder: (c, w) {
                  return Container(
                    width: 20,
                    height: 20,
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      color: _reversedColorTween.value,
                    ),
                    child: Text(
                      itemsInCart.toString(),
                      textAlign: TextAlign.center,
                      style: TextStyle(
                        color: pizzaBottomBarColor,
                        fontFamily: "OpenSans",
                        fontWeight: FontWeight.w500
                      ),
                    ),
                  );
                },
              ),
            );
          },
        );
      }
    }
    return Container();
  }
Konstantin
  • 1,150
  • 13
  • 31
1

I think you can solve this by using

overflow: Overflow.visible,

inside the Stack. So your code will be like:

Stack(
    overflow: Overflow.visible,
    children: [
        /* Your children*/
    ],
)
Abdelazeem Kuratem
  • 1,324
  • 13
  • 20