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
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:
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):
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
),
),
);
},
),
),
)
],
),
),
),
);
}
}