Flutter GestureDetector, если провести вниз, чтобы закрыть

Я хотел бы иметь возможность смахнуть вниз, чтобы закрыть, но с Hero-Animation.

Я пробовал использовать GestureDetector вот так:

  body: GestureDetector(
    onVerticalDragDown: (details) {
      Navigator.pop(context);
    },
    child: 

Анимация с этим выглядит нормально, но проблема в том, что она poping практически при любом жесте. Я бы хотел, чтобы он появлялся только в том случае, если пользователь действительно проводит пальцем вниз.

Также должно быть какое-то свойство finished, чтобы анимация отменялась, если пользователь не полностью проводит пальцем вниз. Возможно ли это, и если да, то как? Я ничего не нашел по этому поводу ..

Желаемый результат должен выглядеть так:

Желаемая анимация

Как видите, я могу провести вниз, а также отменить pop, не полностью проведя пальцем вниз.

Текущая анимация:

Экранное видео

При нажатии кнопки закрытия анимация работает нормально. Однако, если я начну перетаскивать, анимация должна начаться, а если я ее закончу, она должна pop, или я также могу отменить анимацию, и анимация вернется к нормальному экрану.

Это мой код, если это полный код, если это помогает:

 Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onVerticalDragDown: (details) {
          Navigator.pop(context);
        },
        child: Stack(
          children: [
            Hero(
              tag: month.name + 'background',
              // transitionOnUserGestures: true,
              child: Container(
                // color: CustomColors.darkCustom,
                decoration: BoxDecoration(
                  color: CustomColors.darkCustom,
                  borderRadius: BorderRadius.circular(30.0),
                ),
              ),
            ),
            Positioned(
              right: 30,
              top: 15,
              child: SafeArea(
                child: Hero(
                  // transitionOnUserGestures: true,
                  tag: month.name + 'close',
                  child: Container(
                    height: 45,
                    width: 45,
                    child: RawMaterialButton(
                      fillColor: CustomColors.lightGreyCustom,
                      splashColor: Colors.transparent,
                      highlightColor: Colors.transparent,
                      // elevation: 10,
                      shape: CircleBorder(),
                      onPressed: () {
                        Navigator.of(context).pop();
                      },
                      child: SvgPicture.asset('assets/images/close.white.svg',
                          height: 25, width: 25),
                    ),
                  ),
                ),
              ),
            ),
            Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                SafeArea(
                  bottom: false,
                  child: SizedBox(height: 20),
                ),
                Row(
                  children: [
                    Padding(
                      padding:
                          const EdgeInsets.only(left: 45, top: 45, bottom: 35),
                      child: Hero(
                        // transitionOnUserGestures: true,
                        tag: month.name + 'text',
                        // sized box to prevent flickering bug
                        child: SizedBox(
                          height: 40,
                          width: 200,
                          // material is need for Hero + Text
                          child: Material(
                            color: Colors.transparent,
                            child: Text(
                              month.name,
                              style: TextStyle(
                                color: Colors.white,
                                fontSize: 28,
                                fontFamily: Fonts.glossAndBloom,
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
                Hero(
                  tag: month.name + 'frame',
                  child: Container(
                    height: Constants.width(context) - 60,
                    width: Constants.width(context) - 60,
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(10.0),
                      border: Border.all(color: Colors.white, width: 5),
                    ),
                  ),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }

В Swift мне удалось добиться анимации с помощью этого кода:

@objc private func handlePan(gestureRecognizer:UIPanGestureRecognizer) {
    // calculate the progress based on how far the user moved
    let translation = panGR.translation(in: nil)
    let progress = translation.y / 2 / view.bounds.height
    
    switch panGR.state {
    case .began:
        // begin the transition as normal
        self.dismissView()
        break
    case .changed:
        
        Hero.shared.update(progress)
        
    default:
        // finish or cancel the transition based on the progress and user's touch velocity
        if progress + panGR.velocity(in: nil).y / view.bounds.height > 0.3 {
            self.dismissView()
            Hero.shared.finish()
        } else {
            Hero.shared.cancel()
        }
    }
}

person Chris    schedule 02.03.2021    source источник


Ответы (1)


Предлагаю использовать DragEndDetails функции обратного вызова. Простой пример:

onVerticalDragEnd: (endDetails) {
              double velocity = endDetails.primaryVelocity;
              if (velocity > 0 ){                              
               Navigator.pop(context);
              }
            },

В этом случае, если вы удерживаете жест перетаскивания в конце, он не выскочит, потому что скорость будет равна 0.

Редактировать:

Это простой пример реализации анимации на деталях перетаскивания. На DragUpdate высота контейнера будет изменена, но с ограничением от max: 300 до min: 100. На DragEnd зависит от того, смахиваете ли вы вверх или вниз, высота контейнера будет установлена ​​на максимальную или минимальную.

class AnimatedContainerApp extends StatefulWidget {
  @override
  _AnimatedContainerAppState createState() => _AnimatedContainerAppState();
}

class _AnimatedContainerAppState extends State<AnimatedContainerApp> {
  double height = 300;
  bool gestureUp = false;
  
  @override
  Widget build(BuildContext context) {
    final maxHeight = 300.0;
    final minHeight = 100.0;
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('AnimatedContainer Demo'),
        ),
        body: Align(alignment: Alignment.bottomCenter,             
         child:AnimatedContainer(
          color: Colors.red,
            height: height,
            duration: Duration(milliseconds: 200),
            curve: Curves.fastOutSlowIn,
            child: GestureDetector(
               onVerticalDragUpdate: (details) {
                 setState((){
                  if (0 < details.delta.dy)
                    gestureUp = false;
                  else
                    gestureUp = true;
                  height -= details.delta.dy;
                  if (height > maxHeight)
                      height = maxHeight;
                  else if (height < minHeight)
                      height = minHeight;
                  });     
                },
                onVerticalDragEnd: (details) {
                  setState((){
                      if (gestureUp) {
                        height = maxHeight;
                      } else {
                        height = minHeight;
                      }
                    });     
                },
            )
          ),
        ),
      ),
    );
  }
}
person hyobbb    schedule 02.03.2021
comment
на самом деле вам следует использовать kMinFlingVelocity или аналогичные константы. - person pskink; 02.03.2021
comment
хорошо, этот вид работает так, что pop вызывается только тогда, когда он действительно должен быть вызван. Однако при перетаскивании анимация не запускается. при завершении всего перетаскивания анимируется. Но это не 100% желаемая анимация. Вы понимаете, что я имею в виду? - person Chris; 02.03.2021
comment
@ Крис Да. В этом случае вы должны обрабатывать анимацию с помощью обратного вызова onVerticalDragUpdate - person hyobbb; 03.03.2021
comment
@hyobbb, как мне это сделать ?? - person Chris; 03.03.2021
comment
Это зависит от того, как вы хотите реализовать поведение анимации с помощью DragUpdateDetails. ознакомьтесь с этим API-документом. api.flutter.dev/flutter/gestures/DragUpdateDetails-class.html - person hyobbb; 03.03.2021
comment
@hyobbb, хорошо, спасибо, но я совсем потерялся здесь. Как мне это реализовать? используя приведенный выше код? - person Chris; 03.03.2021
comment
@Chris Я отредактировал ответ с помощью примера, который может вам помочь. - person hyobbb; 08.03.2021
comment
@hyobbb спасибо за вашу помощь, но я думаю, что это не совсем то, что я хочу. У меня есть несколько разных Hero-Widgets внутри Stack на моем экране. Все это нужно анимировать. Я думаю, что только анимация контейнера не поможет :( Интересно, есть ли у flutter / Hero эта функция - person Chris; 08.03.2021
comment
Ага. Это пример, чтобы дать вам представление о том, как действовать в ситуации onDragUpdate. Поскольку вы используете стек, вы можете обернуть свой виджет виджетом Positioned и изменить верхний или нижний параметр - person hyobbb; 09.03.2021