简体   繁体   English

动画UIImageView的旋转变化

[英]Animating rotation changes of UIImageView

I'm making an app that (among other things) displays a simplified compass image that rotates according to the device's rotation. 我正在创建一个应用程序(除其他外)显示一个简化的罗盘图像,根据设备的旋转旋转。 The problem is that simply doing this: 问题是,只需这样做:

float heading = -1.0f * M_PI * trueHeading / 180.0f; //trueHeading is always between 0 and 359, never 360
self.compassNeedle.transform = CGAffineTransformMakeRotation(heading);

inside CLLocationManager's didUpdateHeading method makes the animation ugly and choppy. 在CLLocationManager的didUpdateHeading方法中,使动画变得丑陋和不连贯。 I have already used Instruments to find out whether its simply my app not being able to render at more than 30-48 fps, but that's not the case. 我已经使用了Instruments来确定它的应用程序是否只能以超过30-48 fps的速度渲染,但事实并非如此。

How can I smooth out the image view's rotation so that it's more like Apple's own Compass app? 如何平滑图像视图的旋转,使其更像Apple自己的Compass应用程序?

Instead of using the current instant value, try using the average of the last N values for the true heading. 而不是使用当前的即时值,尝试使用真实标题的最后N个值的平均值。 The value may be jumping around a lot in a single instant but settle down "in the average". 价值可能在一瞬间大量跳跃,但在“平均”中稳定下来。

Assuming you have a member variable storedReadings which is an NSMutableArray: 假设你有一个成员变量storedReadings,它是一个NSMutableArray:

-(void)addReading(float):newReading
{
    [storedReadings addObject:[NSNumber numberWithFloat:newReading]];
    while([storedReadings count] > MAX_READINGS)
    {
        [storedReadings removeObjectAtIndex:0];
    }
}

then when you need the average value (timer update?) 那么当你需要平均值(计时器更新?)

-(float)calcReading
{
    float result = 0.0f;
    if([storedReadings count] > 0)
    {
       foreach(NSNumber* reading in storedReadings)
       {
           result += [reading floatValue];
       }  
       result /= [storedReadings count];
    }
    return result;
}

You get to pick MAX_READINGS a priori. 您可以先验地选择MAX_READINGS。

NEXT LEVEL(S) UP 下一级(上)

If the readings are not jumping around so much but the animation is still choppy, you probably need to do something like a "smooth" rotation. 如果读数没有跳得那么多但动画仍然不稳定,你可能需要做一些像“平滑”旋转的事情。 At any given time, you have the current angle you are displaying, theta (store this in your class, start it out at 0). 在任何给定的时间,你有你正在显示的当前角度,theta(在你的班级存储它,从0开始)。 You also have your target angle, call it target . 你也有你的目标角度,称之为目标 This is the value you get from the smoothed calcReading function. 这是从平滑的calcReading函数中获得的值。 The error is defined as the difference between the two: 错误定义为两者之间的差异:

error = target-theta;

Set up a timer callback with a period of something like 0.05 seconds (20x per second). 设置一个定时器回调,其周期为0.05秒(每秒20次)。 What you want to do is adjust theta so that the error is driven towards 0. You can do this in a couple of ways: 你想要做的是调整theta以便将error驱动为0.你可以通过以下两种方式实现:

  1. thetaNext += kProp * (target - theta); //This is proportional feedback. //这是比例反馈。
  2. thetaNext += kStep * sign(target-theta); // This moves theta a fixed amount each update. //每次更新都会移动theta一个固定的数量。 sign(x) = +1 if x >= 0 and -1 if x < 0. 如果x> = 0,则sign(x)= +1,如果x <0,则为-1。

The first solution will cause the rotation to change sharply the further it is from the target. 第一种解决方案将导致旋转从目标越远而急剧变化。 It will also probably oscillate a little bit as it swings past the "zero" point. 当它摆动超过“零”点时,它也可能会稍微摆动一下。 Bigger values of kProp will yield faster response but also more oscillation. 较大的kProp值将产生更快的响应,但也会产生更多的振荡。 Some tuning will be required. 需要进行一些调整。

The second solution will be much easier to control...it just "ticks" the compass needle around each time. 第二种解决方案将更容易控制......它只是每次“嘀嗒”罗盘针。 You can set kStep to something like 1/4 degree, which gives you a "speed" of rotation of about (1/4 deg/update) * (20 updates/seconds) = 5 degrees per second. 你可以将kStep设置为1/4度,这使你的旋转“速度”约为(1/4度/更新​​)*(20更新/秒)=每秒5度。 This is a bit slow, but you can see the math and change kStep to suit your needs. 这有点慢,但您可以看到数学并更改kStep以满足您的需求。 Note that you may to "band" the "error" value so that no action is taken if the error < kStep (or something like that). 请注意 ,您可以“绑定”“错误”值,以便在错误<kStep(或类似的东西)时不采取任何操作。 This prevents your compass from shifting when the angle is really close to the target. 当角度非常接近目标时,这可以防止指南针移动。 You can change kStep when the error is small so that it "slides" into the ending position (ie kStep is smaller when the error is small). 您可以在错误较小时更改kStep,以便它“滑动”到结束位置(即,当错误很小时,kStep会更小)。

For dealing with Angle Issues (wrap around), I "normalize" the angle so it is always within -Pi/Pi. 为了处理角度问题(环绕),我将角度“标准化”,使其始终在-Pi / Pi内。 I don't guarantee this is the perfect way to do it, but it seems to get the job done: 我不保证这是完美的方式,但它似乎完成了工作:

   // Takes an angle greater than +/- M_PI and converts it back
   // to +/- M_PI.  Useful in Box2D where angles continuously
   // increase/decrease.
   static inline float32 AdjustAngle(float32 angleRads)
   {
      if(angleRads > M_PI)
      {
         while(angleRads > M_PI)
         {
            angleRads -= 2*M_PI;
         }
      }
      else if(angleRads < -M_PI)
      {
         while(angleRads < -M_PI)
         {
            angleRads += 2*M_PI;
         }
      }
      return angleRads;
   }

By doing it this way, -pi is the angle you reach from going in either direction as you continue to rotate left/right. 通过这种方式,-pi是您在向左/向右继续旋转时从任一方向前进的角度。 That is to say, there is not a discontinuity in the number going from say 0 to 359 degrees. 也就是说,从0到359度的数字没有不连续性。

SO PUTTING THIS ALL TOGETHER 所以把这一切都放在一起

static inline float Sign(float value)
{
   if(value >= 0)
      return 1.0f;
   return -1.0f;
}

//#define ROTATION_OPTION_1
//#define ROTATION_OPTION_2
#define ROTATION_OPTION_3

-(void)updateArrow
{
   // Calculate the angle to the player
   CGPoint toPlayer = ccpSub(self.player.position,self.arrow.position);
   // Calculate the angle of this...Note there are some inversions
   // and the actual image is rotated 90 degrees so I had to offset it
   // a bit.
   float angleToPlayerRads = -atan2f(toPlayer.y, toPlayer.x);
   angleToPlayerRads = AdjustAngle(angleToPlayerRads);

   // This is the angle we "wish" the arrow would be pointing.
   float targetAngle = CC_RADIANS_TO_DEGREES(angleToPlayerRads)+90;
   float errorAngle = targetAngle-self.arrow.rotation;

   CCLOG(@"Error Angle = %f",errorAngle);


#ifdef ROTATION_OPTION_1
   // In this option, we just set the angle of the rotated sprite directly.
   self.arrow.rotation = CC_RADIANS_TO_DEGREES(angleToPlayerRads)+90;
#endif


#ifdef ROTATION_OPTION_2
   // In this option, we apply proportional feedback to the angle
   // difference.
   const float kProp = 0.05f;
   self.arrow.rotation += kProp * (errorAngle);
#endif

#ifdef ROTATION_OPTION_3
   // The step to take each update in degrees.
   const float kStep = 4.0f;
   //  NOTE:  Without the "if(fabs(...)) check, the angle
   // can "dither" around the zero point when it is very close.
   if(fabs(errorAngle) > kStep)
   {
      self.arrow.rotation += Sign(errorAngle)*kStep;
   }
#endif
}

I put this code into a demo program I had written for Cocos2d. 我把这段代码放到了我为Cocos2d编写的演示程序中。 It shows a character (big box) being chased by some monsters (smaller boxes) and has an arrow in the center that always points towards the character. 它显示了被一些怪物(较小的盒子)追逐的角色(大盒子),并且在中心一个始终指向角色的箭头 The updateArrow call is made on a timer tick (the update(dt) function) regularly. updateArrow调用定期在计时器滴答(更新(dt)函数)上进行。 The player's position on the screen is set by the user tapping on the screen and the angle is based on the vector from the arrow to the player. 玩家在屏幕上的位置由用户点击屏幕设置,角度基于从箭头到玩家的向量。 In the function, I show all three options for setting the angle of the arrow: 在该功能中,我显示了设置箭头角度的所有三个选项:

Option 1 选项1

Just set it based on where the player is (ie just set it). 只需根据玩家所在位置设置(即设置)。

Option 2 选项2

Use proportional feedback to adjust the arrow's angle each time step. 使用比例反馈每次调整箭头的角度。

Option 3 选项3

Step the angle of the arrow each timestep a little bit if the error angle is more than the step size. 如果误差角度大于步长,则逐步调整箭头的角度。

Here is a picture showing roughly what it looks like: 这张照片大致显示了它的样子:

在此输入图像描述

And, all the code is available here on github . 并且, 所有代码都可以在github上找到 Just look in the HelloWorldLayer.m file. 只需查看HelloWorldLayer.m文件即可。

Was this helpful? 这个有帮助吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM