如何使用MKAnnotationView创建自定义的“标注点落下”动画?

24
我有一个 MKMapView 实例,想要使用自定义注释图标代替MKPinAnnotationView提供的标准标记图标。所以,我设置了一个MKAnnotationView的子类CustomMapAnnotation,并重写了-(void)drawRect: 方法来绘制CGImage,这很有效。
问题在于,当我尝试复制MKPinAnnotationView提供的.animatesDrop 功能时,出现了困难;我希望我的图标逐渐出现,从上方向下降,并按从左到右的顺序添加到MKMapView 实例中。
这是CustomMapAnnotation的-(void)drawRect: 方法,如果只绘制UIImage,则可以正常工作(第二行是绘制UIImage)。
- (void)drawRect:(CGRect)rect {
 [super drawRect:rect];
 [((Incident *)self.annotation).smallIcon drawInRect:rect];
 if (newAnnotation) {
  [self animateDrop];
  newAnnotation = NO;
 }
} 

当你添加animateDrop方法时问题就出现了:

-(void)animateDrop {
 CGContextRef myContext = UIGraphicsGetCurrentContext();

 CGPoint finalPos = self.center;
 CGPoint startPos = CGPointMake(self.center.x, self.center.y-480.0);
 self.layer.position = startPos;

 CABasicAnimation *theAnimation;
 theAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
 theAnimation.fromValue=[NSValue valueWithCGPoint:startPos];
 theAnimation.toValue=[NSValue valueWithCGPoint:finalPos];
 theAnimation.removedOnCompletion = NO;
 theAnimation.fillMode = kCAFillModeForwards;
 theAnimation.delegate = self;
 theAnimation.beginTime = 5.0 * (self.center.x/320.0);
 theAnimation.duration = 1.0;
 [self.layer addAnimation:theAnimation forKey:@""];
}

它只是不起作用,可能有很多原因。我现在不会深入讨论所有原因。我的主要问题是这个方法是否可行,或者是否应该尝试完全不同的方法。

我还尝试将整个过程打包成动画事务,以便beginTime参数实际起作用;但似乎完全没有任何效果。我不知道这是因为我缺少关键点还是因为MapKit在某种程度上破坏了我的动画。

  // Does nothing
  [CATransaction begin];
  [map addAnnotations:list];
  [CATransaction commit];

如果有人对像这样的动画MKMapAnnotations有任何经验,我很想得到一些提示;否则,如果您能就此方法提供CAAnimation建议,那也很好。


这不是一个下落动画,而只是一个不错的动画。 - Pramod More
5个回答

58

Paul Shapiro的代码存在一个问题,就是当您在用户正在查看的位置下方添加注释时,它无法处理。这些注释会漂浮在空中,然后掉落,因为它们被移动到用户可见的地图矩形中。

另一个问题是它还会丢失用户位置的蓝点。使用下面的代码,您可以处理用户位置和大量的地图注释屏幕外。我还添加了一个不错的弹跳效果 ;)

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
    MKAnnotationView *aV; 

    for (aV in views) {

        // Don't pin drop if annotation is user location
        if ([aV.annotation isKindOfClass:[MKUserLocation class]]) {
            continue;
        }

        // Check if current annotation is inside visible map rect, else go to next one
        MKMapPoint point =  MKMapPointForCoordinate(aV.annotation.coordinate);
        if (!MKMapRectContainsPoint(self.mapView.visibleMapRect, point)) {
            continue;
        }

        CGRect endFrame = aV.frame;

        // Move annotation out of view
        aV.frame = CGRectMake(aV.frame.origin.x, aV.frame.origin.y - self.view.frame.size.height, aV.frame.size.width, aV.frame.size.height);

        // Animate drop
        [UIView animateWithDuration:0.5 delay:0.04*[views indexOfObject:aV] options:UIViewAnimationCurveLinear animations:^{

            aV.frame = endFrame;

        // Animate squash
        }completion:^(BOOL finished){
            if (finished) {
                [UIView animateWithDuration:0.05 animations:^{
                    aV.transform = CGAffineTransformMakeScale(1.0, 0.8);

                }completion:^(BOOL finished){
                    [UIView animateWithDuration:0.1 animations:^{
                        aV.transform = CGAffineTransformIdentity;
                    }];
                }];
            }
        }];
    }
}

1
这个答案简直太棒了。如果你喜欢像地图应用程序中一样超酷的动画效果,那么请自己考虑使用这段代码,并给自己一个方便之举。 - xci
5
太棒了。一处改进:将 CGAffineTransformMakeScale 的动画挤压变换改为 aV.transform = CGAffineTransformMake(1.0, 0, 0, 0.8, 0, + aV.frame.size.height*0.1);,这样可以使注释视图向下挤压到其底部边缘(然后弹回),而不是均匀地压缩到中心。 - William Denniss
@WilliamDenniss 谢谢你的改进!这是一个非常好的提升。 - Phong Le
1
太棒了,这激励我去研究仿射变换。谢谢! - Joe D'Andrea

52

首先,如果你的视图控制器尚未实现MKMapViewDelegate,则需要让它实现。

接着,实现以下方法:

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views { 
   MKAnnotationView *aV; 
   for (aV in views) {
     CGRect endFrame = aV.frame;

     aV.frame = CGRectMake(aV.frame.origin.x, aV.frame.origin.y - 230.0, aV.frame.size.width, aV.frame.size.height);

     [UIView beginAnimations:nil context:NULL];
     [UIView setAnimationDuration:0.45];
     [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
         [aV setFrame:endFrame];
     [UIView commitAnimations];

   }
}

将您的注释添加到MapView中,当它们被添加时,此委托方法将被调用,并且在添加时将从顶部向底部动画显示引脚。

定时和定位的值可以稍微更改,但我已经调整过它,使其看起来最好/最接近传统的下拉(就我所测试的而言)。希望这可以帮助到您!


你能帮我解决问题吗?我需要在气泡中显示4行数据,例如一个标题、描述、日期和位置名称。所有的示例都只显示2行详细信息。我尝试将所需数据附加到副标题中,并用\n字符分隔,但它没有起作用。请帮帮我。 - S.P.
Sijo,请看一下这个链接。 http://stackoverflow.com/questions/1350050/custom-mkannotationview-callout - Paul Shapiro
3
如果您迭代一个浮点数,可以通过 [UIView setAnimationDelay:offset] 来调整起始时间,以获得顺序播放的动画。 - Corey Floyd

12

或者,如果你正在创建一个MKAnnotationView子类,你可以使用didMoveToSuperview触发动画。以下代码实现了一个下落并以轻微的“压扁”效果结束的动画。

  #define kDropCompressAmount 0.1

  @implementation MyAnnotationViewSubclass

  ...

  - (void)didMoveToSuperview {
      CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
      animation.duration = 0.4;
      animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
      animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, -400, 0)];
      animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];

      CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform"];
      animation2.duration = 0.10;
      animation2.beginTime = animation.duration;
      animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
      animation2.toValue = [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DMakeTranslation(0, self.layer.frame.size.height*kDropCompressAmount, 0), 1.0, 1.0-kDropCompressAmount, 1.0)];
      animation2.fillMode = kCAFillModeForwards;

      CABasicAnimation *animation3 = [CABasicAnimation animationWithKeyPath:@"transform"];
      animation3.duration = 0.15;
      animation3.beginTime = animation.duration+animation2.duration;
      animation3.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
      animation3.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
      animation3.fillMode = kCAFillModeForwards;

      CAAnimationGroup *group = [CAAnimationGroup animation];
      group.animations = [NSArray arrayWithObjects:animation, animation2, animation3, nil];
      group.duration = animation.duration+animation2.duration+animation3.duration;
      group.fillMode = kCAFillModeForwards;

      [self.layer addAnimation:group forKey:nil];
  }

5
对于Michael Tyson的回复(我还不能在所有地方发表评论),我建议在didMoveToSuperview中插入以下代码,以便适当地重用MKAnnotationView,这样它就会再次执行动画,然后模拟注释的顺序添加。
通过调整分隔符和乘数可以获得不同的视觉效果...
- (void)didMoveToSuperview {
    //necessary so it doesn't add another animation when moved to superview = nil
    //and to remove the previous animations if they were not finished!
    if (!self.superview) {
        [self.layer removeAllAnimations];
        return;
    }


    float xOriginDivider = 20.;
    float pos = 0;

    UIView *mySuperview = self.superview;
    while (mySuperview && ![mySuperview isKindOfClass:[MKMapView class]])
        mySuperview = mySuperview.superview;
    if ([mySuperview isKindOfClass:[MKMapView class]]) 
        //given the position in the array
        // pos = [((MKMapView *) mySuperview).annotations indexOfObject:self.annotation];   
        // left to right sequence;
        pos = [((MKMapView *) mySuperview) convertCoordinate:self.annotation.coordinate toPointToView:mySuperview].x / xOriginDivider;

    float yOffsetMultiplier = 20.;
    float timeOffsetMultiplier = 0.05;


    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
    animation.duration = 0.4 + timeOffsetMultiplier * pos;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, -400 - yOffsetMultiplier * pos, 0)];
    animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];

    // rest of animation group...
}

1
对于我的应用程序,在iOS 6中,这里的第一个代码块非常关键,但在之前的版本中则不是。谢谢。 - Keith

0

我认为更好的选择是将“animatesDrop”设置为YES。这是MKPinAnnotationView的一个属性。


它将在地图上落下一个动画标记,但不会随机动画化。 - Gajendra K Chauhan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接