(转载)基于LBS地图的开发,满足地图上有头像的需求
最近做的项目主要是LBS这块 主打成员定位功能 我们的UI设计是这样的

乍一看上去是挺好挺美观的 不同的人会显示不同的头像 可是当人扎堆的时候 问题就来了

当人多的时候(例如上图所示) 地图滑动起来就能感觉到明显顿卡 那种不流畅感能折磨死人 所以 自然我们要解决这个问题(等等 先不要吐槽为什么不用地图聚合 因为这已经是地图放到最大了 聚合不适合这次的问题讨论)
分析
首先看下我是怎么实现这个annotationView的 由于这个annotationsView是异形的(也就是无法通过设置圆角直接得到) 而且里面的图片还因用户而异 所以解决方案就是使用layer.mask来进行遮罩 代码如下
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
@implementation MMAnnotationView- (instancetype)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier{ self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; if ( self ) { self.frame = CGRectMake(0, 0, TRACK_ANNOTATION_SIZE.width, TRACK_ANNOTATION_SIZE.height); self.centerOffset = CGPointMake(0, -(TRACK_ANNOTATION_SIZE.height-3)/2); self.canShowCallout = NO; self.avatarView = [[UIImageView alloc] initWithFrame:self.bounds]; [self addSubview:self.avatarView]; self.avatarView.contentMode = UIViewContentModeScaleAspectFill; CAShapeLayer *shapelayer = [CAShapeLayer layer]; shapelayer.frame = self.bounds; shapelayer.path = self.framePath.CGPath; self.avatarView.layer.mask = shapelayer; self.layer.shadowPath = self.framePath.CGPath; self.layer.shadowRadius = 1.0f; self.layer.shadowColor = [UIColor colorWithHex:0x666666FF].CGColor; self.layer.shadowOpacity = 1.0f; self.layer.shadowOffset = CGSizeMake(0, 0); self.layer.masksToBounds = NO; } return self;}//mask路径- (UIBezierPath *)framePath{ if ( !_framePath ) { CGFloat arrowWidth = 14; CGMutablePathRef path = CGPathCreateMutable(); CGRect rectangle = CGRectInset(CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetWidth(self.bounds)), 3,3); CGPoint p[3] = { {CGRectGetMidX(self.bounds)-arrowWidth/2, CGRectGetWidth(self.bounds)-6}, {CGRectGetMidX(self.bounds)+arrowWidth/2, CGRectGetWidth(self.bounds)-6}, {CGRectGetMidX(self.bounds), CGRectGetHeight(self.bounds)-4} }; CGPathAddRoundedRect(path, NULL, rectangle, 5, 5); CGPathAddLines(path, NULL, p, 3); CGPathCloseSubpath(path); _framePath = [UIBezierPath bezierPathWithCGPath:path]; CGPathRelease(path); } return _framePath;} |
我用代码生成了形状路径 并以此生成了layer的mask和shadowPath
使用时 只要直接用SDWebImage设置头像就行了
|
1
|
[annotationView.avatarView sd_setImageWithURL:[NSURL URLWithString:avatarURL] placeholderImage:placeHolderImage]; |
接下来用工具分析一下问题出来哪 分析性能当然是选择Instrments(用法在这里就不做介绍了) 打开Core Animation 然后运行程序 滑动地图 可以看到性能分析如下

原来平均帧数只有不到30帧 这离我们的目标60帧差得实在太远
再使用Debug Option来深入分析一下

由于MKMapView的原因 这里我们主要关心这几个选项
Color Blended Layers
Color Misaligned Images
Color Offscreen-Rendered Yellow
分别打开这几个选项 结果如下

可以看到
Color Blended Layers没有问题 不过这也是正常的 由于使用了mask 没有透明的地方
Color Misaligned Images除了默认头像外全中 这是因为服务器上的图片大小跟显示的大小不一致 导致缩放 而默认头像则是一致的 所以没问题
Color Offscreen-Rendered Yellow全中 由于使用了mask 导致大量的离屏渲染 这也是性能下降的主要原因
解决
问题的原因找到了 那么接下来该如何解决呢?
首先mask是肯定不能用了
其次下载下来的图片我们要预处理成实际大小
那么 直接把下载下来的图片合成为我们要显示的最终结果不就ok了吗? 试试看
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
- (void)loadAnnotationImageWithURL:(NSString*)url imageView:(UIImageView*)imageView{ //将合成后的图片缓存起来 NSString *annoImageURL = url; NSString *annoImageCacheURL = [annoImageURL stringByAppendingString:@"cache"]; UIImage *cacheImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:annoImageCacheURL]; if ( cacheImage ) { //LLLog(@"hit cache"); imageView.image = cacheImage; } else { //LLLog(@"no cache"); [imageView sd_setImageWithURL:[NSURL URLWithString:annoImageURL] placeholderImage:placeHolderImage completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (!error) { UIImage *annoImage = [image annotationImage]; imageView.image = annoImage; [[SDImageCache sharedImageCache] storeImage:annoImage forKey:annoImageCacheURL]; } }]; }}@implementation UIImage (LJC)- (UIImage*) annotationImage{ static UIView *snapshotView = nil; static UIImageView *imageView = nil; if ( !snapshotView ) { snapshotView = [UIView new]; snapshotView.frame = CGRectMake(0, 0, TRACK_ANNOTATION_SIZE.width, TRACK_ANNOTATION_SIZE.height); imageView = [UIImageView new]; [snapshotView addSubview:imageView]; imageView.clipsToBounds = YES; imageView.frame = snapshotView.bounds; imageView.contentMode = UIViewContentModeScaleAspectFill; CGFloat arrowWidth = 14; CGMutablePathRef path = CGPathCreateMutable(); CGRect rectangle = CGRectInset(CGRectMake(0, 0, CGRectGetWidth(imageView.bounds), CGRectGetWidth(imageView.bounds)), 3,3); CGPoint p[3] = { {CGRectGetMidX(imageView.bounds)-arrowWidth/2, CGRectGetWidth(imageView.bounds)-6}, {CGRectGetMidX(imageView.bounds)+arrowWidth/2, CGRectGetWidth(imageView.bounds)-6}, {CGRectGetMidX(imageView.bounds), CGRectGetHeight(imageView.bounds)-4} }; CGPathAddRoundedRect(path, NULL, rectangle, 5, 5); CGPathAddLines(path, NULL, p, 3); CGPathCloseSubpath(path); CAShapeLayer *shapelayer = [CAShapeLayer layer]; shapelayer.frame = imageView.bounds; shapelayer.path = path; imageView.layer.mask = shapelayer; snapshotView.layer.shadowPath = path; snapshotView.layer.shadowRadius = 1.0f; snapshotView.layer.shadowColor = [UIColor colorWithHex:0x666666FF].CGColor; snapshotView.layer.shadowOpacity = 1.0f; snapshotView.layer.shadowOffset = CGSizeMake(0, 0); CGPathRelease(path); } imageView.image = self; UIGraphicsBeginImageContextWithOptions(TRACK_ANNOTATION_SIZE, NO, 0); [snapshotView.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *copied = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return copied;}@end |
然后使用的时候 只要简单的如下调用就OK了
|
1
|
[self loadAnnotationImageWithURL:avatarURL imageView:annotationView.avatarView]; |
看看修改之后的Instruments表现如何

Color Blended Layers全中 这也是无可避免的 因为显示的就是一张带透明度的图 但是由于地图的特殊性(头像的位置变化间隔较长 所以不会经常引发合成 也没有动画) 所以这里也不是问题
Color Misaligned Images没问题了 因为头像已被缩放成了相同大小
Color Offscreen-Rendered Yellow没问题了 因为只是简单的显示了一张图片 而并没有需要离屏渲染的东西了
再来看下帧数情况

Oh-Yeah~ 不光帧数达到了我们的目标60帧(由于还有业务逻辑线程在后台跑 所以没有那么的稳定) 就连平均运行耗时都下降了不少 就算地图上再多显示几十个人 也不成问题了
小结
不光是MKMapView 其实包括UITableView在内的很多地方都可以用文中所说的方法去优化 其核心点就是 合成+缓存当然 由于合成还是会耗费一部分资源的 所以比较适合头像这种小的资源
关于图形性能优化 可以看下这篇好文(有对文中提到的Debug Option不太明白的 这里有详细的解释)
(转载)基于LBS地图的开发,满足地图上有头像的需求的更多相关文章
- 使用ESMap的地图平台开发三维地图
本文简单的介绍使用ESmap的SDK开发(DIY自己地图的)一个地图的过程.若有不足,欢迎指正. 一.创建地图 只需四步,从无到有,在浏览器中创建一个自己的三维地图,炫酷到爆! 第一步:引入ESM ...
- 百度地图API开发----手机地图做导航功能
第一种方式:手机网页点击打开直接进百度地图APP <a href="baidumap://map/direction?mode=[transit:公交,driving:驾车]& ...
- 关于高德地图Android开发时地图只显示一次、第二次打开不定位的解决办法
我按照高德官方Demo改的 第一次是可以定位的,如左图 第二次就不能定位了,如右图 在onDestory中把aMap置为空即可 aMap = null; 修改完如下图: 原理是第二次打开时aMap不为 ...
- 【转载】MSXML应用总结 开发篇(上)
原文:http://blog.sina.com.cn/s/blog_48f93b530100ejv9.html 本篇是接前文“MSXML应用总结 概念篇”写的,主要总结一下MSXML DOM接口的应用 ...
- 基于MFC与第三方类CWebPage的百度地图API开发范例
在进行百度地图API开发之前你需要到http://developer.baidu.com/map申请密匙 密匙申请之后就可以进行百度地图API的开发了. 下面我们以在visual c++6.0里进行地 ...
- 记录开发基于百度地图API实现在地图上绘制轨迹并拾取轨迹对应经纬度的工具说明
前言: 最近一直在做数据可视化方面的工作,其中平面可视化没什么难度,毕竟已经有很多成熟的可供使用的框架,比如百度的echart.js,highcharts.js等.还有就是3D可视化了,整体来说难度也 ...
- 跨平台移动开发_PhoneGap 使用Geolocation基于所在地理位置坐标调用百度地图API
使用Geolocation基于所在地理位置坐标调用百度地图API 效果图 示例代码 <!DOCTYPE html> <html> <head> <title& ...
- 转-iOS开发系列--地图与定位
来自: http://www.cnblogs.com/kenshincui/p/4125570.html#autoid-3-4-0 概览 现在很多社交.电商.团购应用都引入了地图和定位功能,似乎地图功 ...
- iOS开发系列--地图与定位
概览 现在很多社交.电商.团购应用都引入了地图和定位功能,似乎地图功能不再是地图应用和导航应用所特有的.的确,有了地图和定位功能确实让我们的生活更加丰富多彩,极大的改变了我们的生活方式.例如你到了一个 ...
随机推荐
- CI生成查询记录集result(),row(),row_array().....
result() 该方法执行成功返回一个对象数组,失败则返回一个空数组. 一般情况下,我们使用下面的方法遍历结果,代码就像这样: $query = $this->db->query(&qu ...
- Android 怎么退出整个应用程序?
方法一: 我们在写android应用程序时,经常会遇到想退出当前Acitivity,或者直接退出应用程序.我之前的一般操作是按返回键,或者直接按home键直接返回,其实这两种操作都没有关闭当前应用程序 ...
- MySQL流程函数
#如果value是真,返回t:否则返回f if(value,t f) #如果value1不为空,返回value1,否则返回value2 ifnull(value1,value2) #如果value1是 ...
- 【luogu】 P1880 石子合并
原题原题原题原题原题 先贴上错误代码... ↓错误代码↓ #include <iostream> #include <cstdio> #include <cstring& ...
- [转]定位占用oracle数据库cpu过高的sql
今天在吃饭的时候我的朋友的数据库出现了问题,cpu占用率为97%,当我看到这个问题的时候我就想到了或许是sql导致的此问题,由于忍不住吃饭,暂时没有帮他看这个问题,这是我饭后自己模拟的故障,进行的分析 ...
- js 模版加载到前端
js 模版加载到前端 简单有效不高端 配个路由 /js/:filename , 用 readTemplate 响应请求,前端可以按模块方式直接 require 模板 'use strict' var ...
- VB6.0 为批量字体改名
从网上下载了一个字符包,解压以后查看,发现文件名是这种形式:0120_XXXXXX_GBK.ttf,看上去很不雅观.我想改成 XXXXXX简体.ttf 这种形式,但字体有300多个,手动修改太浪费时间 ...
- C ReadProcessMemory
ReadProcessMemory函数用于读取其他进程的数据. BOOL STDCALL ReadProcessMemory ( HANDLE hProcess, LPCVOID lpBaseAddr ...
- LoadingView
// // LoadingView.h // 蓝桥播报 // // Created by 小小建 on 15/7/10. // Copyright (c) 2015年 蓝桥. All righ ...
- clojure基础入门(一)
最近在看storm的源码,就学习分享下clojure语法. 阅读目录: 概述 变量 运算符 流程控制 总结 概述 clojure是一种运行在JVM上的Lisp方言,属于函数式编程范式,它和java可以 ...