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代码如下

<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();
        }
    }
}```
点击按钮后

![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代码如下

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

}```
看看结果

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那个部分改一下

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

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

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

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

<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不仅可以更新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());  
                    };  
                };<pre code_snippet_id="445431" snippet_file_name="blog_20140808_19_1943618" name="code" class="java">   ```
Android不仅给我们提供了异步消息处理机制让我们更好的完成UI的更新,其实也为我们提供了异步消息处理机制代码的参考~不仅能够知道原理,最好还可以将此设计用到其他的非Android项目中去~今天先把Handler写道这里

总结

内部类:使用内部类作为事件监听器,可以在当前类中重复使用,另外,由于监听器是外部类的内部类,所以可以自由访问外部类的所有界面组件
外部类,外部类作为事件监听器的情况比较少见,原因两点:
1.事件监听器通常属于特定的UI界面组件,定义成外部类不利于提高程序的内聚性。
2.外部类形式的监听器,不能自由访问UI界面组件所在类的组件,编程不够简洁。 
但是如果某个事件监听器确实需要被多个GUI界面所共享,而且主要是用来完成某种业务逻辑的实现,则可以考虑是用外部类的形式来定义事件监听器类。
匿名内部类:我还是最喜欢是用匿名内部类,因为大多书监听器都是一次性使用的,是用也蛮简单,new 监听器接口 就行了,java语法好点的人相对容易掌握
Activity作为事件监听器:这种做法虽然形式简单,但是有两个缺点 :
1.造成程序的混乱,Activity的主要作用是完成初始化界面的工作,但是此时居然还要包含事件处理方法,可能会引起混乱 。
2.Activity实现监听器接口,那么他给开发者的感觉会比较奇怪
绑定到组件事件属性:这种在界面中绑定组件的方式比较直观
android事件拦截处理机制详解:http://www.jianshu.com/p/f93244fbf667
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,049评论 25 707
  • 前言 你的时间有限,不要浪费于重复别人的生活。不要让别人的观点淹没了你内心的声音。 Android事件处理概述 A...
    olaH阅读 1,904评论 0 2
  • 一.基于监听的事件处理 在事件监听的处理模型中,主要涉及如下三类对象: Event Source(事件源):事件发...
    哇楼主阅读 960评论 0 0
  • 或许是你的声音吸引了我得注意,是你那清脆悦耳的歌声陶醉了我,是你幽默风趣的语言魅力感染了我;也或许是你那漂亮的脸蛋...
    葉榮書記阅读 179评论 0 1
  • 两人一前一后的回到了病房,王丹坐在病床上气呼呼的说:“你悄无声息的站在那里干什么?吓我一跳,饭也撒了,一会儿还要出...
    清风禅阅读 140评论 0 0