Как во Flutter позиционированный виджет может ощущать касания за пределами своей родительской области стека?

Stack содержит MyWidget внутри Positioned.

Stack(
  overflow: Overflow.visible,
  children: [
    Positioned(
    top: 0.0,
    left: 0.0,
    child: MyWidget(),
  )],
);

Поскольку переполнение составляет Overflow.visible, а MyWidget больше, чем Stack, оно отображается за пределами Stack, что мне и нужно.

Однако я не могу коснуться области MyWidget, которая находится за пределами области Stack. Там просто игнорирует кран.

Как я могу убедиться, что MyWidget там принимает жесты?


person MarcG    schedule 16.07.2018    source источник
comment
Это дублированный вопрос: stackoverflow.com/questions/51188344/   -  person diegoveloper    schedule 16.07.2018
comment
Это не дубликат. Этот вопрос только спрашивает, является ли это предполагаемым поведением, а я знаю, что это так. Я просто хочу знать обходной путь, чтобы он все равно принимал жесты.   -  person MarcG    schedule 16.07.2018
comment
Это невозможно. Единственное решение - провести рефакторинг макета, чтобы убрать переполнение.   -  person Rémi Rousselet    schedule 16.07.2018
comment
В зависимости от того, что вы хотите, вместо Stack вы можете просто использовать Overlay.   -  person Rémi Rousselet    schedule 16.07.2018


Ответы (4)


Это происходит потому, что стек проверяет, находится ли указатель внутри его границ, прежде чем проверять, попал ли дочерний элемент:

Класс: RenderBox (который расширяет RenderStack)

bool hitTest(BoxHitTestResult result, { @required Offset position }) {

    ...

    if (_size.contains(position)) {
      if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
        result.add(BoxHitTestEntry(this, position));
        return true;
      }
    }
    return false;
}

Мое обходное решение - удаление

if (_size.contains(position))

чек. К сожалению, это невозможно без копирования кода из фреймворка.

Вот что я сделал:

  • Скопировал класс Stack и назвал его Stack2
  • Скопировал RenderStack и назвал его RenderStack2
  • Сделана ссылка на Stack2 RenderStack2
  • Добавлен метод hitTest сверху без проверки _size.contains
  • Скопировал Positioned, назвал его Positioned2 и сделал ссылку на Stack2 в качестве общего параметра.
  • Использовал Stack2 и Positioned2 в моем коде

Это решение ни в коем случае не является оптимальным, но оно обеспечивает желаемое поведение.

person Norbert    schedule 12.09.2019
comment
Было ли предложено изменить фреймворк команде Flutter на Github? Похоже, что в стек можно добавить свойство, чтобы разрешить события указателя за пределами стека. - person buttonsrtoys; 23.03.2020

У меня была аналогичная проблема. В основном, поскольку дочерние элементы стека не используют размер полностью переполненного блока для проверки попадания, я использовал вложенный стек и произвольную большую высоту, чтобы я мог фиксировать щелчки переполненных блоков вложенного стека. Не уверен, что это сработает для вас, но здесь ничего не выходит :)

Итак, в вашем примере, возможно, вы могли бы попробовать что-то вроде этого

Stack(
  clipBehavior: Clip.none,
  children: [
    Positioned(
    top: 0.0,
    left: 0.0,
    height : 500.0 // biggest possible child size or just very big 
    child: Stack(
      children: [MyWidget()]
    ),
  )],
);
person George Chailazopoulos    schedule 30.10.2018

Хорошо, я нашел обходной путь, в основном я добавил GestureDetector к родительскому элементу и реализовал onTapDown. Также вы должны отслеживать свое Widget, используя GlobalKey, чтобы получить текущую позицию.

При обнаружении Tap на родительском уровне проверьте, находится ли положение касания внутри вашего виджета.

Код ниже:

final GlobalKey key = new GlobalKey();

      void onTapDown(BuildContext context, TapDownDetails details) {
        final RenderBox box = context.findRenderObject();
        final Offset localOffset = box.globalToLocal(details.globalPosition);
        final RenderBox containerBox = key.currentContext.findRenderObject();
        final Offset containerOffset = containerBox.localToGlobal(localOffset);
        final onTap = containerBox.paintBounds.contains(containerOffset);
        if (onTap){
          print("DO YOUR STUFF...");
        }
      }

      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          onTapDown: (TapDownDetails details) => onTapDown(context, details),
          child: Container(
            color: Colors.red,
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            child: Align(
              alignment: Alignment.topLeft,
                      child: SizedBox(
                width: 200.0,
                height: 400.0,
                child: Container(
                  color: Colors.black,
                    child: Stack(
                      overflow: Overflow.visible,
                      children: [
                        Positioned(
                          top: 0.0, left: 0.0,
                                          child: Container(
                            key: key,
                            width: 500.0,
                            height: 200.0,
                            color: Colors.blue,
                          ),
                        ),
                      ],
                    ),

                ),
              ),
            ),
          ),
        );
      } 
person diegoveloper    schedule 16.07.2018
comment
Итак, вы на самом деле определяете, прослушивается ли MyWidget? Предположим, он прямоугольный. Хм, я думаю, это полезно для некоторых случаев использования, поэтому я проголосую за этот ответ. Но в моем случае MyWidget - это сложный виджет, и он должен получать собственные жесты, чтобы выполнять свою работу. Например, если он содержит ListView и кнопки, это решение по-прежнему не решает проблему. - person MarcG; 16.07.2018

Вы можете рассмотреть возможность использования наследования, чтобы скопировать метод hitTest, чтобы нарушить правило попадания, например

class Stack2 extends Stack {
  Stack2({
    Key key,
    AlignmentGeometry alignment = AlignmentDirectional.topStart,
    TextDirection textDirection,
    StackFit fit = StackFit.loose,
    Overflow overflow = Overflow.clip,
    List<Widget> children = const <Widget>[],
  }) : super(
          key: key,
          alignment: alignment,
          textDirection: textDirection,
          fit: fit,
          overflow: overflow,
          children: children,
        );

  @override
  RenderStack createRenderObject(BuildContext context) {
    return RenderStack2(
      alignment: alignment,
      textDirection: textDirection ?? Directionality.of(context),
      fit: fit,
      overflow: overflow,
    );
  }
}

class RenderStack2 extends RenderStack {
  RenderStack2({
    List<RenderBox> children,
    AlignmentGeometry alignment = AlignmentDirectional.topStart,
    TextDirection textDirection,
    StackFit fit = StackFit.loose,
    Overflow overflow = Overflow.clip,
  }) : super(
          children: children,
          alignment: alignment,
          textDirection: textDirection,
          fit: fit,
          overflow: overflow,
        );

  @override
  bool hitTest(BoxHitTestResult result, {Offset position}) {
    if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
      result.add(BoxHitTestEntry(this, position));
      return true;
    }
    return false;
  }
}
person zengxiangxi    schedule 26.07.2021