1

I have an animated "flower" made up of petal images that rotate into position on Widget build. The petal images can be of various lengths.

Because I didn't know how to move the pivot point of each petal PNG to the bottom centre for the rotation, I made each petal image a transparent square with the bottom of the petal in the centre of the image, so I can just rotate the entire square image around the centre and it looks like the petal is rotating around its base.

I have 5 of these in a stack, animated, and I want a gesture detector on each one so I can take action when any of them are tapped. I already have a GestureDetector on an image I use for the centre of the flower and it works, but none of the petals do.

I have tried using HitTestBehavior.translucent with no luck...

class _PFMainScreenState extends State<PFMainScreen>
    with TickerProviderStateMixin {
  AnimationController rotationController;

  @override
  void initState() {
    super.initState();
    rotationController = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
  }

  void onAfterBuild(BuildContext context) {
    Future.delayed(const Duration(milliseconds: 1000), () {
      rotationController.forward();
    });
  }

  @override
  void dispose() {
    rotationController.dispose();
    super.dispose();
  }

  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) => onAfterBuild(context));
    return Scaffold(
      backgroundColor: Colors.black,
      body: Center(
        child: DecoratedBox(
          position: DecorationPosition.background,
          decoration: BoxDecoration(
            color: Colors.black,
            image: DecorationImage(
                image: AssetImage('images/background.jpg'),
                fit: BoxFit.contain),
          ),
          child: Stack(
            children: <Widget>[
              Center(
                child: GestureDetector(
                    onTap: () {
                      print('1st image tapped');
                    },
                    behavior: HitTestBehavior.translucent,
                    child: Image.asset('images/petal-square.png', height: 350)),
              ),
              Center(
                child: RotationTransition(
                  turns: Tween(begin: 0.0, end: 0.2).animate(
                      new CurvedAnimation(
                          parent: rotationController,
                          curve: Curves.decelerate,
                          reverseCurve: Curves.decelerate)),
                  child: GestureDetector(
                      onTap: () {
                        print('2nd image tapped');
                      },
                      behavior: HitTestBehavior.translucent,
                      child:
                          Image.asset('images/petal-square.png', height: 250)),
                ),
              ),
              Center(
                child: RotationTransition(
                  turns: Tween(begin: 0.0, end: 0.4).animate(
                      new CurvedAnimation(
                          parent: rotationController,
                          curve: Curves.decelerate,
                          reverseCurve: Curves.decelerate)),
                  child: Image.asset('images/petal-square.png', height: 400),
                ),
              ),
              Center(
                child: RotationTransition(
                  turns: Tween(begin: 0.0, end: 0.6).animate(
                      new CurvedAnimation(
                          parent: rotationController,
                          curve: Curves.decelerate,
                          reverseCurve: Curves.decelerate)),
                  child: Image(
                    image: AssetImage('images/petal-square.png'),
                  ),
                ),
              ),
              Center(
                child: RotationTransition(
                  turns: Tween(begin: 0.0, end: 0.8).animate(
                      new CurvedAnimation(
                          parent: rotationController,
                          curve: Curves.decelerate,
                          reverseCurve: Curves.decelerate)),
                  child: Image.asset('images/petal-square.png', height: 200),
                ),
              ),
              Center(
                child: GestureDetector(
                  onTap: () {
                    //rotationController.forward(from: 0.0);
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => PFMenuScreen(),
                      ),
                    );
                  },
                  behavior: HitTestBehavior.translucent,
                  child: Image(
                    height: 100.0,
                    width: 100.0,
                    image: AssetImage('images/centre.png'),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Is there a way to detect taps on non-translucent images in a Stack?

Cheers

Lucas Young
  • 187
  • 2
  • 13

1 Answers1

0

If you run your app with the flutter inspector of Dart Dev Tools, you can see that each of your Center widgets are filling the whole screen. That may effect your gesture detection depending on where you put your gesture detector.

Image of Flutter inspector in Dart Dev Tools

Here is some code that should work for you. I refactored your rotating image into a re-usable widget.

class PFMainScreen extends StatefulWidget {
  @override
  _PFMainScreenState createState() => _PFMainScreenState();
}

class _PFMainScreenState extends State<PFMainScreen>
    with TickerProviderStateMixin {
  AnimationController rotationController;

  @override
  void initState() {
    super.initState();
    rotationController = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
  }

  void onAfterBuild(BuildContext context) {
    Future.delayed(const Duration(milliseconds: 1000), () {
      rotationController.forward();
    });
  }

  @override
  void dispose() {
    rotationController.dispose();
    super.dispose();
  }

  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) => onAfterBuild(context));
    return Scaffold(
      backgroundColor: Colors.black,
      body: Center(
        child: Stack(
          alignment: AlignmentDirectional.center,
          children: <Widget>[
            RotatingImage(
              rotationController: rotationController,
              onTap: () {
                print("Image 1 tapped");
              },
              imageHeight: 400,
              tweenEnd: 0.2,
            ),
            RotatingImage(
              rotationController: rotationController,
              onTap: () {
                print("Image 2 tapped");
              },
              imageHeight: 350,
              tweenEnd: 0.4,
            ),
            RotatingImage(
              rotationController: rotationController,
              onTap: () {
                print("Image 3 tapped");
              },
              imageHeight: 250,
              tweenEnd: 0.6,
            ),
            RotatingImage(
              rotationController: rotationController,
              onTap: () {
                print("Image 4 tapped");
              },
              imageHeight: 200,
              tweenEnd: 0.8,
            ),
          ],
        ),
      ),
    );
  }
}

class RotatingImage extends StatelessWidget {
  final AnimationController rotationController;
  final double imageHeight;
  final double tweenEnd;
  final Function onTap;
  const RotatingImage({
    this.onTap,
    this.imageHeight,
    this.tweenEnd,
    @required this.rotationController,
  });

  @override
  Widget build(BuildContext context) {
    return RotationTransition(
      turns: Tween(begin: 0.0, end: tweenEnd).animate(new CurvedAnimation(
          parent: rotationController,
          curve: Curves.decelerate,
          reverseCurve: Curves.decelerate)),
      child: GestureDetector(
          onTap: onTap,
          behavior: HitTestBehavior.translucent,
          child: Image.asset('images/petal-square.png', height: imageHeight)),
    );
  }
}
mskolnick
  • 1,062
  • 8
  • 12
  • That's amazing, thank you. The other option I was going to look at was using a Pointer Listener, but I'll try your way first :) – Lucas Young Jan 29 '20 at 20:53
  • Actually, implementing your code, no matter where I tap I get a notification for Image 4. I've read more about HitTestBehavior.translucent and I think that it doesn't work the intuitive way we expect. https://github.com/flutter/flutter/issues/45253#issuecomment-564093260 Also https://flutter.dev/docs/development/ui/advanced/gestures#gesture-disambiguation So I either need to make the PNGs not overlap (move their pivot points) or use a Pointer Listener... – Lucas Young Feb 01 '20 at 13:54
  • Hmm.. Did you copy paste my code exactly? I was able to receive a click on each of the images. Make sure you have the largest image in the back (at the top of the stack), and the smallest image in the front (at the bottom of the stack). – mskolnick Feb 02 '20 at 16:11