重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
1.基于监听的事件处理机制,有一个关键就是事件注册。 但是我们在实践的时候并没有自己手动的为某个视图控件注册监听器。
创新互联建站-专业网站定制、快速模板网站建设、高性价比西岗网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式西岗网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖西岗地区。费用合理售后完善,十余年实体公司更值得信赖。
解答: 我们会经常用到 诸如 setOnclickListener(),OnTouchListener()方法等。 从字面意义理解,它为设置...监听器。 但是,它 跟注册还是颇有一些区别的。 我想注册实践监听器,就是将它挂在在一个线程上,也就是说有一个事件监听线程,那么,有事件的视图,就至少是双线程的程序了。 不过很可惜,在去看set..Listener的源码的时候,是看不到它在java源码方面的具体实现的。 也就是说,要么它依赖操作系统实现,要么它依赖jni实现,并且,事件线程由jni管理。 换言之,实现注册监听是由ni实现的。
2.事件源的触发流程:
解答: 学习过操作系统朋友应该知道,操作系统的很多操作都是通过中断来完成。 同理,比如一个点击事件,android手机硬件中,包括了一个触摸屏的硬件,它分为内屏和外屏。 其中负责触发屏幕点击和触摸中断的为内屏。 内屏大概由五个层次构成,具体有什么用不知道,反正我拆过~~~ 从内屏上,当有电容屏感应的时候,会接收到你触摸的位置信息,甚至触摸力度!!! 这个消息经由系统中断(具有最高优先级,应该是由最高优先级的进程通知)发送给cpu,经由cpu通过进程间的消息机制传递给这个进程(当前正在用户界面运行的进程,这时候只有一个),也就是这个程序运行的内存空间的某个点。(或者说通过广播机制,将这个事件发送给所有的app也是有可能的)。
UI编程通常都会伴随事件处理,Android也不例外,它提供了两种方式的事件处理:基于回调的事件处理和基于监听器的事件处理。
对于基于监听器的事件处理而言,主要就是为Android界面组件绑定特定的事件监听器;对于基于回调的事件处理而言,主要做法是重写Android组件特定的回调函数,Android大部分界面组件都提供了事件响应的回调函数,我们主要重写它们就行。
一 基于监听器的事件处理
相比于基于回调的事件处理,这是更具“面向对象”性质的事件处理方式。在监听器模型中,主要涉及三类对象:
1)事件源Event Source:产生事件的来源,通常是各种组件,如按钮,窗口等。
2)事件Event:事件封装了界面组件上发生的特定事件的具体信息,如果监听器需要获取界面组件上所发生事件的相关信息,一般通过事件Event对象来传递。
3)事件监听器Event Listener:负责监听事件源发生的事件,并对不同的事件做相应的处理。
基于监听器的事件处理机制是一种委派式Delegation的事件处理方式,事件源将整个事件委托给事件监听器,由监听器对事件进行响应处理。这种处理方式将事件源和事件监听器分离,有利于提供程序的可维护性。
举例:
View类中的OnLongClickListener监听器定义如下:(不需要传递事件)
[java] view plaincopyprint?
public interface OnLongClickListener {
boolean onLongClick(View v);
}
public interface OnLongClickListener {
boolean onLongClick(View v);
}
View类中的OnLongClickListener监听器定义如下:(需要传递事件MotionEvent)
[java] view plaincopyprint?
public interface OnTouchListener {
boolean onTouch(View v, MotionEvent event);
}
public interface OnTouchListener {
boolean onTouch(View v, MotionEvent event);
}
二 基于回调的事件处理
相比基于监听器的事件处理模型,基于回调的事件处理模型要简单些,该模型中,事件源和事件监听器是合一的,也就是说没有独立的事件监听器存在。当用户在GUI组件上触发某事件时,由该组件自身特定的函数负责处理该事件。通常通过重写Override组件类的事件处理函数实现事件的处理。
举例:
View类实现了KeyEvent.Callback接口中的一系列回调函数,因此,基于回调的事件处理机制通过自定义View来实现,自定义View时重写这些事件处理方法即可。
[java] view plaincopyprint?
public interface Callback {
// 几乎所有基于回调的事件处理函数都会返回一个boolean类型值,该返回值用于
// 标识该处理函数是否能完全处理该事件
// 返回true,表明该函数已完全处理该事件,该事件不会传播出去
// 返回false,表明该函数未完全处理该事件,该事件会传播出去
boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyLongPress(int keyCode, KeyEvent event);
boolean onKeyUp(int keyCode, KeyEvent event);
boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
}
public interface Callback {
// 几乎所有基于回调的事件处理函数都会返回一个boolean类型值,该返回值用于
// 标识该处理函数是否能完全处理该事件
// 返回true,表明该函数已完全处理该事件,该事件不会传播出去
// 返回false,表明该函数未完全处理该事件,该事件会传播出去
boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyLongPress(int keyCode, KeyEvent event);
boolean onKeyUp(int keyCode, KeyEvent event);
boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
}
三 比对
基于监听器的事件模型符合单一职责原则,事件源和事件监听器分开实现;
Android的事件处理机制保证基于监听器的事件处理会优先于基于回调的事件处理被触发;
某些特定情况下,基于回调的事件处理机制会更好的提高程序的内聚性。
四 基于自定义监听器的事件处理流程
在实际项目开发中,我们经常需要自定义监听器来实现自定义业务流程的处理,而且一般都不是基于GUI界面作为事件源的。这里以常见的app自动更新为例进行说明,在自动更新过程中,会存在两个状态:下载中和下载完成,而我们的程序需要在这两个状态做不同的事情,“下载中”需要在UI界面上实时显示软件包下载的进度,“下载完成”后,取消进度条的显示。这里进行一个模拟,重点在说明自定义监听器的事件处理流程。
4.1)定义事件监听器如下:
Android 事件机制包含系统启动流程、输入管理(InputManager)、系统服务和 UI 的通信(WindowManagerService + ViewRootImpl + Window)、事件分发等一系列的环节。
Android 系统中将输入事件定义为 InputEvent,根据输入事件的类型又分为了 KeyEvent(键盘事件) 和 MotionEvent(屏幕触摸事件)。这些事件统一由系统输入管理器 InputManager 进行分发。
在系统启动的时候,SystemServer 会启动 WindowManagerService,WMS 在启动的时候通过 InputManager 来负责监控键盘消息。
InputManager 负责从硬件接收输入事件,并将事件通过 ViewRootImpl 分发给当前激活的窗口处理,进而分发给 View。
Window 和 InputManagerService 之间通过 InputChannel 来通信,底层通过 socket 进行通信。
Android Touch 事件的基础知识:
KeyEvent 对应了键盘的输入事件;MotionEvent 就是手势事件,鼠标、笔、手指、轨迹球等相关输入设备的事件都属于 MotionEvent。
InputEvent 统一由 InputManager 进行分发,负责与硬件通信并接收输入事件。
system_server 进程启动时会创建 InputManagerService 服务。
system_server 进程启动时同时会启动 WMS,WMS 在启动的时候就会通过 IMS 启动 InputManager 来监控键盘消息。
App 端与服务端建立了双向通信之后,InputManager 就能够将产生的输入事件从底层硬件分发过来,Android 提供了 InputEventReceiver 类,以接收分发这些消息:
Window 和 IMS 之间通过 InputChannel 通信。InputChannel 是一个 pipe,底层通过 socket 进行通信。在 ViewRootImpl.setView() 过程中注册 InputChannel。
Android 事件传递机制是 先分发再处理 ,先由外部的 View 接收,然后依次传递给其内层的 View,再从最内层 View 反向依次向外层传递。
三个方法的关系如下:
分发事件:
应用了树的 深度优先搜索算法 (Depth-First-Search,简称 DFS 算法),每个 ViewGroup 都持有一个 mFirstTouchTarget, 当接收到 ACTION_DOWN 时,通过递归遍历找到 View 树中真正对事件进行消费的 Child,并保存在 mFirstTouchTarget 属性中,依此类推组成一个完整的分发链。在这之后,当接收到同一事件序列的其它事件如 ACTION_MOVE、ACTION_UP 时,则会跳过递归流程,将事件直接分发给下一级的 Child。
ViewGroup 分发事件的主要的任务是找一个 Target,并且用这个 Target 处理事件,主要逻辑如下 :
为什么倒序查找 TouchTarget?
如果按添加顺序遍历,当 View 重叠时(FrameLayout),先添加的 View 总是能消费事件,而后添加的 View 不可能获取到事件。
拦截事件:
[1] Android 事件分发机制的设计与实现
[2] Android 事件拦截机制的设计与实现
第一种点击事件
在xml中设置onclick属性
android:onClick="myOnclick"
第二种;获取Button然后一个一个单独绑定点击事件
"
xmlns:tools=" "
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/btn_imgBtn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="myOnclick"
android:text="imageButton"
/
android:id="@+id/btn_imgView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="myOnclick"
android:text="imageView"
/
public class MainActivity extends ActionBarActivity {
private Button btnImageBtn;
private Button btnImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnImageBtn = (Button) findViewById(R.id.btn_imgBtn);
btnImageView = (Button) findViewById(R.id.btn_imgView);
btnImageBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "点击ImageButton", Toast.LENGTH_SHORT).show();
}
});
btnImageView.setOnClickListener(new MyListener());
}
第三种:写一个类(MyListener)实现OnClickListener接口,然后Button在设置onclickListener的时候new一个MyListener
btnImageView.setOnClickListener(new MyListener());
class MyListener implements OnClickListener{
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_imgBtn:
Toast.makeText(MainActivity.this, "点击ImageButton", Toast.LENGTH_SHORT).show();
break;
case R.id.btn_imgView:
Toast.makeText(MainActivity.this, "点击imageView", Toast.LENGTH_SHORT).show();
break;
}
第四种:整个类(MianActivity)实现onclickListener的接口
跳转界面
Intent:意图,用于访问android中的组件
用Intent跳转界面(activity)
第一步:new一个Intent()
Intent intent1 = new Intent(MainActivity.this,ImageButtonActivity.class);
startActivity(intent1);
public void myOnclick(View view){
switch (view.getId()) {
case R.id.btn_imgBtn:
Intent intent1 = new Intent(MainActivity.this,ImageButtonActivity.class);
startActivity(intent1);
break;
case R.id.btn_imgView:
Intent intent2 = new Intent(MainActivity.this,ImageViewActivity.class);
startActivity(intent2);
break;
Intent intent = new Intent(当前的activity,跳转到的acticvity.class);
startActivity(intent);
3.ImageView
展示方式:scaleType:
4.ImageButton:
触摸事件:当控件或者屏幕呗触摸的时候,产生的反应
public boolean onTouchEvent(MotionEvent event) {
}
imageButton:现在已经呗button代替,用于展示图片的按钮。不能显示文字。
imageView
scaleType:图片展示的方式
fitStart:展示在控件的上方
fitCenter:展示在控件的中间
fitEnd;展示在控件的下方
fitXY:不按照比例拉伸
matrix:矩阵模式
matrix可以设置图片旋转,缩放。移动
获取图片的高度和宽度
int h = imgView.getDrawable().getIntrinsicHeight();
int w = imgView.getDrawable().getIntrinsicWidth();
Matrix m = new Matrix();
m.postRotate(45);
m.postRotate(45, w/2, h/2);
imgView.setImageMatrix(m);
移动事件:
按下:MotionEvent.ACTION_DOWN
抬起:MotionEvent.ACTION_UP
移动:MotionEvent.ACTION_MOVE
获取当前的移动事件,
event.getAction()
"
xmlns:tools=" "
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/img_01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/ss"
android:scaleType="fitXY"/
android:id="@+id/img_02"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/gl"
android:visibility="gone"
android:scaleType="fitXY"/
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/bird"
android:layout_gravity="center"
/
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="#ff0000"
android:gravity="center"
android:text="小鸟飞"/
public class MainActivity extends Activity {
private ImageView img01;
private ImageView img02;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img01 = (ImageView) findViewById(R.id.img_01);
img02 = (ImageView) findViewById(R.id.img_02);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//System.out.println("被摸了");
if(event.getAction()==MotionEvent.ACTION_UP){
Log.v("TAG", "被抬起来");
if(img01.getVisibility()==View.VISIBLE){
img01.setVisibility(View.GONE);
img02.setVisibility(View.VISIBLE);
}else{
img01.setVisibility(View.VISIBLE);
img02.setVisibility(View.GONE);
}
}else if(event.getAction()==MotionEvent.ACTION_DOWN){
Log.v("TAG", "被按下了");
}else if(event.getAction()==MotionEvent.ACTION_MOVE){
Log.v("TAG", "移动了");
}
return super.onTouchEvent(event);
}
触摸事件在Android手机中有多重要不言而喻,用户的每次操作都和它有关。不知道大家有没有见过一些自定义的控件有这样的问题:当单手操作的时候,没有问题,但是当第二根手指头放上去的时候,该View中的内容就会“跳动一下”,如果有这样问题的控件,就是没有处理多点触控。该篇内容主要讲解MotionEvent对象中的多点触控信息,以及RecyclerView对于这种多点触控是如何处理的。MotionEvent又是啥呢?用来记录用户在屏幕中的触摸信息的对象。假如你点了一下屏幕(假如你手没抖,而且速度很快),这个时候就会产生两个MotionEvent对象(UP和DOWM)。
一次完整的触摸事件流(官方叫gesture)至少包括ACTION_DOWN和ACTION_UP两个Event(手指触摸的情况,不考虑使用鼠标的情况,本文后面所描述的所有情况都是手指触摸的情况)。前面提到了Event的ACTION,我们一般有两个方法可以取到ACTION,一个是 getAction() ,还有一个就是 getActionMasked() 。这两个方法有什么区别呢?action中包含了Pointer的Index,而actionMasked中不包含这个Index。那什么又是Pointer呢?这就涉及到该篇内容要重点讨论的多点触控。可以理解成每一个触控点,通俗点讲就是你放在屏幕上的手指头。我们一般都用ActionMasked这个方法来拿这些Action。下面就简单来说明一下这些常见的Event。
第一根手指头触摸到屏幕(之前屏幕上没有手指头),一次事件触摸流的开始,很简单,但是很重要,这里也要简单的提一下,在ViewGroup中也是根据这次事件的坐标来决定该次事件流交给谁来处理,直到这次事件流完成(ACTION_UP)。
就是你的手指头在屏幕上滑动,就会产生这个事件。
最后一根手指头离开屏幕(屏幕上没有手指头了),标志着该次事件流已经完成。
这次事件流被取消了,虽然还没有完成,一般是ViewGroup经过某种条件判断会设置这样的ACTION。
当屏幕上已经有手指头的时候,再按一个手指头下去就会触发这个事件。
当手指头离开屏幕,同时屏幕上还有手指头的时候就会触发这个事件。
如果你的自定义控件处理好了上面的6种ACTION,那么你的控件对触摸的处理就很好了,因为RecyclerView就只是处理了这6种Event。
在一个MotionEvent对象中,包含了你在屏幕上所有的触摸点信息,他默认会有一个类似于active的触摸点,可以通过方法 getActionIndex() 拿到这个触摸点的Index,然后再通过方法 getPointerId() 能拿到这个触摸点的Id,Id通过 findPointerIndex() ,能再拿到这个Index。这里需要注意的是在一次事件流中,同一个触摸点的Index是可能发生改变的,但是Id是不会改变的。在方法 getX() 和 getY() 中都可以传一个Index来拿你想要的触摸点的坐标,不止这两个方法可以传入index,其他的读者自己去研究了。下面我们来讨论下不同情况下active的默认触摸点都是哪些点呢?
这些ACTIONs默认的active触摸点Index都是0,也就是说这些事件如果你的初次点击屏幕的手指头没有离开屏幕,那就一直是这个点,如果这个手指头已经离开屏幕,那这个点就变成了第二个点击屏幕的点,依次类推。
默认active触摸点是新点击到屏幕上的那个点,但是在后续的move中这个默认index又回变成0。这里也可以简单解释下文章开篇中提到的“跳一下”的Bug:因为在第二个手指头点击屏幕的瞬间,active的触摸点为第二个手指头,这个时候默认的坐标也是第二个指头,后续的move事件中默认的active触摸点又会变成第一个手指头,所以会出现跳一下的Bug。
这个Event和ACTION_POINTER_DOWN类似,只是默认的active的Index变成了离开屏幕的那个触摸点。
下面我们来看看RecyclerView中是如何来处理多点触控的。
这里简单说明下:一个手指到滑动就不说了,当屏幕上有新的手指加入时(之前屏幕上已经有手指头了),这个新加入的手指就会接管RecyclerView的滑动。当有手指头离开屏幕时(屏幕上还有其他的手指头),这个时候如果离开的手指头刚好时接管滑动的那个手指头,这个时候就会找index为0或着1的手指头重新接管滑动;如果离开的不是接管滑动的手指头,就不用管。
到这里多点触控相关的内容就没了,如果有错误的地方,欢迎大家指出来。后面可能还会有触摸事件的发送和滚动相关内容。