【问题标题】:adding inertia to a UIPanGestureRecognizer向 UIPanGestureRecognizer 添加惯性
【发布时间】:2013-06-13 09:25:27
【问题描述】:

我试图在屏幕上移动一个子视图,但我也想为对象添加惯性或动量。
我已经拥有的 UIPanGestureRecognizer 代码如下。

提前致谢。

UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self       action:@selector(handlePan:)];
[self addGestureRecognizer:panGesture];

(void)handlePan:(UIPanGestureRecognizer *)recognizer
{

    CGPoint translation = [recognizer translationInView:self.superview];
    recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
                                     recognizer.view.center.y + translation.y);
    [recognizer setTranslation:CGPointMake(0, 0) inView:self.superview];

    if (recognizer.state == UIGestureRecognizerStateEnded) {
        [self.delegate card:self.tag movedTo:self.frame.origin];
    }
}

再次感谢。

【问题讨论】:

    标签: ios uipangesturerecognizer momentum


    【解决方案1】:

    看看RotationWheelAndDecelerationBehaviour。有一个示例说明如何对线性平移和旋转运动进行减速。诀窍是查看用户结束触摸并以小减速继续朝该方向前进时的速度。

    【讨论】:

    • 这不是一个非常彻底的答案。我建议您阅读 how to answer 并展开它。
    【解决方案2】:

    好吧,我不是专业人士,但检查了多个答案后,我设法编写了自己的代码,我对此很满意。

    请告诉我如何改进它以及我是否使用了任何不良做法。

    - (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
    
    CGPoint translatedPoint = [recognizer translationInView:self.postViewContainer];
    CGPoint velocity = [recognizer velocityInView:recognizer.view];
    
    float bottomMargin = self.view.frame.size.height - containerViewHeight;
    float topMargin = self.view.frame.size.height - scrollViewHeight;
    
    if ([recognizer state] == UIGestureRecognizerStateChanged) {
    
        newYOrigin = self.postViewContainer.frame.origin.y + translatedPoint.y;
    
        if (newYOrigin <= bottomMargin  && newYOrigin >= topMargin) {
            self.postViewContainer.center = CGPointMake(self.postViewContainer.center.x, self.postViewContainer.center.y + translatedPoint.y);
        }
        [recognizer setTranslation:CGPointMake(0, 0) inView:self.postViewContainer];
    }
    
    if ([recognizer state] == UIGestureRecognizerStateEnded) {
    
        __block float newYAnimatedOrigin = self.postViewContainer.frame.origin.y + (velocity.y / 2.5);
    
        if (newYAnimatedOrigin <= bottomMargin && newYAnimatedOrigin >= topMargin) {
            [UIView animateWithDuration:1.2 delay:0
                                options:UIViewAnimationOptionCurveEaseOut
                             animations:^ {
                                 self.postViewContainer.center = CGPointMake(self.postViewContainer.center.x, self.postViewContainer.center.y + (velocity.y / 2.5));
                             }
                             completion:^(BOOL finished) {
                                 [self.postViewContainer setFrame:CGRectMake(0, newYAnimatedOrigin, self.view.frame.size.width, self.view.frame.size.height - newYAnimatedOrigin)];
                             }
             ];
        }
        else {
            [UIView animateWithDuration:0.6 delay:0
                                options:UIViewAnimationOptionCurveEaseOut
                             animations:^ {
                                 if (newYAnimatedOrigin > bottomMargin) {
                                     self.postViewContainer.center = CGPointMake(self.postViewContainer.center.x, bottomMargin + self.postViewContainer.frame.size.height / 2);
                                 }
    
                                 if (newYAnimatedOrigin < topMargin) {
                                     self.postViewContainer.center = CGPointMake(self.postViewContainer.center.x, topMargin + self.postViewContainer.frame.size.height / 2);
                                 }
                             }
                             completion:^(BOOL finished) {
                                 if (newYAnimatedOrigin > bottomMargin)
                                     [self.postViewContainer setFrame:CGRectMake(0, bottomMargin, self.view.frame.size.width, scrollViewHeight)];
    
                                 if (newYAnimatedOrigin < topMargin)
                                     [self.postViewContainer setFrame:CGRectMake(0, topMargin, self.view.frame.size.width, scrollViewHeight)];
                             }
             ];
        }
    }
    

    }

    我使用了两种不同的动画,一种是默认惯性,另一种是当用户高速甩动 containerView 时。

    它在 iOS 7 下运行良好。

    【讨论】:

      【解决方案3】:

      我从公认答案的实施中获得灵感。这是 Swift 5.1 版本


      逻辑:

      • 您需要计算角度随平移手势结束时的速度变化,并在无休止的计时器中继续旋转滚轮,直到速度因减速率而磨损。
      • 在计时器的每次迭代中不断降低当前速度 有一些因素(比如,0.9)。
      • 保持速度的下限 使计时器无效并完成减速过程。

      用于计算减速度的主函数:

      // deceleration behaviour constants (change these for different deceleration rates)
      private let timerDuration = 0.025
      private let decelerationSmoothness = 0.9
      private let velocityToAngleConversion = 0.0025
      
      private func animateWithInertia(velocity: Double) {
          _ = Timer.scheduledTimer(withTimeInterval: self.timerDuration, repeats: true) { [weak self] timer in
              guard let this = self else {
                  return
              }
              let concernedVelocity = this.currentVelocity == 0.0 ? velocity : this.currentVelocity
              let newVelocity = concernedVelocity * this.decelerationSmoothness
              this.currentVelocity = newVelocity
              var angleTraversed = newVelocity * this.velocityToAngleConversion * this.maximumRotationAngleInCircle
              if !this.isClockwiseRotation {
                  angleTraversed *= -1
              }
              // exit condition
              if newVelocity < 0.1 {
                  timer.invalidate()
                  this.currentVelocity = 0.0
              } else {
                  this.traverseAngularDistance(angle: angleTraversed)
              }
          }
      }
      

      handlePanGesture 函数中包含上述函数的辅助函数、扩展和用法的完整工作代码。

      // deceleration behaviour constants (change these for different deceleration rates)
      private let timerDuration = 0.025
      private let decelerationSmoothness = 0.9
      private let velocityToAngleConversion = 0.0025
      
      private let maximumRotationAngleInCircle = 360.0
      
      private var currentRotationDegrees: Double = 0.0 {
          didSet {
              if self.currentRotationDegrees > self.maximumRotationAngleInCircle {
                  self.currentRotationDegrees = 0
              }
              if self.currentRotationDegrees < -self.maximumRotationAngleInCircle {
                  self.currentRotationDegrees = 0
              }
          }
      }
      private var previousLocation = CGPoint.zero
      private var currentLocation = CGPoint.zero
      private var velocity: Double {
          let xFactor = self.currentLocation.x - self.previousLocation.x
          let yFactor = self.currentLocation.y - self.previousLocation.y
          return Double(sqrt((xFactor * xFactor) + (yFactor * yFactor)))
      }
      
      private var currentVelocity = 0.0
      private var isClockwiseRotation = false
      
      @objc private func handlePanGesture(panGesture: UIPanGestureRecognizer) {
          let location = panGesture.location(in: self)
      
          if let rotation = panGesture.rotation {
              self.isClockwiseRotation = rotation > 0
              let angle = Double(rotation).degrees
              self.currentRotationDegrees += angle
              self.rotate(angle: angle)
          }
      
          switch panGesture.state {
          case .began, .changed:
              self.previousLocation = location
          case .ended:
              self.currentLocation = location
              self.animateWithInertia(velocity: self.velocity)
          default:
              print("Fatal State")
          }
      }
      
      private func animateWithInertia(velocity: Double) {
          _ = Timer.scheduledTimer(withTimeInterval: self.timerDuration, repeats: true) { [weak self] timer in
              guard let this = self else {
                  return
              }
              let concernedVelocity = this.currentVelocity == 0.0 ? velocity : this.currentVelocity
              let newVelocity = concernedVelocity * this.decelerationSmoothness
              this.currentVelocity = newVelocity
              var angleTraversed = newVelocity * this.velocityToAngleConversion * this.maximumRotationAngleInCircle
              if !this.isClockwiseRotation {
                  angleTraversed *= -1
              }
              if newVelocity < 0.1 {
                  timer.invalidate()
                  this.currentVelocity = 0.0
                  this.selectAtIndexPath(indexPath: this.nearestIndexPath, shouldTransformToIdentity: true)
              } else {
                  this.traverseAngularDistance(angle: angleTraversed)
              }
          }
      }
      
      private func traverseAngularDistance(angle: Double) {
          // keep the angle in -360.0 to 360.0 range
          let times = Double(Int(angle / self.maximumRotationAngleInCircle))
          var newAngle = angle - times * self.maximumRotationAngleInCircle
          if newAngle < -self.maximumRotationAngleInCircle {
              newAngle += self.maximumRotationAngleInCircle
          }
          self.currentRotationDegrees += newAngle
          self.rotate(angle: newAngle)
      }
      

      上面代码中使用的扩展:

      extension UIView {
          func rotate(angle: Double) {
              self.transform = self.transform.rotated(by: CGFloat(angle.radians))
          }
      }
      
      extension Double {
          var radians: Double {
              return (self * Double.pi)/180
          }
      
          var degrees: Double {
              return (self * 180)/Double.pi
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-16
        • 2021-11-22
        • 1970-01-01
        • 1970-01-01
        • 2012-02-04
        相关资源
        最近更新 更多