[Android]视图的控触操作-MotionEvent
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
引入
对屏幕的任何操作系统都会创建一个触摸事件的对象MotionEvent来应对这个操作。当点击手机屏幕的某一个视图时最先感应到的是屏幕因为Activity系统是分层的结构底层是一些驱动所以驱动就会得到信息并且把信息传到被点击的应用应用再交给ActivityActivity通过MotionEvent对象来实现对视图的触控操作那么接下来我们学习一下MotionEvent对象如何实现对视图的触控操作。
一、事件的操作类型
正常情况下一次手指触摸屏幕的行为会引起一系列的点击事件MotionEvent对象存在变量action来反映这一系列点击事件的操作类型所以我们可以通过MotionEvent对象的action变量的值来得到当前的点击状态。
- 手指按下时action的值等于ACTION_DOWN等于0
- 手指在屏幕上移动时action的值等于ACTION_MOVE等于2
- 手指离开屏幕action的值等于ACTION_UP等于1
单点触控一次简单的交互流程有两种情况
- 手指按下马上离开action的值的变化为0->1
- 手指按下在屏幕上移动一段距离后离开屏幕action的值的变化为0->2->...->2->1
二、MotionEvent的传递消耗处理过程
事件分发的三个重要方法
1、dispatchTouchEvent()方法会从Activity开始一层一层地向子View分发事件直到没有子 View。分发的时候只能是View分发给子View不能View分发给孙View
2、onTouchEvent()方法会在dispatchTouchEvent()方法调用到最底层View之后再从最底 层View一层一层地往上回调如果某个View的onTouchEvent方法返回true那么就会停止 向上回调。
3、onInterceptTouchEvent方法伴随着dispatchTouchEvent()方法存在它的作用就是拦截 ViewGroup的事件不让它继续向下分发事件。
ActivityViewViewGroup和MotionEvent的主要方法
Activity
dispatchTouchEvent(ev: MotionEvent?): Boolean | 分发事件 |
onTouchEvent(ev: MotionEvent?): Boolean | 处理事件的回调 |
View
dispatchTouchEvent(ev: MotionEvent?): Boolean | 分发事件 |
onTouchEvent(ev: MotionEvent?): Boolean | 处理事件的回调 |
setOnTouchListener(l:OnTouchListener) | 设置事件监听器 |
setOnClickListener(l:OnClickListener) | 设置点击监听 |
setOnLongClickListener(l:OnClickListener) | 设置长按监听 |
setOnCreateContextMenuListener(l:OnCreateContextMenuListener) | 用于创建菜单 |
注意OnTouchListener中的onTouch()事件优先级高于onTouchEvent()事件如果onTocuh()的返回结果为ture那么该View的onTouchEvent()事件将不会被调用。
ViewGroup
dispatchTouchEvent(ev: MotionEvent?): Boolean | 分发事件 |
onInterceptTouchEvent(ev: MotionEvent?): Boolean | 拦截事件 |
注意onInterceptTouchEvent方法只在ViewGroup中可以重写。
MotionEvent
同时通过MotionEvent对象我们可以得到点击事件的x和y轴坐标。
系统提供的方法如下
getX() | 得到事件发生的x轴坐标相对于当前视图 |
getY() | 得到事件发生的y轴坐标相对于当前视图 |
getRawX() | 得到事件发生的x轴坐标相对于屏幕左顶点 |
getRawY() | 得到事件发生的y轴坐标相对于屏幕左顶点 |
下面我们来理解一下MotionEvent的传递消耗处理过程
如图所示当点击事件产生之后事件首先会传递给当前的ActivtiyActivity会调用分发事件方法dispatchTouchEvent将事情传递给最大的View然后再一层层地向下传递给子View直到传递到最小的view调用最小的view的onTouchEvent方法向上传递直到有一个view的onTouchEvent方法返回true消耗这个点击事件消耗这个点击事件之后就不会向上传递了。如果没有那么事件最终会被activity消耗。
通俗点说就是爷爷Activity得到了一个苹果点击事件爷爷把苹果给了爸爸View爸爸把苹果给了我子View如果我选择不吃苹果那么就把苹果给爸爸如何爸爸如果选择吃了苹果就是把苹果消耗了如果爸爸选择不吃苹果就把苹果给爷爷爷爷只能吃掉苹果结束。
通过代码加深理解MotionEvent的传递消耗处理过程
当View的onTouch和onTouchEvent方法都返回false时
点击一下View产生的点击事件如下
点击View并移动产生的点击事件如下
最终都是Activity消费了点击事件。
当View的onTouch方法返回true时
点击一下View产生的点击事件如下
点击View并移动产生的点击事件如下
最终都是View消费了点击事件并且可以看出OnTouchListener中的onTouch()事件优先级高于onTouchEvent()事件如果onTocuh()的返回结果为ture那么该View的onTouchEvent()事件将不会被调用。
小项目
先看效果图
- 功能描述
通过手指移动来拖动图片
控制图片不能超过屏幕显示区域
- 技术点
MotionEvent处理
对View进行动态定位layout
代码
class MainActivity : AppCompatActivity(),View.OnTouchListener{
var lastX=0
var lastY=0
lateinit var imageView: ImageView
lateinit var parentView:RelativeLayout
var maxRight=0
var maxBottom=0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
imageView=findViewById(R.id.imageView4)
parentView=imageView.parent as RelativeLayout
imageView.setOnTouchListener(this)
}
override fun onTouch(p0: View?, event: MotionEvent): Boolean {
val eventX=event.rawX
val eventY=event.rawY
when(event.action){
MotionEvent.ACTION_DOWN->{
if(maxRight==0){
maxRight=parentView.right
maxBottom=parentView.bottom
}
lastX= eventX.toInt()
lastY=eventY.toInt()}
MotionEvent.ACTION_MOVE->{
var dx:Int=(eventX-lastX).toInt()
var dy:Int=(eventY-lastY).toInt()
var left=imageView.left+dx
var top=imageView.top+dy
var right=imageView.right+dx
var bottom=imageView.bottom+dy
//限制left>=0
if(left<0){
right+=-left
left=0
}
//限制top>=0
if(top<0){
bottom+=-top
top=0
}
//限制right<maxRight
if(right>maxRight){
left-=right-maxRight
right=maxRight
}
//限制bottom>=0
if(bottom>maxBottom){
top-=bottom-maxBottom
bottom=maxBottom
}
imageView.layout(left, top, right, bottom)
lastX=eventX.toInt()
lastY=eventY.toInt()
}
}
return true
}
}