android 触摸按压,Android笔记:触摸事件的分析与总结----MotionEvent对象

其他相关博文:

一、MotionEvent对象

当用户触摸屏幕时,将创建一个MontionEvent对象。MotionEvent包含了关于发生触摸的位置和时间的信息,以及触摸事件的其他细节。

获取MontionEvent对象的方法有:

1.重载Activity中的onTouchEvent(MotionEvent event)方法;

2.View对象调用View.setOnTouchListener接口实现onTouch(View v, MotionEvent event)方法;

获得MontionEvent对象后,可以通过以下常用方法进一步获取触控事件的具体信息:event.getAction() //获取触控动作比如ACTION_DOWN

event.getPointerCount(); //获取触控点的数量,比如2则可能是两个手指同时按压屏幕

event.getPointerId(nID); //对于每个触控的点的细节,我们可以通过一个循环执行getPointerId方法获取索引

event.getX(nID); //获取第nID个触控点的x位置

event.getY(nID); //获取第nID个点触控的y位置

event.getPressure(nID); //LCD可以感应出用户的手指压力,当然具体的级别由驱动和物理硬件决定的

event.getDownTime() //按下开始时间

event.getEventTime() // 事件结束时间

event.getEventTime()-event.getDownTime()); //总共按下时花费时间

触控对象中的主要相关常量:/**

* 用于多点触控进行操作

*/

public static final int ACTION_MASK = 0xff;

/**

* 手指按下时

*/

public static final int ACTION_DOWN = 0;

/**

* 手指放开时

*/

public static final int ACTION_UP = 1;

/**

* 移动操作时

*/

public static final int ACTION_MOVE = 2;

/**

* 用户无规则的操作时可能触发. 此操作用于表明,一个触摸序列在未发生任何实际操作的情况下结束.

*/

public static final int ACTION_CANCEL = 3;

/**

* 触摸操作发生在窗口之外,但仍然能够找到该操作的特殊情况下设置.

*/

public static final int ACTION_OUTSIDE = 4;

/**

*

*/

public static final int ACTION_POINTER_DOWN = 5;

/**

*

*/

public static final int ACTION_POINTER_UP = 6;

/**

*

*/

public static final int ACTION_HOVER_MOVE = 7;

/**

* Android3.1开始引入的常量,来自于输入设备(如鼠标),而非触摸屏.

*/

public static final int ACTION_SCROLL = 8;

二、MotionEvent对象处理过程

范例视图如下:

20d79f05c1a26a41fb943cdbac11eecd.png

activity_main.xml代码如下:

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".MainActivity" >

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_alignParentLeft="true"

android:layout_alignParentTop="true"

android:orientation="vertical" >

android:id="@+id/layout1"

android:layout_width="match_parent"

android:layout_height="0dp"

android:layout_weight="1"

android:orientation="vertical"

android:tag="true_Layout" >

android:id="@+id/trueButton1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:tag="true_Btn_上"

android:text="true_Btn_上" />

android:id="@+id/falseButton1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:tag="false_Btn_上"

android:text="false_Btn_上" />

android:id="@+id/layout2"

android:layout_width="match_parent"

android:layout_height="0dp"

android:layout_weight="1"

android:background="#ff00ff"

android:orientation="vertical"

android:tag="false_Layout" >

android:id="@+id/trueButton2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:tag="true_Btn_下"

android:text="true_Btn_下" />

android:id="@+id/falseButton2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:tag="false_Btn_下"

android:text="false_Btn_下" />

自定义Button类代码如下:package com.example.d_motionevent;

import android.content.Context;

import android.util.AttributeSet;

import android.util.Log;

import android.view.MotionEvent;

import android.widget.Button;

/**

*

* @author zeng

*/

public class BooleanButton extends Button

{

public BooleanButton(Context context, AttributeSet attrs)

{

super(context, attrs);

}

protected boolean myValue()

{

return false;

}

@Override

public boolean onTouchEvent(MotionEvent event)

{

String myTag = this.getTag().toString();

Log.e(myTag, "===========" + "Button 的 onTouchEvent 方法" + "===========");

Log.e(myTag, MainActivity.describeEvent(this, event));

Log.e(myTag, "父类 onTouchEvent() = " + super.onTouchEvent(event));

Log.e(myTag, "该button touch = " + myValue());

return myValue();

}

}

TrueButton类代码:package com.example.d_motionevent;

import android.content.Context;

import android.util.AttributeSet;

/**

* onTouchEvent返回true的Button.

* @author zeng

*/

public class TrueButton extends BooleanButton

{

public TrueButton(Context context, AttributeSet attrs)

{

super(context, attrs);

}

@Override

protected boolean myValue()

{

return true;

}

}

FalseButton类代码:package com.example.d_motionevent;

import android.content.Context;

import android.util.AttributeSet;

/**

* onTouchEvent返回false的Button.

* @author zeng

*/

public class FalseButton extends BooleanButton

{

public FalseButton(Context context, AttributeSet attrs)

{

super(context, attrs);

}

}

MainActivity.class代码如下:package com.example.d_motionevent;

import android.os.Bundle;

import android.app.Activity;

import android.util.Log;

import android.view.MotionEvent;

import android.view.View;

import android.view.View.OnTouchListener;

import android.widget.Button;

/**

* 地址:http://glblong.blog.51cto.com/3058613/1557683

* @author zeng

*/

public class MainActivity extends Activity implements OnTouchListener

{

@Override

protected void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

View layout1 = findViewById(R.id.layout1);

layout1.setOnTouchListener(this);

Button trueButton1 = (Button) findViewById(R.id.trueButton1);

trueButton1.setOnTouchListener(this);

Button falseButton1 = (Button) findViewById(R.id.falseButton1);

falseButton1.setOnTouchListener(this);

View layout2 = findViewById(R.id.layout2);

layout2.setOnTouchListener(this);

Button trueButton2 = (Button) findViewById(R.id.trueButton2);

trueButton2.setOnTouchListener(this);

Button falseButton2 = (Button) findViewById(R.id.falseButton2);

falseButton2.setOnTouchListener(this);

}

@Override

public boolean onTouch(View v, MotionEvent event)

{

String myTag = v.getTag().toString();

Log.e(myTag, "===========" + "Activity 的 onTouch 方法" + "===========");

Log.e(myTag, "被点击的View = " + myTag);

Log.e(myTag, describeEvent(v, event));

if ("true".equals(myTag.substring(0, 4)))

{

Log.e(myTag, " == true");

return true;

}

else

{

Log.e(myTag, " == false");

return false;

}

}

@Override

public boolean onTouchEvent(MotionEvent event)

{

return super.onTouchEvent(event);

}

protected static String describeEvent(View view, MotionEvent event)

{

StringBuilder sb = new StringBuilder(300);

sb.append("Action: ").append(event.getAction()).append("\n");// 获取触控动作比如ACTION_DOWN

sb.append("相对坐标: ").append(event.getX()).append("  *  ").append(event.getY()).append("   ");

sb.append("绝对坐标: ").append(event.getRawX()).append("  *  ").append(event.getRawY()).append("\n");

if (event.getX()  view.getWidth() || event.getY()  view.getHeight())

{

sb.append("未点击在View范围内");

}

sb.append("Edge flags: ").append(event.getEdgeFlags()).append("  ");// 边缘标记,但是看设备情况,很可能始终返回0

sb.append("Pressure: ").append(event.getPressure()).append("  ");// 压力值,0-1之间,看情况,很可能始终返回1

sb.append("Size: ").append(event.getSize()).append("\n");// 指压范围

sb.append("Down time: ").append(event.getDownTime()).append("ms   ");

sb.append("Event time: ").append(event.getEventTime()).append("ms   ");

sb.append("Elapsed: ").append(event.getEventTime() - event.getDownTime()).append("ms\n");

return sb.toString();

}

}

点击过程分析:

1.点击上部的【true_Btn_上】,运行日志如下:

4a40f14f524f11c3d79ba2bfec1c8cfd.png

此时执行的是Activity上的onTouch方法并返回了True。

onTouch()方法返回true,因为编码TrueButton的目的是为返回true。返回true会告诉Android,MotionEvent对象已经被使用,不能将它提供给其他方法。

它还告诉Android,继续将此触摸序列的触摸事件发送到此方法。这就是为什么我们在ACTION_DOWN事件后还会看到ACTION_UP或ACTION_MOVE等其他事件。

当触摸【true_Btn_上】时,按钮并没有高亮颜色变化。这是因为,onTouch()是在调用任何按钮方法之前调用的,而且onTouch()方法返回了true,所以Android就不会再调用【true_Btn_上】按钮的onTouchEvent()方法了。

如果在onTouch()方法中,在返回true的行之前添加一行"v.onTouchEvent(event);",那么将会看到触摸时按钮会有颜色变化了。

2.点击上部的【false_Btn_上】,运行日志如下:

5796bbeea9baad690c10217d062676eb.png

点击后,false_btn按钮会处于常亮状态。效果如图:

c8ccabc35987667e64289803e7723c1a.png

Android接收到MotionEvent对象中的ACTION_DOWN事件,将它传递给MainActivity类中的onTouch()方法。onTouch()执行后返回false。

这个过程告诉Android,onTouch()方法未使用该事件,所以Android寻找要调用的下一个方法,也就是范例中的FalseButton类中重写的onTouchEvent()方法。参见BooleanButton.java中的onTouchEvent()方法,先执行父类的onTouchEvent()方法,然后再返回了false。此时打印出来的Logcat日志与之前的完全一样,因为我们仍然在同一个View对象FalseButton中。

根据日志可以看到,父类希望从onTouchEvent()返回true,但是FalseButton的该方法返回了false。通过返回false,再次告诉Android我们未使用此事件,所以Android不会将ACTION_UP事件发送到我们的按钮,以至于该按钮不知道手指是否已离开了触摸屏。这也是为什么FalseButton在被按下时一直停留在被按下的颜色状态。

简而言之,每次从收到MotionEvent对象的UI对象返回false时,Android就会停止将MotionEvent对象继续发送到该UI对象,同时还会不断的查找另一个UI对象来使用MotionEvent对象。

接着看日志,Android两次尝试找到ACTION_DOWN事件的使用者但都失败了,现在它前进道应用程序中下一个可能接收该事件的View,也就是按钮底层的布局true_Layout。

此时,Android再次调用了MainActivity类中的onTouch()方法,但是现在接收事件的对象变成了true_Layout。接收到的MotionEvent对象的所有信息也与之前相同,只有Y坐标除外,因为点击位置的Y坐标相对于布局左上角要比相对于按钮的左上角的距离来得大。因为true_Layout的onTouch()方法返回true,所以Android将触摸事件的剩余信息发送到了布局。

3.点击上部的【true_Btn_上】按钮不放,同时触摸其他区域。

触摸【true_Btn_上】按钮,在离开按钮之前,其他手指在屏幕上其他区域移动。此时,Logcat将显示接收触摸事件的对象都是【true_Btn_上】按钮。甚至手指离开【true_Btn_上】按钮,而另一手指仍然在屏幕上移动时,接收到触摸事件的仍然还是【true_Btn_上】按钮。

当从onTouch()返回true时,触摸事件序列将与【true_Btn_上】按钮相关联。这告诉了Android,它可以停止查找用来接收MotionEvent对象的UI对象了,只需将此触摸序列的所有未来MotionEvent对象发送给我们。即使在拖动手指时遇到了另一个视图,我们仍然会绑定到此序列的原始视图。

4.点击下部的【false_Btn_下】按钮,运行日志如下:

067ca32d260d094e4f506073067f9aad.png

原理与前几点相同。此时若按住按钮的手指离开前,其他手指继续触摸屏幕,则将不再继续输出日志,因为Android找不到接收MotionEvent对象并返回true结果的UI对象,直到开始下一个新触摸序列。

范例源码见附件。地址:http://glblong.blog.51cto.com/3058613/1557683

三、MotionEvent回收

MotionEvent类中有个recycle()方法,但是不要通过此方法对MotionEvent对象进行回收。如果回调方法没有使用MotionEvent对象,并且返回了false,MotionEvent对象可能会被传递到其他某个方法、视图或我们的活动。

MotionEvent类中还有个obtain()方法,通过此方法可以创建一个MotionEvent的副本或者全新的MotionEvent,这个副本或全新的事件对象是在完成之后应该回收的对象。

四、小结

1.onTouch()方法与View类中的onTouchEvent()方法的区别

onTouch()是View.setOnTouchListener后调用的方法,如果一个View对象setOnTouchListener后,同时又重写了View类自身的onTouchEvent()方法,那么当屏幕触摸时会先调用哪个方法呢?

每当事件产生时,都要先经由dispatchTouchEvent方法来分发。看下View类中的dispatchTouchEvent()源码,如下:/**

* Pass the touch screen motion event down to the target view, or this view

* if it is the target.

*

* @param event

*            The motion event to be dispatched.

* @return True if the event was handled by the view, false otherwise.

*/

public boolean dispatchTouchEvent(MotionEvent event)

{

// 用于测试目,直接忽略

if (mInputEventConsistencyVerifier != null)

{

mInputEventConsistencyVerifier.onTouchEvent(event, 0);

}

// 上面代码的第2个标注。 未被其他窗口遮盖

if (onFilterTouchEventForSecurity(event))

{

// noinspection SimplifiableIfStatement

if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event))

{

// 如果有监听器,执行监听器的,不在执行当前视图的onTouchEvent方法

return true;

}

// 执行当前视图的onTouchEvent方法

if (onTouchEvent(event))

{

return true;

}

}

// 用于测试目,直接忽略

if (mInputEventConsistencyVerifier != null)

{

mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);

}

return false;

}

dispatchTouchEvent()方法里先是判断了是否有监听器,然后才会去判断onTouchEvent(event)。因此会先执行onTouch()方法,如果监听器的onTouch()返回false,则继续执行View的onTouchEvent()。

2.触摸事件的传递过程

1).触摸事件最先被最顶层的UI对象(比如范例中的Btn)接收,如果该View对象接收后返回false,则继续传向其上一层UI对象(比如范例中的layout),依此类推,直到最底层的Activity。

2).同一个View对象,接收到触摸事件时,如果有设置监听器,先执行View.setOnTouchListener里的onTouch(),然后执行View类中的onTouchEvent(event)方法。

3).当一个UI对象接收到触摸事件并返回true时,同时多指触摸的其他事件序列都会被该UI对象接收,直至下一个新的触摸事件序列产生。

4).当各层的UI对象接收触摸事件都返回false时,此刻同时多指触摸,由此产生的其他事件序列将找不到接收的UI对象。因为当前原始的接收UI对象返回false,系统会一直查找下一个可能接收事件并返回true的UI对象但却又无法找到。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值