Unexpected hit priority on `Flow`'s `children` property
raulmabe opened this issue · comments
Hi, I am trying to implement several FAB
s through the Flow
widget.
Context
What defers my case from your example is that I want to have the button that expands/shrinks the Flow
– from now on menu button– is at position 0 of the children
property, thus keeping it immobile. For this reason, my FlowDelegate
paints the children
from last
to first
; so when all buttons are shrunk, my menu button is painted above the others.
This video section may clear out my intentions.
Problem
My problem starts when using this package. When all widgets are shrunk, the pointer event triggers the last button, not my menu button, thus not working properly.
Expected output
Trigger the menu button (GradientFAB
) onPressed
property, as its painted last and above the others.
Actual output
Triggers 'Second extra button'
, while being painted under the menu button.
Files
Widget file
import 'dart:developer';
import 'dart:math' as math;
import 'dart:ui';
import 'package:defer_pointer/defer_pointer.dart';
import 'package:flutter/material.dart';
class AnimatedFloatingActionButton {
const AnimatedFloatingActionButton({
required this.text,
required this.icon,
this.onPressed,
});
final String text;
final IconData icon;
final VoidCallback? onPressed;
}
const kPadding = 8.0;
const fabSize = 56.0;
class AnimatedFloatingActionButtons extends StatefulWidget {
const AnimatedFloatingActionButtons({
Key? key,
required this.buttons,
}) : super(key: key);
final List<AnimatedFloatingActionButton> buttons;
@override
_AnimatedFloatingActionButtonsState createState() =>
_AnimatedFloatingActionButtonsState();
}
class _AnimatedFloatingActionButtonsState
extends State<AnimatedFloatingActionButtons>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late CurvedAnimation _curvedAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: 300.ms,
vsync: this,
);
_curvedAnimation =
CurvedAnimation(parent: _controller, curve: Curves.decelerate);
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: fabSize,
width: fabSize,
child: DeferPointer(
paintOnTop: true,
child: Flow(
clipBehavior: Clip.none,
delegate: _MyFlowDelegate(
animation: _curvedAnimation,
),
children: [
DeferPointer(
// paintOnTop: true,
child: GradientFAB(
onPressed: _toggleAnimation,
child: AnimatedBuilder(
animation: _curvedAnimation,
builder: (ctx, child) => Transform.rotate(
angle: lerpDouble(
0,
(90 + 45) * (math.pi / 180),
_controller.value,
)!,
child: child,
),
child: const Icon(
Icons.add,
color: Colors.white,
size: 40.0,
),
),
),
),
...widget.buttons
.map(buildButton)
.map((e) => DeferPointer(child: e))
.toList(),
],
),
),
);
}
Widget buildButton(AnimatedFloatingActionButton button) {
return AnimatedBuilder(
animation: _curvedAnimation,
builder: (context, child) {
return FloatingActionButton(
isExtended: true,
elevation: lerpDouble(0, 6.0, _controller.value),
heroTag: button.text,
child: child!,
backgroundColor: context.colorScheme.primaryVariant,
onPressed: () => log(button.text), //button.onPressed,
);
},
child: Icon(button.icon),
);
}
void _toggleAnimation() {
if (_controller.status == AnimationStatus.completed) {
_controller.reverse();
} else {
_controller.forward();
}
}
@override
void dispose() {
_controller.dispose();
_curvedAnimation.dispose();
super.dispose();
}
}
class _MyFlowDelegate extends FlowDelegate {
const _MyFlowDelegate({
required this.animation,
});
final Animation animation;
@override
void paintChildren(FlowPaintingContext context) {
final size = context.size;
final xStart = size.width - fabSize;
final yStart = size.height - fabSize;
for (var i = context.childCount - 1; i >= 0; --i) {
final offset = i * animation.value * (fabSize + kPadding);
context.paintChild(
i,
transform: Matrix4.translationValues(
xStart,
yStart - offset,
0,
),
);
}
}
@override
bool shouldRepaint(covariant _MyFlowDelegate oldDelegate) {
return animation != oldDelegate.animation;
}
}
Example of use
AnimatedFloatingActionButtons(
buttons: [
AnimatedFloatingActionButton(
text: 'First extra button',
icon: Icons.add_a_photo,
onPressed: () {},
),
AnimatedFloatingActionButton(
text: 'Second extra button',
icon: Icons.add_alarm,
onPressed: () {},
),
],
),