拖拽和缩放
多点触控的理论学完了之后,这里开始实践。本节主要介绍使用onTouchEvent()
方法处理触控事件。
拖动一个对象
如果你使用的是Android 3.0或者之后的系统,那么你可以使用内置的拖拽实践监听器`View.OnDragListener`。
用touch手势把一个对象从屏幕的一边拽到另一边是很常见的一种用法。以下代码展示了如何拖拽一个屏幕上的图片。但是主要一下内容:
在一个拖动(或者叫做scroll)操作里,app需要知道开始触控点,不管有几根指头放在屏幕上只需要记住第一根指头的点。但是,在拖动图片的过程中,用户有放上了好几个手指,然后把第一个触控的手指拿开了屏幕。如果你的app只记录单个手指的触控操作,那么就需要把第二个手指的触控点作为默认触控点并把图片移动到该位置。
为了防止上面的情况发生,你的app需要区分初始触控点和其他的触控点。要记录初始触控点以外的触控点,就需要用到前文所说的
ACTION_POINTER_DOWN
和ACTION_POINTER_UP
事件。ACTION_POINTER_DOWN
和ACTION_POINTER_UP
可以在onTouchEvent()
回调中取得。在
ACTION_POINTER_UP
发生的时候,下面的示例代码可以获取到index,并确保触控ID指向的触控事件是有效的。如果触控点已经无效,则获取一个有效的,并获取其X和Y坐标。这个坐标是在ACTION_MOVE
事件中获取,图片将被移动到该位置。
以下代码会记录起始触控点的位置,追踪手指移动的新位置,并把图片移动到该位置。并按照上面描述的方法处理可能的异常情况。
注意以下代码用了getActionMasked()
方法。为了保证兼容可以使MotionEventCompat.getActionMasked()
来获取触控的MotionEvent
。
补充说明,为了不让读者态度困惑。所以没有使用Google的示例代码。原来的示例代码并没有突出在Activity中拖动一个View的功能。而是主要在一个View内部如何相应拖动和缩放功能。这和前文所述的主题有一定的不符合,所以笔者做了下面的修改。如有不妥请指出。
override fun onTouchEvent(event: MotionEvent?): Boolean {
var action = MotionEventCompat.getActionMasked(event)
when (action) {
MotionEvent.ACTION_DOWN -> {
val pointerIndex = MotionEventCompat.getActionIndex(event!!)
val x = MotionEventCompat.getX(event!!, pointerIndex)
val y = MotionEventCompat.getY(event!!, pointerIndex)
mLastTouchX = x
mLastTouchY = y
mActivePointerId = MotionEventCompat.getPointerId(event, 0)
}
MotionEvent.ACTION_MOVE -> {
val pointerIndex = MotionEventCompat.findPointerIndex(event!!, mActivePointerId)
val x = MotionEventCompat.getX(event!!, pointerIndex)
val y = MotionEventCompat.getY(event!!, pointerIndex)
val dx = x - mLastTouchX!!
val dy = y - mLastTouchY!!
mPosX = mPosX ?: 0.0f + dx
mPosY = mPosY ?: 0.0f + dy
// mImageView?.x = mPosX!!
// mImageView?.y = mPosY!!
mImageView?.x = x
mImageView?.y = y
Log.d("##DRAG", "Pointer x- $x | y- $y")
Log.d("##DRAG", "View x- ${mImageView?.x} y- ${mImageView?.y}")
mLastTouchX = x
mLastTouchY = y
}
MotionEvent.ACTION_UP -> {
mActivePointerId = INVALID_POINTER_ID
}
MotionEvent.ACTION_CANCEL -> {
mActivePointerId = INVALID_POINTER_ID
}
MotionEvent.ACTION_POINTER_UP -> {
val pointerIndex = MotionEventCompat.getActionIndex(event!!)
val pointerId = MotionEventCompat.getPointerId(event!!, pointerIndex)
if (pointerId == mActivePointerId) {
val newPointerIndex = if (pointerIndex == 0) 1 else 0
mLastTouchX = MotionEventCompat.getX(event!!, newPointerIndex)
mLastTouchY = MotionEventCompat.getY(event!!, newPointerIndex)
mActivePointerId = MotionEventCompat.getPointerId(event!!, newPointerIndex)
}
}
}
return true
}
总结以下上述内容:
一
- 触控ID(pointer Id)可以唯一指定一个触控事件
- index对应的触控事件可能发生改变。但是,只有通过触控的index可以获得触控的坐标。
所以,使用的时候先在ACTION_DOWN里得到触控index,再用触控index获得触控Id存起来。之后每次在ACTION_MOVE事件中,用触控Id得到触控index,再用这个index得到触控的坐标。
在ACTION_UP、ACTION_CANCEL事件里把触控Id设置为空(或者无效)。在ACTION_POINTER_UP里把作废的触控Id置换为有效的触控Id。
二
这个是关于触控的坐标的。在默认的Activity里实现这个拖拽的功能,你会发现这个图片在拖动的一瞬间图片会下移一定的距离。由于图片的大小设定为50dp,下移的距离和这个距离非常接近。在设定为全屏的时候,下移不会再发生。说明触控点的坐标是基于全屏的,但是ImageView
定位的坐标是基于当前的ViewGroup
的。这一点也非常重要,如果你需要保留ActionBar的话,坐标数据需要从全屏转化为当前ImageView
所在的ViewGroup的。
GestureDetector实现拖拽
前面的示例说明了如何用在onTouchEvent()
里实现一个拖动。Android提供了一个更加简单的方法来实现拖拽。下面的代码演示了如何使用GestureDetector.SimpleOnGestureListener
在onScroll()
方法里实现拖动。
当用户在屏幕上拖动ImageView的时候,onScroll()
就会被调用。用户的手指放到屏幕上的时候就会调用onScroll()
。手指离开屏幕的时候,要么手势结束,要不就会触发一个fling手势(如果手指离开屏幕前以一定的速度滑动的话)。
下面是onScroll()
的代码:
class DragDropScrollActivity : Activity() {
private val TAG: String = DragDropScrollActivity::class.java.simpleName
val mPanGesture: GestureDetector by lazy {
GestureDetector(this@DragDropScrollActivity, MySimpleOnGestureListener())
}
var mImageView: ImageView? = null
var mPosX: Float? = null
var mPosY: Float? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_drag_drop_scroll)
mImageView = findViewById(R.id.drag_drop_image_view) as ImageView
mPosX = mImageView?.x
mPosY = mImageView?.y
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
mPanGesture.onTouchEvent(event)
return super.onTouchEvent(event)
}
inner class MySimpleOnGestureListener() : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?): Boolean {
return true
}
override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
mImageView?.x = mPosX!! - distanceX
mImageView?.y = mPosY!! - distanceY
mPosX = mImageView?.x
mPosY = mImageView?.y
Log.d(TAG, "distance X: $distanceX, Y: $distanceY")
Log.d(TAG, "position X: ${mImageView!!.x} Y: ${mImageView!!.y}")
return true
}
}
}
缩放
to be continued...