Трепещущий детектор жестов, если провести пальцем вниз, чтобы отклонить

#flutter #dart #flutter-animation #gesturedetector

#трепетание #дротик #флаттер-анимация #детектор жестов

Вопрос:

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

Я пытался использовать GestureDetector подобное:

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

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

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

Желаемый результат должен выглядеть следующим образом:

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

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

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

Screenvideo

При нажатии кнопки закрытия анимация работает просто отлично. Однако, если я начну перетаскивать, анимация должна начаться, и если я ее закончу, она должна 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()
        }
    }
}
 

Ответ №1:

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

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

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

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

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

 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;
                      }
                    });     
                },
            )
          ),
        ),
      ),
    );
  }
}
 

Комментарии:

1. на самом деле вы должны использовать kMinFlingVelocity или некоторые подобные константы

2. хорошо, этот вид работает так, что pop вызывается только тогда, когда он действительно должен быть вызван. Однако при перетаскивании вниз анимация не запускается. при завершении всего перетаскивания он анимируется. Но это не 100% желаемая анимация. Вы понимаете, что я имею в виду?

3. @Chris Да. В этом случае вы должны обрабатывать анимацию с onVerticalDragUpdate помощью обратного вызова

4. @hyobbb как бы я это сделал??

5. Это зависит от того, как вы хотите реализовать поведение анимации с помощью DragUpdateDetails . ознакомьтесь с этим документом api. api.flutter.dev/flutter/gestures/DragUpdateDetails-class.html