一、前期基础知识储备
1.onTouchEvent()和onTouch()处理的对象是谁?
点击事件(Touch事件)。当用户触摸屏幕时(View 或 ViewGroup派生的控件),将产生点击事件(Touch事件)。常见的点击事件包括:单击、双击、触摸、滑动。
Touch事件的相关细节(发生触摸的位置、时间等)被封装成MotionEvent对象。
所以传入参数之后完整的方法展示:onTouchEvent(MotionEvent event)和onTouch(View v, MotionEventevent)。参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。
2. onTouchEvent()和onTouch()何时使用?
①onTouchEvent(MotionEvent event)是View中定义的方法,而且是Public类型,所以Activity、ViewGroup、View均可以调用这个方法,最常见的关于这块的知识点在Android事件分发中,onTouchEvent()是每个事件处理对象都有的方法,用于根据下层的onTouchEvent()返回值类型判断是否调用,最常见的用法是自定义View时写入到view中,从而让该View获取用户对手机屏幕的各种操作,并对不同类型的操作实现不同的反馈,属于一个宏观的屏幕触摸监控方法;
②onTouch((View v, MotionEvent event)是View.OnTouchListener接口中实现的唯一方法,接收两个参数,第二个参数是之前提过的event事件对象,第一个参数是一个具体的view类型对象,这就意味着onTouch()方法必须和某个控件进行绑定,即某个控件实现了View.OnTouchListener接口,才能调用onTouch()方法。
3. onTouchEvent()和onTouch()方法的返回值类型
均是布尔型的返回值,所以完整的代码语句展示如下:
①public boolean onTouchEvent(MotionEventevent){
return true;
}
该方法的返回值是当已经完整地处理了该事件且不希望其他回调方法再次处理时返回true,否则返回false。即view如果通过onTouchEvent()方法处理了事件,不希望也不必要传到ViewGroup中时,即返回true,如果view处理不了事件,需要往上级传时,返回值为false,同理ViewGroup和Activity之间事件的控制也是如此。
②public boolean onTouch(View v, MotionEventevent) {
return true;
}
该方法的返回值类型主要用于控制是否执行onTouchEvent()方法及onTouchEvent()方法内部内置的各种click点击事件是否执行。
利用此特性可以单独拦截某个View的点击事件,比如
private void initView() {
Button btn_onTouch = findViewById(R.id.test_on_touch_btn);
LinearLayout ll_onTouch = findViewById(R.id.test_on_touch_ll);
// 重写父布局的onTouch()方法
ll_onTouch .setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 拦截底部布局的点击事件 返回值为true 消费事件
return true;
}
});
btn_onTouch.setOnClickListener(this);
ll_onTouch .setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.test_on_touch_btn:
Toast.makeText(this, "点击了按钮", Toast.LENGTH_SHORT).show();
break;
case R.id.test_on_touch_ll:
Toast.makeText(this, "点击了布局", Toast.LENGTH_SHORT).show();
break;
}
}
效果如下:
可以看到,点击按钮红色区域按钮时,会有Toast显示;点击蓝色布局时没有,因为onTouch()方法中已经返回true,拦截了点击事件,处于事件分发末端的onClick()方法不在触发了。
4. onTouchEvent()和onTouch()方法优先级及控制关系
①如果onTouch()方法返回值是true(事件被消费)时,则onTouchEvent()方法将不会被执行;
②只有当onTouch()方法返回值是false(事件未被消费,向下传递)时,onTouchEvent方法才被执行。
由此可见,给View设置监听OnTouchListener时,重写的onTouch()方法,其优先级比onTouchEvent()要高,假如onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。
可以看出,平时我们使用的OnClickListener,其优先级最低,即处于事件传递的尾端。
二、上代码,具体实现
1. onTouchEvent()方法使用——随手势移动view
@Override
public boolean onTouchEvent(MotionEvent event) {
//每次回调onTouchEvent的时候,我们获取触摸点的代码
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
// 在当前left、top、right、bottom的基础上加上偏移量
layout(getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY);
break;
}
return true;
}
自定义一个View,放置在布局文件中,同时在自定义的View中写入onTouchEvent()方法,将每次手势移动产生的坐标设置给view,从而实现view滑动的实时更新。
2.onTouch()方法使用——随手势移动button
类似悬浮窗效果,该button不在xml布局中写入,而是通过Java代码动态的创建该按钮,通过LayoutParams设置其横纵坐标,实现View.OnTouchListener接口,重写onTouch()方法,在里面从触摸事件中不断拿到新坐标设置给button,实现button动态滑动的效果。
使用WindowManger实现,记得申请权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
private Button btnTest2;
private Button btnTest1;
private WindowManager.LayoutParams layoutParams;
private WindowManager windowManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnTest1 = (Button) findViewById(R.id.btn_test);
windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
}
//布局文件中的按钮写入的点击事件onButtonClick,用于控制“滑动按钮的显示”
public void onButtonClick(View view) {
if (view == btnTest1) {
btnTest2 = new Button(this);
btnTest2.setText("滑动按钮");
layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0, 0,//0,0 分别是type和flags参数,在后面分别配置了
PixelFormat.TRANSPARENT);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
//申明以下type时 需要申请权限android.permission.SYSTEM_ALERT_WINDOW
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.x = 100;
layoutParams.y = 300;
windowManager.addView(btnTest2, layoutParams);
btnTest2.setOnTouchListener(this);
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
break;
}
/*依旧是是从event事件中拿到坐标 为了实现动态移动的按钮 必须动态的创建按钮 指定按钮的坐标*/
case MotionEvent.ACTION_MOVE:
layoutParams.x = rawX;
layoutParams.y = rawY;
windowManager.updateViewLayout(btnTest2, layoutParams);
break;
case MotionEvent.ACTION_UP: {
break;
}
}
return false;
}
@Override
protected void onDestroy() {
try {
windowManager.removeView(btnTest2);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
super.onDestroy();
}
}
总结:Android开发中会遇到很多事件监听事件,例如我们常见的普通控件的OnClickListener监听事件、高级控件(ListView、RecyclerView)的OnItemClickListener监听事件、CheckBox的OnCheckedChangeListener、还有SeekBar的OnSeekBarChangeListener等等。
除此之外我们常常看到一些触摸事件,例如侧滑菜单或者ViewPager的实现,那么这些监听是如何实现的呢?
实际上以上所说的触摸事件就是我们常常听到的手势监听,代码中实现手势监听分三种,
一种是重写Activity内部或者自定义view 内部重写onTouchEvent方法来实现全局的手势监听;
另一种是通过某个系统控件实现OnTouchListener接口重写onTouch方法实现某一控件的手势监听;这样做的两个好处在于,不用单纯为了Touch事件去自定义控件,并且在Activity内部控件采用此处理方式,非常适合进行数据的传递;
最后一种是通过Activity全局实现OnTouchListener接口重写onTouch方法,然后在该方法内部用GestureDetector拦截掉事件:
@Override
public boolean onTouch(View v, MotionEvent event) {
return mIndicatorGestureDetector.onTouchEvent(event);
}