1.前言
在android的进阶之路上,看《android开发艺术探索》实在看不下去了,开始怀疑自己的android基础了,于是找了两本android基础的书把android基础过一遍,还确实发现了好多知识漏洞-基于回调的事件,这个还真以前没用过,然后就想着把android事件处理机制的知识点都整理一遍,嗯,就是这样的。
2.基于监听的事件
基于监听的事件更接近于“面向对象”的事件处理,这种处理方法与java的AWT/Swing的处理方式相同。
2.1监听的处理流程
基于监听的事件处理主要涉及3个对象
- Event Source(事件源):事件发生的场所,通常就是组件,每个组件在不同情况下发生的事件不尽相同,而且产生的事件对象也不相同
- Event(事件):事件封装了界面组件上发生的特定事件,通常是用户的操作,如果程序需要获得界面组件上发生的相关信息,一般可通过Event对象来获取
- Event Listener(事件监听器):负责监听事件源所发生的事件,并对各种事件做出相应的处理
-
那么我们怎么把事件源与事件联系到一起呢?就需要为事件注册监听器了,就相当于把事件和监听器绑定到一起,当事件发生后,系统就会自动通知事件监听器来处理相应的事件.怎么注册监听器呢,很简单,就是实现事件对应的Listener接口。
1).为事件对象添加监听
2).当事件发生时,系统会将事件封装成相应类型的事件对象
3).当监听器对象接收到事件对象之后,系统调用监听器中相应的事件处理来处理事件
注意:事件源可以是任何的界面组件,不太需要开发者参与,注册监听器叶只要一行代码就实现了,因此事件编程的重点是实现事件监听器类
android设备可用物理编码按键及案件编码
• View.OnClickListener:单击事件的事件监听器必须要实现的接口
• View.OnCreateContextMenuListener:创建上下文菜单的事件监听器必须要实现的接口
• View.OnFocusChangedListener:焦点改变事件的事件监听器必须实现的接口
• View.OnKeyListener:按钮事件的事件监听器必须实现的接口
• View.OnLongClickListener:长单击事件的事件监听器必须要实现接口
• View.OnTouchListener:触摸事件的事件监听器必须要实现的接口
与普通java方法调用不同的是:普通java程序里的方法是由程序主动调用的,而事件处理中的初见处理器方法是由系统负责调用的
程序中实现监听器有以下几种方法
- 内部类形式
- 外部类形式
- 匿名内部类形式
- Activity作为事件监听器类形式(activity本身实现监听器接口)
2.2内部类作为事件监听器类
新建一个工程,界面如下
xml代码如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="helloworld" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="内部类形式"/>
</LinearLayout>
MainActivity.java代码如下
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
private Button Button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button = (android.widget.Button) findViewById(R.id.button);
Button.setOnClickListener(new EnterClickListener());
}
class EnterClickListener implements android.view.View.OnClickListener{
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "内部类形式", Toast.LENGTH_SHORT).show();
}
}
}
点击按钮后,
2.3外部类形式作为事件监听器类
布局界面如下
xml代码如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="操作数1" />
<EditText
android:id="@+id/operator1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="请输入一个操作数"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="操作数2" />
<EditText
android:id="@+id/operator2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="请输入一个操作数"/>
<Button
android:paddingTop="20dp"
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="结果"/>
</LinearLayout>
MainActivity.java代码如下
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.view.View.OnClickListener;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
private EditText etNumber1;
private EditText etNumber2;
private Button btnResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
btnResult.setOnClickListener(new Claculator(this,etNumber1,etNumber2));
}
private void findView() {
etNumber1 = (EditText) findViewById(R.id.operator1);
etNumber2 = (EditText) findViewById(R.id.operator2);
btnResult = (Button) findViewById(R.id.result);
}
}
其中,Claculator.java 代码如下
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
public class Claculator implements OnClickListener {
private Activity activity;
private EditText etNumber1;
private EditText etNumber2;
public Claculator(Activity activity,EditText editText,EditText editText2){
this.activity = activity;
this.etNumber1 = editText;
this.etNumber2 = editText2;
}
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(activity, "计算结果为:" + result, Toast.LENGTH_SHORT).show();
}
}
看看结果
2.4使用匿名内部类作为事件监听器类
我们就在上面的基础上直接改MainActivity.java就行了
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
private EditText etNumber1;
private EditText etNumber2;
private Button btnResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
//btnResult.setOnClickListener(new Claculator(this,etNumber1,etNumber2));//是用外部类
//是用匿名内部类
btnResult.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "计算结果为:" + result, Toast.LENGTH_SHORT).show();
}
});
}
private void findView() {
etNumber1 = (EditText) findViewById(R.id.operator1);
etNumber2 = (EditText) findViewById(R.id.operator2);
btnResult = (Button) findViewById(R.id.result);
}
}
2.5Activity作为事件监听器
直接改MainActivity.java就行了
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity implements OnClickListener{
private EditText etNumber1;
private EditText etNumber2;
private Button btnResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
//btnResult.setOnClickListener(new Claculator(this,etNumber1,etNumber2));//是用外部类
//是用匿名内部类
/*btnResult.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "计算结果为:" + result, Toast.LENGTH_SHORT).show();
}
});*/
btnResult.setOnClickListener(this);
}
private void findView() {
etNumber1 = (EditText) findViewById(R.id.operator1);
etNumber2 = (EditText) findViewById(R.id.operator2);
btnResult = (Button) findViewById(R.id.result);
}
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "计算结果为:" + result, Toast.LENGTH_SHORT).show();
}
}
结果
2.6绑定到组件事件属性
就是在界面组件中为指定的组件通过属性标签定义监听器类
刚刚那个xml文件把button那个部分改一下
<Button
android:paddingTop="20dp"
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="结果"
android:onClick="getResult"/>
然后,在MainActivity.java 里面写那个方法就行了,注意,必须是public void getResult(View view)这个格式
public void getResult(View view){
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "计算结果为:" + result, Toast.LENGTH_SHORT).show();
}
结果
3.基于回调的事件
3.1回调机制与监听机制
如果说事件监听机制是一种委托的事件处理,那么回调机制则与之相反,对于基于事件的处理模型来说,事件源与事件监听器是统一的,或者说是事件监听器完全消失了,当用户在UI组件上触发某个事件时,组建自己特定的方法将会负责处理事件
为了使回调方法机制类处理UI组件上发生的事件,开发者需要为该组件提供对应的事件处理方法,而java是一种静态语言,无法为某个对象动态的添加方法,因此只能继续是用UI组件类,并通过重写该类的事件处理的方法来实现
为了处理回调机制的事件处理,android为所有UI组件提供了一些事件处理的回调方法,以view为例
public boolean onKeyDown(int keyCode,
KeyEvent event)
Default implementation of KeyEvent.Callback.onKeyMultiple(): perform press of the view
when KeyEvent.KEYCODE_DPAD_CENTER or KeyEvent.KEYCODE_ENTER
is released, if the view is enabled and clickable.
public boolean onKeyShortcut(int keyCode,
KeyEvent event)
Called when an unhandled key shortcut event occurs.
public boolean onKeyUp(int keyCode,
KeyEvent event)
Default implementation of KeyEvent.Callback.onKeyMultiple(): perform clicking of the view
when KeyEvent.KEYCODE_DPAD_CENTER or
KeyEvent.KEYCODE_ENTER is released.
public boolean onTouchEvent(MotionEvent event)
Implement this method to handle touch screen motion events.
public boolean onTrackballEvent(MotionEvent event)
Implement this method to handle trackball motion events. The
relative movement of the trackball since the last event
can be retrieve with MotionEvent.getX() and
MotionEvent.getY(). These are normalized so
that a movement of 1 corresponds to the user pressing one DPAD key (so
they will often be fractional values, representing the more fine-grained
movement information available from a trackball).
下面以一个小例子来说明一下,新建一个工程,布局文件很简单,就一个textview,MainActivity.java中重写了onKeyDown和onKeyUp方法
代码如下
package cn.aiyuan1996.huidiao;
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
}
public void showText(String string){
text.setText(string);
}
public void showToast(String string){
Toast toast = Toast.makeText(this, string, Toast.LENGTH_LONG);
toast.setGravity(Gravity.TOP, 0, 100);
toast.show();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_0:
showText("你按下了数字键0");
break;
case KeyEvent.KEYCODE_BACK:
showText("你按下了返回键");
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_0:
showToast("你松开了数字键0");
break;
case KeyEvent.KEYCODE_BACK:
showToast("你松开了返回键");
break;
default:
break;
}
text.setText("你没有按下任何键");
return super.onKeyUp(keyCode, event);
}
}
运行截图有四张,按下数字0和松开数字0,按下返回键和松开返回键
3.2基于回调事件的传播流程
几乎所有基于回调的事件都有一个boolean类型的返回值,发方法用于标识该处理方法是否能够完全处理该事件
(1),如果处理事件的回调方法返回的值为true,则表明该处理方法已完全处理该事件,且事件不会被传播出去
(2),如果处理事件的回调方法返回的值为false,则表明该处理方法并未完全处理该事件,且事件会被传播出去
对于基于回调的事件传播而言,某组件上所发生的事件不仅能触发该组件上的回调方法,也会触发该组件所在的activity类的回调方法-只要事件传播到该activity类
下面以一个小例子来说明android系统中的事件传播流程,该程序重写了EditText类的onKeyDown()方法,而且重写了该EditText所在的Activity类的onKeyDown()方法,由于程序中没有阻止事件的传播,所以程序中可以看到事件从RditText传播到Activity的全过程
自定义的组件类MyTestBox.java
package cn.aiyuan1996.huidiaoprocess;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.EditText;
public class MyTestBox extends EditText{
public MyTestBox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Log.i("MyTestBox", "这里是MyTestBox的onKeyDown");
return false;
}
}
上面的MyTextBox类重写了EditText类的onKeyDwon()方法,因此,当用户在此组件上按下任意键时都会触发OnKeyDown()方法,在该方法中返回false,即按键事件会继续向外传递
布局文件挺简单的,就是把上面那个自定义的组件包含进来就ok,不过此处包含进来的时候必须要完整包
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res/cn.aiyuan1996.huidiaoprocess"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<cn.aiyuan1996.huidiaoprocess.MyTestBox
android:tag="cn.aiyuan1996.huidiaoprocess.MyTestBox"
android:id="@+id/myTextBox"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</cn.aiyuan1996.huidiaoprocess.MyTestBox>
</LinearLayout>
然后就是MainActivity了
package cn.aiyuan1996.huidiaoprocess;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnKeyListener;
public class MainActivity extends Activity {
private MyTestBox myTestBox;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myTestBox = (MyTestBox) findViewById(R.id.myTextBox);
myTestBox.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(event.getAction() == KeyEvent.ACTION_DOWN)
Log.i("onCreate", "这里是MyTextBox的OnKeyListener");
return false;
}
});
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Log.i("OnKeyDown", "这里是Activity的onKeyDown");
return false;
}
}
然后运行程序,发现程序崩溃了,很好,对于这个问题我花了四个小时没解决,后来我同学也花了半小时没解决,后来他回宿舍看了一个他以前写的,才发现问题,下面我们先来看看报错信息
下面是报错信息
10-25 15:51:20.378: W/IInputConnectionWrapper(1411): showStatusIcon on inactive InputConnection
10-25 16:50:33.068: I/MainActivity(1463): ------>>>>1
10-25 16:50:33.368: D/AndroidRuntime(1463): Shutting down VM
10-25 16:50:33.378: W/dalvikvm(1463): threadid=1: thread exiting with uncaught exception (group=0x409961f8)
10-25 16:50:33.398: E/AndroidRuntime(1463): FATAL EXCEPTION: main
10-25 16:50:33.398: E/AndroidRuntime(1463): java.lang.RuntimeException: Unable to start activity ComponentInfo{cn.aiyuan1996.huidiaoprocess/cn.aiyuan1996.huidiaoprocess.MainActivity}: android.view.InflateException: Binary XML file line #8: Error inflating class cn.aiyuan1996.huidiaoprocess.MyTestBox
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1955)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1980)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.access$600(ActivityThread.java:122)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1146)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.os.Handler.dispatchMessage(Handler.java:99)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.os.Looper.loop(Looper.java:137)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.main(ActivityThread.java:4340)
10-25 16:50:33.398: E/AndroidRuntime(1463): at java.lang.reflect.Method.invokeNative(Native Method)
10-25 16:50:33.398: E/AndroidRuntime(1463): at java.lang.reflect.Method.invoke(Method.java:511)
10-25 16:50:33.398: E/AndroidRuntime(1463): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
10-25 16:50:33.398: E/AndroidRuntime(1463): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
10-25 16:50:33.398: E/AndroidRuntime(1463): at dalvik.system.NativeStart.main(Native Method)
10-25 16:50:33.398: E/AndroidRuntime(1463): Caused by: android.view.InflateException: Binary XML file line #8: Error inflating class cn.aiyuan1996.huidiaoprocess.MyTestBox
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.createView(LayoutInflater.java:589)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:680)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.rInflate(LayoutInflater.java:739)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
10-25 16:50:33.398: E/AndroidRuntime(1463): at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:251)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.Activity.setContentView(Activity.java:1835)
10-25 16:50:33.398: E/AndroidRuntime(1463): at cn.aiyuan1996.huidiaoprocess.MainActivity.onCreate(MainActivity.java:19)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.Activity.performCreate(Activity.java:4465)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1919)
10-25 16:50:33.398: E/AndroidRuntime(1463): ... 11 more
10-25 16:50:33.398: E/AndroidRuntime(1463): Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet]
10-25 16:50:33.398: E/AndroidRuntime(1463): at java.lang.Class.getConstructorOrMethod(Class.java:460)
10-25 16:50:33.398: E/AndroidRuntime(1463): at java.lang.Class.getConstructor(Class.java:431)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.createView(LayoutInflater.java:561)
10-25 16:50:33.398: E/AndroidRuntime(1463): ... 22 more
10-25 16:50:43.078: I/Process(1463): Sending signal. PID: 1463 SIG: 9
错误中你能一眼看到错误的地方
Unable to start activity ComponentInfo{cn.aiyuan1996.huidiaoprocess/cn.aiyuan1996.huidiaoprocess.MainActivity}: android.view.InflateException: Binary XML file line #8: Error inflating class cn.aiyuan1996.huidiaoprocess.MyTestBox
但是看到这个信息,你大概知道是你的自定义view出问题了,但是你可能还是不知道怎么解决,正如笔者,后来研究了好长时间,也查看了相关blog,但是还是没有解决,还好有同学的帮忙。
其实就是构造函数那块出了问题,构造函数要用有两个参数的那个,把上面那个构造函数改成这个就行了
public MyTestBox(Context context, AttributeSet attrs) {
super(context, attrs);
}
为什么必须要是这个构造函数呢,看看这三个构造函数先
public View(Context context)Simple constructor to use when creating a view from code.
public View(Context context,
AttributeSet attrs)Constructor that is called when inflating a view from XML. This is called when a view is being constructed from an XML file, supplying attributes that were specified in the XML file. This version uses a default style of 0, so the only attribute values applied are those in the Context's Theme and the given AttributeSet.
The method onFinishInflate() will be called after all children have been added.
public View(Context context,
AttributeSet attrs,
int defStyle)Perform inflation from XML and apply a class-specific base style. This constructor of View allows subclasses to use their own base style when they are inflating. For example, a Button class's constructor would call this version of the super class constructor and supply R.attr.buttonStyle for defStyle; this allows the theme's button style to modify all of the base view attributes (in particular its background) as well as the Button class's attributes.
很明显,两个参数的那个构造函数是负责自定义组件的构造的
bug改好后,我们再运行一遍
随便输入一个东西,我们看看打印了什么内容
主要是看这个顺序,首先是触发的是该组件的绑定的事件监听器,然后是该组件所在的类提供的事件回调方法,最后才是传播给组件所在Activity类,如果在任何一个事件处理方法返回了true,那么该事件将不会被继续向外传播
3.3基于回调触摸事件处理
屏幕事件的处理方法onTouchEvent(),该方法的返回值与键盘响应事件相同,都是当程序完整的处理的该事件,且不希望其他回调方法再次处理该事件时返回true,否则返回false
1)屏幕被按下MotionEvent.getAction()==MotionEvent.ACTION_DOWN
2)离开屏幕MotionEvent.getAction()==MotionEvent.ACTION_UP
3)在屏幕中拖动MotionEvent.getAction()==MotionEvent.ACTION_MOVE
下面以一个小例子来说明没有布局文件,直接上MainActivity.java
package cn.aiyuan1996.touchevent;
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.widget.LinearLayout;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
setContentView(layout);
}
public void showToast(String string){
Toast toast = Toast.makeText(this, string, Toast.LENGTH_LONG);
toast.setGravity(Gravity.TOP, 0, 100);
toast.show();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
showToast("触摸屏幕");
break;
case MotionEvent.ACTION_UP:
showToast("离开屏幕");
break;
case MotionEvent.ACTION_MOVE:
showToast("在屏幕中移动");
break;
default:
break;
}
return super.onTouchEvent(event);
}
}
看看运行截图
4.Handler消息传递机制
出于性能优化考虑,android的ui线程操作是不安全的,这意味者如果多个线程并发操作UI组件,可能导致线程安全问题,为了解决这个问题,android制定了一条简单的规则,只允许UI线程修改android里的UI组件
当一个程序第一次启动时,android会同时启动一条主线程,这线程主要负责与UI相关度事件,例如用户的按键事件,用户的触摸事件,以及屏幕绘图事件,并非相关的时间分发到组件进行处理,所以主线程又叫UI线程,故android平台只允许Ui线程修改activity的ui组件,新的进程需要动态改变界面组件的属性值时,就需要用到Handler了
4.1Handler类简介
Handler类主要有两个作用:在新启动的线程中发送消息,在主线程中获取和处理消息
只能通过回调的方法来实现-开发者只需要重写Handler类中处理的消息的方法即可,当新启动的线程发送消息时,消息会发送到与之关联的MessageQueue,而Handler会不断的从MessageQueue中获取并处理消息-这将导致Handler中的处理消息的方法被回调
下面是Handler类中常用的几个方法
public void handleMessage(Message msg)Subclasses must implement this to receive messages.
public final boolean hasMessages(int what)Check if there are any pending posts of messages with code 'what' in the message queue.
public final boolean hasMessages(int what,
Object object)Check if there are any pending posts of messages with code 'what' and whose obj is 'object' in the message queue.
public final Message obtainMessage()Returns a new Message from the global message pool. More efficient than creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this). If you don't want that facility, just call Message.obtain() instead.
public final boolean sendEmptyMessage(int what)Sends a Message containing only the what value.
public final boolean sendEmptyMessageDelayed(int what,
long delayMillis)Sends a Message containing only the what value, to be delivered after the specified amount of time elapses.
public final boolean sendMessage(Message msg)Pushes a message onto the end of the message queue after all pending messages before the current time. It will be received in handleMessage(android.os.Message), in the thread attached to this handler.
下面一个实例演示如何在界面中修改界面的组件,循环播放相册中的照片
布局文件很简单,就一个imageview
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/image"/>
</LinearLayout>
然后是MainActivity.java
package cn.aiyuan1996.handler;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;
public class MainActivity extends Activity {
int[] imgList = new int[]{R.drawable.a,R.drawable.b,R.drawable.c,R.drawable.d,R.drawable.e,R.drawable.f};
int currentIndex = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ImageView imageView = (ImageView) findViewById(R.id.image);
final Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what == 0x123){
//动态修改图片
currentIndex++;
imageView.setImageResource(imgList[currentIndex%imgList.length]);
}
}
};
if(currentIndex <= imgList.length){
new Timer().schedule(new TimerTask() {
@Override
public void run() {
handler.sendEmptyMessage(0x123);
}
}, 0,1200);
}
}
}
上面代码中的Timer类会启动一个新线程,由于不允许在线程中修改UI界面,所以该线程每隔1200毫秒会发送一个消息,该消息会传递到Activity中,再由Handler类进行处理,从而实现了动态切换的效果。
其实Handler还有蛮多要写的,这篇blog篇幅已经不少了,今天先把Handler写道这里,改天有时间后好好研究研究Handler和Looper后,整理一些资料,再写。
总结
- 内部类:使用内部类作为事件监听器,可以在当前类中重复使用,另外,由于监听器是外部类的内部类,所以可以自由访问外部类的所有界面组件
- 外部类,外部类作为事件监听器的情况比较少见,原因两点:1.事件监听器通常属于特定的UI界面组件,定义成外部类不利于提高程序的内聚性2.外部类形式的监听器,不能自由访问UI界面组件所在类的组件,编程不够简洁。
但是如果某个事件监听器确实需要被多个GUI界面所共享,而且主要是用来完成某种业务逻辑的实现,则可以考虑是用外部类的形式来定义事件监听器类。 - 匿名内部类:我还是最喜欢是用匿名内部类,因为大多书监听器都是一次性使用的,是用也蛮简单,new 监听器接口 就行了,java语法好点的人相对容易掌握
- Activity作为事件监听器:这种做法虽然形式简单,但是有两个缺点
1.造成程序的混乱,Activity的主要作用是完成初始化界面的工作,但是此时居然还要包含时间处理方法,可能会引起混乱
2.Activity实现监听器接口,那么他给开发者的感觉会比较奇怪 - 绑定到组件事件属性:这种在界面中绑定组件的方式比较直观
作为一个开发者,我还是最喜欢是用匿名内部类的形式,代码简介,一目了然。