多点触控基本上可以分为3种类型:
接力型: 新加进来的手指控制屏幕
配合型: 多个手指共同控制屏幕
各自为战型: 多个手指各自控制屏幕
现在就分别对这三种的实现做个小结:
开始之前我们先看下单点触控怎么实现
1 class MultiTouchView(context: Context, attributeSet: AttributeSet): View(context, attributeSet) { 2 3 private val paint = Paint(Paint.ANTI_ALIAS_FLAG) 4 val bitmap = getBitMap(resources,100f.toPx.toInt()) 5 var offSetX = 0f 6 var offSetY = 0f 7 //手指落下时的位置 8 var downX = 0f 9 var downY = 0f 10 //初始位置 11 var originalOffSetX = 0f 12 var originalOffSetY = 0f 13 14 override fun onDraw(canvas: Canvas) { 15 super.onDraw(canvas) 16 canvas.drawBitmap(bitmap, offSetX, offSetY, paint) 17 } 18 19 override fun onTouchEvent(event: MotionEvent): Boolean { 20 //先看下单点触控怎么实现 21 when(event.actionMasked){ 22 MotionEvent.ACTION_DOWN -> { 23 downX = event.x 24 downY = event.y 25 //每次有新的action_down事件时, 把上次的offset赋值给originalOffSet 26 originalOffSetX = offSetX 27 originalOffSetY = offSetY 28 } 29 MotionEvent.ACTION_MOVE -> { 30 //为了移动时让图片基于手指落下时的位置移动,我们应该给它减掉手指落下时的位置 31 //第一次事件序列结束后, 图片可能已经移动到了某个位置, 那么下次move事件时,应该基于它来移动 32 offSetX = originalOffSetX + event.x - downX 33 offSetY = originalOffSetY + event.y - downY 34 invalidate() 35 } 36 } 37 //这里必须返回true以消费事件 38 return true 39 } 40 }
现在看看接力型:接力型的关键点在于找到新加进来的那个手指, 把控制权交给它
1 class MultiTouchView(context: Context, attributeSet: AttributeSet): View(context, attributeSet) { 2 3 private val paint = Paint(Paint.ANTI_ALIAS_FLAG) 4 val bitmap = getBitMap(resources,100f.toPx.toInt()) 5 var offSetX = 0f 6 var offSetY = 0f 7 //手指落下时的位置 8 var downX = 0f 9 var downY = 0f 10 //初始位置 11 var originalOffSetX = 0f 12 var originalOffSetY = 0f 13 //记录新加入的手指的id 14 var newPointId = 0 15 16 override fun onDraw(canvas: Canvas) { 17 super.onDraw(canvas) 18 canvas.drawBitmap(bitmap, offSetX, offSetY, paint) 19 } 20 21 override fun onTouchEvent(event: MotionEvent): Boolean { 22 //多点触控这里必须用event.actionMasked而不能用event.action, 否则可能导致行为偏差 23 when(event.actionMasked){ 24 MotionEvent.ACTION_DOWN -> { 25 downX = event.x 26 downY = event.y 27 //每次有新的action_down事件时, 把上次的offset赋值给originalOffSet 28 originalOffSetX = offSetX 29 originalOffSetY = offSetY 30 //action_down只在第一根手指加入时调用, 所以可以直接用index 0来获取id 31 newPointId = event.getPointerId(0) 32 } 33 MotionEvent.ACTION_MOVE -> { 34 //这里涉及到一个问题, 就是如何获取到有控制权的那根手指 35 //我们知道每个MotionEvent它包含4个信息【x, y, index, id】 36 //event.x其实就等于event.getX(index=0) 37 //随着多个手指的加入和抬起, 这个index是不断变化的 38 //因此我们只能在每次有手指加入时,记录下id, 然后通过id来找 39 //event.findPointerIndex(newPointId)通过id来查找index 40 offSetX = originalOffSetX + event.getX(event.findPointerIndex(newPointId)) - downX 41 offSetY = originalOffSetY + event.getY(event.findPointerIndex(newPointId)) - downY 42 invalidate() 43 } 44 MotionEvent.ACTION_POINTER_DOWN -> { 45 //event.pointerCount可以获取到当前一共有多少个手指 46 newPointId = event.getPointerId(event.actionIndex) 47 //新加入的手指获得控制权, 那么就要先记录下新手指触摸到的位置 48 downX = event.getX(event.actionIndex) 49 downY = event.getY(event.actionIndex) 50 //同理,把上次的offset赋值给originalOffSet 51 originalOffSetX = offSetX 52 originalOffSetY = offSetY 53 } 54 MotionEvent.ACTION_POINTER_UP -> { 55 val actionIndex = event.actionIndex 56 val pointId = event.getPointerId(actionIndex) 57 //每当有手指抬起时, 要把控制权交给最后加入的那根手指 58 //只有当抬起的手指当前拥有控制权的时候才需要操作 59 if(pointId == newPointId){ 60 //如果它时最后一根手指 61 val newIndex = if(actionIndex == event.pointerCount -1){ 62 event.pointerCount -2 63 }else { 64 event.pointerCount -1 65 } 66 newPointId = event.findPointerIndex(newIndex) 67 downX = event.getX(newIndex) 68 downY = event.getY(newIndex) 69 originalOffSetX = offSetX 70 originalOffSetY = offSetY 71 } 72 73 } 74 } 75 //这里必须返回true以消费事件 76 return true 77 } 78 }
配合型: 因为要多个手指配合, 所以这种类型的关键在于找到中心点, 每次有新手指加入/抬起的时候重新计算中心点, 移动的时候只参考中心点
获取中心点的方式就是各个手指的坐标加起来除以手指的个数
我们在第一种的基础上稍加改变:
1 //配合型 2 class MultiTouchView2(context: Context, attributeSet: AttributeSet): View(context, attributeSet) { 3 4 private val paint = Paint(Paint.ANTI_ALIAS_FLAG) 5 val bitmap = getBitMap(resources,100f.toPx.toInt()) 6 var offSetX = 0f 7 var offSetY = 0f 8 //手指落下时的位置 9 var downX = 0f 10 var downY = 0f 11 //初始位置 12 var originalOffSetX = 0f 13 var originalOffSetY = 0f 14 15 override fun onDraw(canvas: Canvas) { 16 super.onDraw(canvas) 17 canvas.drawBitmap(bitmap, offSetX, offSetY, paint) 18 } 19 20 override fun onTouchEvent(event: MotionEvent): Boolean { 21 //中心点坐标 22 val focusX: Float 23 val focusY: Float 24 //一共有几个手指 25 var pointerCount = event.pointerCount 26 var sumX = 0f 27 var sumY = 0f 28 //是否有手指抬起 29 var isPointerUp = event.actionMasked == MotionEvent.ACTION_POINTER_UP 30 for (i in 0 until pointerCount){ 31 //如果不是一个手指抬起事件,就去累加 32 if (!(isPointerUp && i==event.actionIndex)){ 33 sumX += event.getX(i) 34 sumY += event.getY(i) 35 } 36 } 37 if (isPointerUp){ 38 pointerCount-- 39 } 40 focusX = sumX/pointerCount 41 focusY = sumY/pointerCount 42 when(event.actionMasked){ 43 //这里我们已经不用关注加入几根手指或者抬起几根手指了 44 //反正每次我们都重新计算focus 45 MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_POINTER_UP-> { 46 downX = focusX 47 downY = focusY 48 originalOffSetX = offSetX 49 originalOffSetY = offSetY 50 } 51 MotionEvent.ACTION_MOVE -> { 52 offSetX = originalOffSetX + focusX - downX 53 offSetY = originalOffSetY + focusY - downY 54 invalidate() 55 } 56 } 57 return true 58 } 59 }
第三种, 各自为战型, 比如画板, 多个手指可以同时去画,并且相互不干扰
实现这种的关键是, 我们要分别记录下每个手指的path,各自实现
比如我们要实现一个画板, 那么我们可以维护一个Map
每当有新手指加入,就在MAP里加入一个path
有手指抬起,就把这个path移除