InkWell and GestureDetector, how to make them work?

Viewed 9173

I'd like to use a GestureDetector for it's onTapDown callback but also have a nice InkWell splash effect.

Is it possible to use these two together?

4 Answers

If you want to unconditionally handle the pointer down event with no gesture disambiguation, you can make the InkWell a child of a Listener, and set the onPointerDown handler.

For example:

new Listener(
  onPointerDown: (e) { print('onPointerDown'); },
  child: new InkWell(
    child: new Text('Tap me'),
    onTap: () { print('onTap'); }
  ),
),

It might make sense to add an onTapDown handler to InkWell.

You can pass a HitTestBehavior to GestureDetector to make it "non-blocking" by setting the value to "translucent"

Or you can also trigger it yourself using Material.of(context)

InkWell uses a GestureDetector under it's hood:

https://github.com/flutter/flutter/blob/79b5e5bc8af7d9df3374dfe6141653848d1c03ac/packages/flutter/lib/src/material/ink_well.dart#L560

The reason why a GestureDetector isn't able to work well if the InkWell-Widget is, that it sets the behavior on it's internal GestureDetector to "HitTestBehavior.opaque". This prevents "event bubbling" / event capturing on parent widgets (if my understanding is correct). And because the "behavior" is final, we can't change / fix that by our own.

https://github.com/flutter/flutter/blob/79b5e5bc8af7d9df3374dfe6141653848d1c03ac/packages/flutter/lib/src/material/ink_well.dart#L566

As I mentioned in a comment above, I wrapped the InkWell within a widget (which handles other stuff too) and provided a way to pass a callback into it which is executed on the desired event.

This is my example solution:

import 'package:flutter/material.dart';

class CardPreview extends StatefulWidget {   

  final dynamic data;   
  final VoidCallback onTap;

  const CardPreview(this.data, this.onTap);

  CardPreview._(this.data, this.onTap);

  factory CardPreview.fromData(dynamic data, VoidCallback onTap) {
    return new CardPreview(data, onTap);
  }

  CardPreview createState() => new CardPreview(this.data, this.onTap);
}

class CardPreviewState extends State<CardPreview> {   
  final dynamic data;   
  final VoidCallback onTap;

  CardPreviewState(this.data, this.onTap);

  Widget buildCard() {
    return Material(
      color: Colors.transparent,
      child: Ink(
        child: InkWell(
          child: null,
          onTap: () {
            if (this.onTap == null) {
              return;
            }

            this.onTap();
          },
        ),
      ),
    );  
 }

  @override   
  Widget build(BuildContext context) {
    return Container(
      child: buildCard(),
    );   
  } 
}

Building on Rémi's suggestion of adding a HitTestBehavior.translucent behavior to your GestureDetector, this is how I would solve your issue:

Material(
    child: GestureDetector(
        behavior: HitTestBehavior.translucent,
        onTapDown: (details) {
            //Do something
        },
        child: InkWell(
            onTap: () {},
            child: Container(height: 20.0, width: 20.0, color: Colors.red),
        ),
    ),
),

I was able to use something similar to essentially add a onLongPressStart and onLongPressEnd to my InkWell.

Related