android 后台处理事情_Android事件处理机制

与界面编程紧密相关的就是事件处理机制,当用户在程序界面上执行各种操作时,应用程序必须为用户动作提供响应动作,这种响应动作就需要通过事件处理来完成。

Android提供了两种方式的事件处理:基于回调的事件处理和基于监听的事件处理。

一般来说,基于回调的事件可用于处理一些具有通用性的事件,基于回调的事件处理代码会显得比较简洁。但对于某些特定的事件,无法使用基于回调的事件处理,只能采用基于监听的事件处理。

一 基于监听器的事件处理

相比于基于回调的事件处理,这是更具“面向对象”性质的事件处理方式。在监听器模型中,主要涉及三类对象:

1)事件源Event Source:产生事件的来源,通常是各种组件,如按钮,窗口等。

2)事件Event:事件封装了界面组件上发生的特定事件的具体信息,如果监听器需要获取界面组件上所发生事件的相关信息,一般通过事件Event对象来传递。

3)事件监听器Event Listener:负责监听事件源发生的事件,并对不同的事件做相应的处理。

基于监听器的事件处理机制是一种委派式Delegation的事件处理方式,事件源将整个事件委托给事件监听器,由监听器对事件进行响应处理。这种处理方式将事件源和事件监听器分离,有利于提供程序的可维护性。

举例:

View类中的OnLongClickListener监听器定义如下:(不需要传递事件)

public interface OnLongClickListener {

boolean onLongClick(View v);

}```

View类中的OnLongClickListener监听器定义如下:(需要传递事件MotionEvent)

public interface OnTouchListener {

boolean onTouch(View v, MotionEvent event);

}```

二 基于回调的事件处理

相比基于监听器的事件处理模型,基于回调的事件处理模型要简单些,该模型中,事件源和事件监听器是合一的,也就是说没有独立的事件监听器存在。当用户在GUI组件上触发某事件时,由该组件自身特定的函数负责处理该事件。通常通过重写Override组件类的事件处理函数实现事件的处理。

举例:

View类实现了KeyEvent.Callback接口中的一系列回调函数,因此,基于回调的事件处理机制通过自定义View来实现,自定义View时重写这些事件处理方法即可。

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的事件处理机制保证基于监听器的事件处理会优先于基于回调的事件处理被触发;

某些特定情况下,基于回调的事件处理机制会更好的提高程序的内聚性。

四、下面拿例子说一说两种事件处理事件的不同流程:

1.基于监听的事件:监听的处理流程

基于监听的事件处理主要涉及3个对象

Event Source(事件源):事件发生的场所,通常就是组件,每个组件在不同情况下发生的事件不尽相同,而且产生的事件对象也不相同

Event(事件):事件封装了界面组件上发生的特定事件,通常是用户的操作,如果程序需要获得界面组件上发生的相关信息,一般可通过Event对象来获取

Event Listener(事件监听器):负责监听事件源所发生的事件,并对各种事件做出相应的处理

-

那么我们怎么把事件源与事件联系到一起呢?就需要为事件注册监听器了,就相当于把事件和监听器绑定到一起,当事件发生后,系统就会自动通知事件监听器来处理相应的事件.怎么注册监听器呢,很简单,就是实现事件对应的Listener接口。

1)为事件对象添加监听

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-863760fba945ef6e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2)当事件发生时,系统会将事件封装成相应类型的事件对象

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-bbeeac2cd7bf9587.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

3)当监听器对象接收到事件对象之后,系统调用监听器中相应的事件处理来处理事件

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-6320700750450857.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

注意:事件源可以是任何的界面组件,不太需要开发者参与,注册监听器叶只要一行代码就实现了,因此事件编程的重点是实现事件监听器类

android设备可用物理编码按键及案件编码

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-757430ca0e238b1f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

View.OnClickListener:单击事件的事件监听器必须要实现的接口

View.OnCreateContextMenuListener:创建上下文菜单的事件监听器必须要实现的接口

View.OnFocusChangedListener:焦点改变事件的事件监听器必须实现的接口

View.OnKeyListener:按钮事件的事件监听器必须实现的接口

View.OnLongClickListener:长单击事件的事件监听器必须要实现接口

View.OnTouchListener:触摸事件的事件监听器必须要实现的接口

与普通java方法调用不同的是:普通java程序里的方法是由程序主动调用的,而事件处理中的初见处理器方法是由系统负责调用的

程序中实现监听器有以下几种方法

内部类形式

外部类形式

匿名内部类形式

Activity作为事件监听器类形式(activity本身实现监听器接口)

1>.内部类作为事件监听器类

新建一个工程,界面如下 :

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-a9e630a6e844872f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

xml代码如下

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

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textSize="30dp"

android:text="helloworld" />

android:id="@+id/button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:hint="内部类形式"/>

```

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();

}

}

}```

点击按钮后

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-294a385ccbc06d3c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2>.外部类形式作为事件监听器类

布局界面如下

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-5d18320b0c2eb72b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

xml代码如下

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

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textSize="30dp"

android:text="操作数1" />

android:id="@+id/operator1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:hint="请输入一个操作数"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textSize="30dp"

android:text="操作数2" />

android:id="@+id/operator2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:hint="请输入一个操作数"/>

android:paddingTop="20dp"

android:id="@+id/result"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="结果"/>

```

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();

}

}```

看看结果

Paste_Image.png

3>.使用匿名内部类作为事件监听器类

就在上面的基础上直接改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);

}

}```

4>.Activity作为事件监听器

直接改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();

}

}```

结果

Paste_Image.png

5>.绑定到组件事件属性

就是在界面组件中为指定的组件通过属性标签定义监听器类

刚刚那个xml文件把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();

}```

结果

Paste_Image.png

2.基于回调的事件

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,按下返回键和松开返回键

Paste_Image.png

Paste_Image.png

Paste_Image.png

Paste_Image.png

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,不过此处包含进来的时候必须要完整包

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" >

android:tag="cn.aiyuan1996.huidiaoprocess.MyTestBox"

android:id="@+id/myTextBox"

android:layout_width="match_parent"

android:layout_height="match_parent"

>

```

然后就是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;

}

}```

然后运行程序,发现程序崩溃了,很好,对于这个问题我花了四个小时没解决,后来我同学也花了半小时没解决,后来他回宿舍看了一个他以前写的,才发现问题,下面我们先来看看报错信息

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-e997abd9fee68d49.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

下面是报错信息

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: [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出问题了,其实就是构造函数那块出了问题,构造函数要用有两个参数的那个,把上面那个构造函数改成这个就行了

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改好后,我们再运行一遍

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-199c50c3659e7bcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

随便输入一个东西,我们看看打印了什么内容

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-45606c1af7d00bb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

主要是看这个顺序,首先是触发的是该组件的绑定的事件监听器,然后是该组件所在的类提供的事件回调方法,最后才是传播给组件所在Activity类,如果在任何一个事件处理方法返回了true,那么该事件将不会被继续向外传播

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);

}

}```

看看运行截图

Paste_Image.png

Paste_Image.png

Paste_Image.png

再来说一说Handler消息传递机制

出于性能优化考虑,android的ui线程操作是不安全的,这意味者如果多个线程并发操作UI组件,可能导致线程安全问题,为了解决这个问题,android制定了一条硬性的规则,只允许UI线程(主线程)修改android里的UI组件

当一个程序第一次启动时,android会同时启动一条主线程,这线程主要负责与UI相关度事件,例如用户的按键事件,用户的触摸事件,以及屏幕绘图事件,并非相关的时间分发到组件进行处理,所以主线程又叫UI线程,故android平台只允许Ui线程修改activity的ui组件,新的进程需要动态改变界面组件的属性值时,就需要用到Handler了

很多android初学者对android 中的handler不是很明白,其实Google参考了Windows的消息处理机制,

在Android系统中实现了一套类似的消息处理机制。在下面介绍handler机制前,首先得了解以下几个概念:

1. Message

消息,理解为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。

2. Message Queue

消息队列,用来存放通过Handler发布的消息,按照先进先出执行。

3. Handler

Handler是Message的主要处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。

4. Looper

循环器,扮演Message Queue和Handler之间桥梁的角色,循环取出Message Queue里面的Message,并交付给相应的Handler进行处理。

5. 线程

UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。

每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构。在你的应用程序里,可以定义Handler的子类别来接收Looper所送出的消息。```

来张图解:

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-c2e3c6b58d4a5e8f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

Handler 、 Looper 、Message 这三者都与Android异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢?

异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。

说了这一堆,那么和Handler 、 Looper 、Message有啥关系?其实Looper负责的就是创建一个MessageQueue,然后进入一个无限循环体不断从该MessageQueue中读取消息,而消息的创建者就是一个或多个Handler 。

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 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

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

android:layout_width="match_parent"

android:layout_height="match_parent" >

android:layout_width="match_parent"

android:layout_height="match_parent"

android:id="@+id/image"/>

```

然后是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不仅可以更新UI,你完全可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建Handler实例的线程中运行。

new Thread()

{

private Handler handler;

public void run()

{

Looper.prepare();

handler = new Handler()

{

public void handleMessage(android.os.Message msg)

{

Log.e("TAG",Thread.currentThread().getName());

};

};

   ```

Android不仅给我们提供了异步消息处理机制让我们更好的完成UI的更新,其实也为我们提供了异步消息处理机制代码的参考~不仅能够知道原理,最好还可以将此设计用到其他的非Android项目中去~今天先把Handler写道这里

总结

内部类:使用内部类作为事件监听器,可以在当前类中重复使用,另外,由于监听器是外部类的内部类,所以可以自由访问外部类的所有界面组件

外部类,外部类作为事件监听器的情况比较少见,原因两点:

1.事件监听器通常属于特定的UI界面组件,定义成外部类不利于提高程序的内聚性。

2.外部类形式的监听器,不能自由访问UI界面组件所在类的组件,编程不够简洁。

但是如果某个事件监听器确实需要被多个GUI界面所共享,而且主要是用来完成某种业务逻辑的实现,则可以考虑是用外部类的形式来定义事件监听器类。

匿名内部类:我还是最喜欢是用匿名内部类,因为大多书监听器都是一次性使用的,是用也蛮简单,new 监听器接口 就行了,java语法好点的人相对容易掌握

Activity作为事件监听器:这种做法虽然形式简单,但是有两个缺点 :

1.造成程序的混乱,Activity的主要作用是完成初始化界面的工作,但是此时居然还要包含事件处理方法,可能会引起混乱 。

2.Activity实现监听器接口,那么他给开发者的感觉会比较奇怪

绑定到组件事件属性:这种在界面中绑定组件的方式比较直观

android事件拦截处理机制详解:http://www.jianshu.com/p/f93244fbf667

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值