原文發(fā)布于簡書: http://m.itdecent.cn/p/9a51f7a4bfa3
作者: zerojian
大家都知道,地圖上的大頭針視圖如果想平滑的從一個坐標點移動到另一個坐標點,重新添加一個大頭針是無法實現(xiàn)這種效果的,那樣不會有一個移動效果,如果想達到類似滴滴車輛平滑移動效果,就必須在同一個大頭針對象中改變它的坐標,大頭針的視圖方向也要根據(jù)新的角度改變他的視圖角度.

我們這里新建一個 LocationManager 類用來獲取用戶的坐標,當然如果業(yè)務需求是服務器獲取,實現(xiàn)思路類似,只是改變一下坐標點的獲取方式.具體 LocationManager 單例的創(chuàng)建細節(jié)就不多說了,我們在獲取到用戶位置的回調里面添加一個通知
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
CLLocation *location = locations.firstObject;
[[NSNotificationCenter defaultCenter] postNotificationName:kdidUpdateLocation object:location];
}
主要說說自定義大頭針,大頭針分 MKAnnotationView 用來顯示大頭針視圖, 和 MKAnnotation 用來設置大頭針的一些屬性,可以簡單的把它看成一個 view - model 的關系
如果需要自定義大頭針, 需要遵守 BKAnnotation 協(xié)議,其中 coordinate 屬性是必須實現(xiàn)的屬性
@protocol MKAnnotation <NSObject>
// Center latitude and longitude of the annotation view.
// The implementation of this property must be KVO compliant.
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@optional
// Title and subtitle for use by selection UI.
@property (nonatomic, readonly, copy, nullable) NSString *title;
@property (nonatomic, readonly, copy, nullable) NSString *subtitle;
// Called as a result of dragging an annotation view.
- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate NS_AVAILABLE(10_9, 4_0);
@end
在自定義的大頭針里面我們添加幾個自定義屬性
@property (nonatomic, assign) MyAnnotationType type;
@property (nonatomic, assign) double angle;
@property (nonatomic, strong) MKAnnotationView *annotationView;
其中 type 是一個枚舉類型,用來自定義大頭針的類型, angle 表示方向,用來顯示大頭針的方向
在 .m 文件中自定義幾個內部屬性
NSMutableArray *locations;
NSArray *moveArray;
BOOL movingOver;
NSInteger currentIndex;
UIImage *annnationImage;
在大頭針的初始化方法中,監(jiān)聽定位通知,初始化保存定位的數(shù)組
- (instancetype)initWithType:(MyAnnotationType)type
coordinate:(CLLocationCoordinate2D)coordinate
angle:(double)angle {
if (self == [super init]) {
_type = type;
_coordinate = coordinate;
_angle = angle;
if (type == MyAnnotationTypeCar) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onNotifiedCurrentLocationUpdated:) name:kdidUpdateLocation
object:nil];
locations = [[NSMutableArray alloc] initWithCapacity:20];
movingOver = YES;
}
}
return self;
}
在監(jiān)聽的通知中, 我們保存獲取到的定位對象,為了保證大頭針的移動效果,我們需要在建一個移動的數(shù)組,這個移動數(shù)組保證有一定量的定位數(shù)據(jù)才開始移動動畫效果,而定位數(shù)組只負責把所有獲取到的定位對象保存下來
- (void)onNotifiedCurrentLocationUpdated:(NSNotification *)notification
{
if (notification.object) {
CLLocation *location = notification.object;
[locations addObject:location];
if (locations.count >= 5 && movingOver == YES) {
moveArray = [NSArray arrayWithArray:locations];
[locations removeAllObjects];
NSLog(@"----- moveArrayCount: %ld",moveArray.count);
NSLog(@"------beging moving -----");
currentIndex = 0;
movingOver = NO;
[self startMoving];
}
}
}
核心的移動大頭針方法,使用遞歸的方式一次次的移動大頭針,在移動數(shù)組全部使用后結束遞歸,等待新獲取到的數(shù)組
- (void)startMoving
{
NSInteger index = currentIndex % moveArray.count;
CLLocation *newLocation = moveArray[index];
self.annotationView.image = [annnationImage zj_imageRotatedByAngle:newLocation.course];
CLLocation *currentLocation = [[CLLocation alloc] initWithLatitude:self.coordinate.latitude longitude:self.coordinate.longitude];
double distance = [newLocation distanceFromLocation:currentLocation];
double speed = newLocation.speed;
CLLocationCoordinate2D newCoordinate = newLocation.coordinate;
[UIView animateWithDuration:distance/speed animations:^{
self.coordinate = newCoordinate;
currentIndex ++;
} completion:^(BOOL finished) {
if (currentIndex == moveArray.count) {
movingOver = YES;
moveArray = nil;
} else {
[self startMoving];
}
}];
}
只能需要注意的幾個細節(jié),一個是大頭針角度,在 CLLocation 對象里面是有一個角度屬性和速度屬性的,當然如果只有兩個坐標點也是可以通過算法獲取到的.
這里要用到的速度和兩個坐標點的距離用來設定車輛移動動畫的時間,這樣可以保證車輛的移動速度是均勻的平滑的.
我們在每次動畫后把創(chuàng)建的 index + 1 ,然后和數(shù)組的 count 通過取余運算獲取到下次遞歸數(shù)組使用的 index
大頭針的平滑移動,只需使用 UIView 動畫簡單的把新的坐標賦值給大頭針即可實現(xiàn).
項目 DEMO : https://github.com/ZeroJian/SmoothMovingAnnotation
有幫助給個 star 吧!