## Android事件處理概述
不管是桌面應用還是手機應用程序, 面對最多的就是用戶,經常需要處理的就是用戶動作一一也就是需要為用戶動作提供響應,這種為用戶動作提供響應的機制就是事件處理。
Android 提供了兩套強大的事件處理機制:
* 基于監聽的事件處理。
* 基于回調的事件處理。
## 基于監聽的事件處理
基于監昕的事件處理是一種更“ 面向對象”的事件處理,這種處理方式與Java 的AWT、
Swing 的處理方式幾乎完全相同。
### 監聽的處理模型
在事件監聽的處理模型中, 主要涉及如下三類對象。
* **Event Source (事件源)**: 事件發生的場所, 通常就是各個組件, 例如按鈕、窗口、菜單等。
* **Event (事件)** : 事件封裝了界面組件上發生的特定事情(通常就是一次用戶操作) 。如果程序需要獲得界面組件上所發生事件的相關信息, 一般通過Event 對象來取得。
* **Event Listener (事件監昕器)** : 負責監聽事件源所發生的事件,并對各種事件做出相應的響應。
>[success]**提示**:有過JavaScript、VisualBasic等變成經驗的讀者都知道,時間響應的動作實際上就是一系列程序語句,通常以方法的形式組織起來。但是Java是面向對象的編程語言,方法不能獨立存在,所以必須以類的形式來組織這些方法,所以事件監聽器的核心就是它所包含的方法——這些方法也被稱為事件處理器(EventHandler)。
Android 的事件處理機制是一種委派式(Delegation )事件處理方式: 普通組件(事件源)將整個事件處理委托給特定的對象(事件監聽器): 當該事件源發生指定的事件時,就通知所委托的事件監聽器,由事件監聽器來處理這個事件。
每個組件均可以針對特定的事件指定一個事件監聽器, 每個事件監聽器也可監聽一個或多個事件源。因為同一個事件源上可能發生多種事件, 委派式事件處理方式可以把事件源上所有可能發生的事件分別授權給不同的事件監聽器來處理:同時也可以讓一類事件都使用同一個事件監聽器來處理。
>[success]提示:委派式事件處理方式明顯抄襲了人類社會的分工協作,例如某個單位發生了火災,該單位通常不會自己處理該事件,而是將該事件委派給消防局(事件監聽器)處理,如果發生了打架斗毆事件,則委派給公安局(事件監聽器)處理,而消防器、公安局也會同時監聽多個單位的火災、打架斗毆事件。這種委派式的處理方式將事件源和事件監聽器分離,從而提供更好的程序模型,有利于提高程序的可維護性。
下圖是事件流程處理示意圖

下面示例講解基于艦艇的事件處理模型
```
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal">
<EditText
android:id="@+id/txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:editable="false"
android:cursorVisible="false"
android:textSize="12pt"/>
<!-- 定義一個按鈕,該按鈕將作為事件源 -->
<Button
android:id="@+id/bn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="單擊我"/>
</LinearLayout>
```
上面的程序定義的按鈕將作為事件源,接下來程序將會為該按鈕綁定一個事件監聽器——監聽器類必須由開發者來實現
```
public class MainActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 獲取應用程序中的bn按鈕
Button bn = (Button) findViewById(R.id.bn);
// 為按鈕綁定事件監聽器
bn.setOnClickListener(new MyClickListener()); // ①
}
// 定義一個單擊事件的監聽器
class MyClickListener implements View.OnClickListener
{
// 實現監聽器類必須實現的方法,該方法將會作為事件處理器
@Override
public void onClick(View v)
{
EditText txt = (EditText) findViewById(R.id.txt);
txt.setText("bn按鈕被單擊了!");
}
}
}
```
上面程序中的粗體字代碼定義了一個View.OnClickListener 實現類, 這個實現類將會作為事件監聽器使用。程序中①號代碼用于為bn 按鈕注冊事件監聽器。當程序中的bn 按鈕被單擊時, 該處理器被觸發, 將看到程序中文本框內變為“ bn 按鈕被單擊了”。
從上面的程序可以看出,基于監聽器的事件處理模型的編程步驟如下:
1. 獲取普通界面組件(事件源) , 也就是被監聽的對象。
2. 實現事件監聽器類,該監聽器類是一個特殊的Java 類,必須實現一個XxxListener 接口。
3. 調用事件源的setXxxListener 方法將事件監聽器對象注冊給普通組件(事件源)。
當事件源上發生指定事件時, Android 會觸發事件監聽器,由事件監聽器調用相應的方法(事件處理器)來處理事件。
把上面的程序與上圖結合起來看,可以發現基于監聽的事件處理有如下規則。
* **事件源**: 就是程序中的bn 按鈕,其實開發者不需要太多的額外處理,應用程序中任何組件都可作為事件源。
* **事件監聽器**: 就是程序中的MyClickListener 類。監聽器類必須由程序員負責實現,實現監聽器類的關鍵就是實現處理器方法。
* **注冊監聽器**:只要調用事件源的setXxxListener(XxxListener)方法即可。
對于上面三件事情,事件源可以是任何界面組件,不太需要開發者參與:注冊監聽器也只要一行代碼即可, 因此事件編程的關鍵就是實現事件監聽器類。
所謂事件監聽器,其實就是實現了特定接口的Java 類的實例。在程序中實現事件監昕器,通常有如下幾種形式。
* **內部類形式**: 將事件監昕器類定義成當前類的內部類。
如上面的代碼示例
* **外部類形式**: 將事件監聽器類定義成一個外部類。
* **Activity 本身作為事件監聽器類**: 讓Activity本身實現監聽器接口,并實現事件處理方法。
* **匿名內部類形式**: 使用匿名內部類創建事件監聽器對象,是目前使用最廣泛的事件監聽器形式。
* **直接綁定到布局標簽**:直接在界面布局文件中為指定標簽綁定事件處理方法。
對于很多Android 界面組件標簽而言,它們都支持onClick屬性,該屬性的屬性值就是一個形如xxx(View source)方法的方法名
示例如下
```
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal">
<EditText
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:editable="false"
android:cursorVisible="false"/>
<!-- 在標簽中為按鈕綁定事件處理方法 -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="單擊我"
android:onClick="clickHandler"/>
//②
</LinearLayout>
```
上面程序中的②處代碼用于在界面布局文件中為Button 按鈕綁定一個事件處理方法:clickHanlder , 這就意味著開發者需要在該界面布局對應的Activity中定義一個void clickHandler(View source )方法,該方法將會負責處理該按鈕上的單擊事件。下面是該界面布局
對應的Java 代碼。
```
public class MainActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
// 定義一個事件處理方法
// 其中source參數代表事件源
public void clickHandler(View source)
{
EditText show = (EditText) findViewById(R.id.show);
show.setText("bn按鈕被單擊了");
}
}
```
## 參考文章
下面是網上的一些相關技術文章
[Android響應onClick方法的五種實現方式](https://www.jianshu.com/p/5776ac0c6e16)
在Android的開發中,對于點擊事件的OnClickListener有下面四種實現方式,可以根據實際場景的需要選擇合適的用法。下面以Button按鈕來舉例說明。
### 方法一:
**適合場景**:任何場景都通用,但對于一個Activity中要是有多個控件要實現onClick方法就會顯得代碼冗余。
```
Button bt_Demo = (Button)findViewById(R.id.bt_Demo);
bt_Demo.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//具體點擊操作的邏輯
}
});
```
### 方法二:
**適合場景**:適合有多個同類型控件(比如Button數組)要實現onClick()方法時使用
```
Button[] demoBtns ;
……
for(Button button : demoBtns ){
button.setOnClickListener(listener);
}
private OnClickListener listener = new OnClickListener(){
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
switch(arg0.getId()){
case R.id.btn_Demo:
//具體點擊操作的邏輯
break;
default:
break;
}
}
}
```
### 方法三:
**適合場景**:同方法二,兩者差別不大
```
Button bt_Demo = (Button)findViewById(R.id.bt_Demo);
bt_Demo.setOnClickListener(new ButtonListener());
private class ButtonListener implements OnClickListener{
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
switch(arg0.getId()){
case R.id.btn_Demo:
//具體點擊操作的邏輯
break;
default:
break;
}
}
}
```
### 方法四:
**適合場景**:適合界面上有不同類型的控件,這種方式將所有控件的onClick方法在一個方法里面實現,看起來比較簡潔
在Activity中實現OnClickListener接口:
```
public class MyActivity extends Activity implements OnClickListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//按紐
Button btn_Demo = (Button)findViewById(R.id.bt_Demo);
bt_Demo.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.btn_Demo:
//具體點擊操作的邏輯
break;
default:
break;
}
}
}
```
### 方法五:
**適合場景**:通用場景,特別是某個控件的點擊方法要實現的邏輯較復雜的時候使用。
在布局文件中加上android:onClick="方法名",對于有些控件(比如TextView)需要兼容舊的SDK API,還需要加上`android:clickable="true"
`
```
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:onClick="onTextViewClick"
android:text="點擊事件"
android:textSize="16sp" />
//在代碼中實現onTextViewClick()方法即可。
public void onTextViewClick(View view){
//具體點擊操作的邏輯
}
```
## android:onClick vs setOnClickListener的區別
[android:onClick vs setOnClickListener](https://www.cnblogs.com/graphics/p/4680073.html)
為Android Widgets添加點擊事件處理函數又兩種方法,一個是在Xml文件中添加onClick屬性,然后在代碼中添加對應的函數。另一個是直接在代碼中添加setOnClickListener函數。兩者什么區別呢?
兩者底層并沒有任何區別,但是在使用方法五,也就是直接綁定到標簽這種處理方式,要注意以下幾點、
1. 事件處理函數必須是**public**的。
2. 事件處理函數必須在**Activity**中定義。
3. 事件處理函數必須有一個View類型的參數。
4. **只能用在API Level 4及以后的版本**。