在刚接入地图后,发现地图的缩放在手指离开后就戛然而止,这和人家的体验不太一样啊。有点尬,开始解决一下这个问题。文章代码是在上一篇代码的基础上,如果有什么疑问可以看下上一篇
一、解决思路
让地图在手指离开后不立马停止缩放,而是继续缩放一定的比例后停止。什么时候停止?在手指离开后到停止这段时间为百度地图设置的缩放值如何变化?基于这两个问题,现在有两种思路:
(1)自己指定继续缩放的次数与时间,通过 Handler 实现:(这个是开发同一项目的一位同事的思路)
val none = 0 //初始
val large = 1 //放大
val small = -1 //缩小
var mode = 0
baiduMap.setOnMapTouchListener { val pointerCount = it.pointerCount when (it.action) { MotionEvent.ACTION_DOWN -> { mapView.map.uiSettings.isScrollGesturesEnabled = true } MotionEvent.ACTION_MOVE -> { if (pointerCount >= 2) { if (fingersStep == null) { fingersStep = (it.getX(0) - it.getX(1)) * (it.getX(0) - it.getX(1)) + (it.getY(0) - it.getY(1)) * (it.getY(0) - it.getY(1)) } val temp = (it.getX(0) - it.getX(1)) * (it.getX(0) - it.getX(1)) + (it.getY(0) - it.getY(1)) * (it.getY(0) - it.getY(1)) val size = temp - fingersStep!! mode = when { size == 0f -> none size > 0 -> large else -> small } fingersStep = temp mapView.map.uiSettings.isScrollGesturesEnabled = false } } MotionEvent.ACTION_UP -> { handler.postDelayed({ mapView.map.uiSettings.isScrollGesturesEnabled = true }, 500) when (mode) { small, large -> { letMapSmallerOrLarger(mode) } } fingersStep = null mode = none } } }
/** * 地图放大或者缩小 */ private fun letMapSmallerOrLarger(state: Int) { var zoom: Float handler.postDelayed({ zoom = mapChangeStatus.zoom zoom += (0.5f * state) baiduMap.animateMapStatus(MapStatusUpdateFactory.newLatLngZoom(mapStatus.value!!.target, zoom)) handler.postDelayed({ zoom += (0.3f * state) baiduMap.animateMapStatus(MapStatusUpdateFactory.newLatLngZoom(mapStatus.value!!.target, zoom)) handler.postDelayed({ zoom += (0.1f * state) baiduMap.animateMapStatus(MapStatusUpdateFactory.newLatLngZoom(mapStatus.value!!.target, zoom)) }, 96) }, 64) }, 32) }
这种思路也可以在视觉上展现平滑的效果,但是有一个缺点,那就是不管是你操作的动作有多大,它的平滑效果都是一样的。就像你演小品和宋小宝演小品,观众的反应是一样的。这就涉及到一个词 —— 惯性,下面这种思路就是惯性的平滑缩放地图
(2)惯性的平滑缩放地图
缩放地图的动作大小就是指滑动速度v,v越大,惯性越大,但是它慢慢的会变为零。符合这个特性的,上一张图你就明白:(注意在手势操作期间并不一定是按照这个函数,我们只是通过两个坐标模拟后续的值,实现平滑缩放)
仔细看一下图片就知道: 缩放 z 与 时间 t 是一个一元二次方程: z = at2 + bt + c ,对其求导可得:v = 2at + b,v=0 时,tm = -b/2a 。假如我们已经解出这个方程,然后可以得到 tm . 让地图在 t2 到 tm 这段时间内按照对应的函数值 z 进行缩放即可。怎么样是不是很奶思,现在剩下的问题就是如何解这个方程了。看看我们目前有什么:
1. 两个点的坐标,地图开始缩放的时间与缩放值 (t1,z1) ; 地图结束缩放后的时间与缩放值 (t2,z2) 注:(tm,zm) 为我们要惯性的平滑缩放地图最终的结束点
2. v = 2at + b ,所以 a 是一个加速度 ,这个可以由我们自己来决定。所以 a 也拿到了。
两个坐标一个 a ,那 b、c 是不是都出来了哈。通过图片也可以看出缩小和放大是有区别的。下面我们就看代码吧。
class MapOverlayLayout(context: Context?, attrs: AttributeSet?) : RelativeLayout(context, attrs) { private var mMapView: MapView? = null private var isMutilPoint = false private var isOnePoint = true private lateinit var startZoomPair: Pair<Long, Float> private var endZoom = Variable<Pair<Long, Float>>() init { startSlideScaleMap() } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { super.onLayout(changed, l, t, r, b) if (changed) setup() } private fun setup() { if (mMapView != null) return for (i in 0 until childCount) { val child = getChildAt(i) if (child != null && child is MapView) { mMapView = child break } } if (childCount > 0 && mMapView == null) Log.e(this.javaClass.simpleName, "未将地图放置在子节点下") } fun setBaiduMap(mapView: MapView) { mMapView = mapView } override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { var isIntercept = false when (ev!!.action) { MotionEvent.ACTION_DOWN -> { startZoomPair = Pair(System.currentTimeMillis() % 100000, mMapView!!.map.mapStatus.zoom) } MotionEvent.ACTION_MOVE -> { mMapView!!.map.uiSettings.isScrollGesturesEnabled = !isMutilPoint && isOnePoint if (ev.pointerCount >= 2) { isMutilPoint = true } if (ev.pointerCount == 1) { isOnePoint = true } } MotionEvent.ACTION_UP -> { if (isMutilPoint) { endZoom new Pair(System.currentTimeMillis() % 100000, mMapView!!.map.mapStatus.zoom) } isMutilPoint = false isOnePoint = true } } return isIntercept } private fun startSlideScaleMap() { endZoom.subscribeOn(Schedulers.computation()) .subscribe { if (mMapView == null) { Log.e(this.javaClass.simpleName, "未将地图放置在子节点下") return@subscribe } var t1 = startZoomPair.first.toFloat() val zoom1 = startZoomPair.second var t2 = it.first.toFloat() val temp = (t2 - t1) / 1000 t1 = 0f t2 = (t1 + temp) val zoom2 = it.second
// 判断是缩小还是放大,设置 a 值 val a = if (zoom2 > zoom1) -3 else 3 val b = (zoom2 - zoom1) / (t2 - t1) - a * (t1 + t2) val c = zoom1 val tStirless = -b / (2 * a) val tMove = tStirless - t2 val MAP_ZOOM_NUMS = 10 val unitZoomLevelDuringTime = tMove / MAP_ZOOM_NUMS var lastZoom = zoom2 for (i in 1..MAP_ZOOM_NUMS) { val tempT = t2 + i * unitZoomLevelDuringTime val temZoom = a * tempT * tempT + b * tempT + c var duty: Float if (lastZoom > zoom1 && temZoom > lastZoom) { duty = temZoom - lastZoom if (lastZoom - zoom2 > 1) return@subscribe mMapView!!.map.animateMapStatus(MapStatusUpdateFactory.zoomBy(duty)) lastZoom = temZoom } if (lastZoom < zoom1 && temZoom < lastZoom) { duty = temZoom - lastZoom if (zoom2 - lastZoom > 1) return@subscribe mMapView!!.map.animateMapStatus(MapStatusUpdateFactory.zoomBy(duty)) lastZoom = temZoom } } } } }
代码说明一下,在 onInterceptTouchEvent 获取两个坐标点的值,起始点坐标 startZoomPair ,结束点项目中通过 rxjava 观测结束点变化 ,方程函数操作在 startSlideScaleMap() 方法中 ,代码中时间基点取为了 0 ,所时间 t1 在运算时为 0 , t2 为其差值
昨天下班匆忙,今天扫个尾。对于已经实现了地图的平滑缩放,我们可以通过调整 a 的值来控制顺滑缩放的速度,以及加一些判断控制平滑缩放的缩放层级。结束了哈,工作愉快。附上借鉴的一片文章:
https://www.aliyun.com/jiaocheng/355235.html
以及MapOverlayLayout源码: 链接: https://pan.baidu.com/s/1GqZQ7wgozC0gDbizLrLHEw 密码: 2hqp