基于監(jiān)聽的事件處理機(jī)制
在前面的章節(jié)我們都是以開發(fā)布局為主,涉及到的邏輯非常少,這樣安排是因?yàn)榫帉?UI 會更加直觀,寫完即能看到效果,可以增加我們的學(xué)習(xí)興趣,并能夠?qū)?Android 開發(fā)有一個直觀的感受。在我們設(shè)計出精美的 UI 之后,需要讓它服務(wù)于我們的應(yīng)用程序,這就需要有事件處理機(jī)制了,讓各個 View 進(jìn)行操作的時候它會執(zhí)行相應(yīng)的邏輯,完成我們給它分配的任務(wù)。
1. 事件處理
事件對應(yīng)一個行為,它通常發(fā)生在用戶和App進(jìn)行交互的時候,比如輸入文字、點(diǎn)擊按鈕、手勢等等。Android系統(tǒng)將事件處理設(shè)計成了一種先進(jìn)先出(FIFO)的隊列形式,所以我們可以按照用戶操作的順序來依次處理用戶事件
2. 事件監(jiān)聽
在系統(tǒng)發(fā)生了一個事件之后,我們?nèi)绾谓邮盏竭@個事件呢?這就需要在事件發(fā)生之前提前向系統(tǒng)注冊一個事件監(jiān)聽器,告訴 Android 系統(tǒng)我關(guān)心那些事件,那么系統(tǒng)就會在事件發(fā)生的相應(yīng)時間點(diǎn)給你一個回調(diào)通知,常見的事件監(jiān)聽器有以下幾個:
- OnClickListener:
用來監(jiān)聽控件的點(diǎn)擊事件,即在用戶點(diǎn)擊某個 View 的時候回調(diào)此接口。(這也是開發(fā)過程中最最最常見的接口,一定要牢牢掌握!) - OnLongClickListener:
顧名思義,在 View 被長按的時候回調(diào) - OnFocusChangeListener:
當(dāng)控件的焦點(diǎn)發(fā)生變化的時候回調(diào) - OnKeyListener:
當(dāng)用戶點(diǎn)擊手機(jī)上的按鍵的時候回調(diào)此接口,通??梢杂脕頂r截按鍵事件,然后針對特殊場景做特殊處理 - OnTouchListener:
當(dāng)用戶觸摸屏幕的時候回調(diào),此接口會發(fā)生在OnClickListener
回調(diào)的前面,所以我們可以在Touch事件進(jìn)行一些更早期的預(yù)處理事務(wù)。 - OnMenuItemClickListener:
當(dāng)用戶點(diǎn)擊菜單的時候調(diào)用
以上就是 Android 系統(tǒng)提供的常用事件處理監(jiān)聽器,其中最為常見的就是OnClickListener
,未來的開發(fā)中會大量的使用到,所以必須掌握。所以接下來會以OnClickListener
為例子來演示如何完成事件處理,其他的監(jiān)聽器使用方式也都大同小異。
3. 事件處理方式
事件處理要經(jīng)過以下 4 大步驟:
- 注冊監(jiān)聽器
- 用戶進(jìn)行相應(yīng)操作,系統(tǒng)將事件入隊
- 事件經(jīng)過系統(tǒng)層層分發(fā),最終回調(diào)步驟 1 中注冊的接口
- 執(zhí)行回調(diào)中的邏輯,完成事件處理
Android 中所有的事件處理都會經(jīng)過以上 4 個步驟,但是具體的處理方式會有所不同,接下來介紹一下幾種不同的處理方式,最終達(dá)到的效果是每次點(diǎn)擊 Button 的時候彈出一個 Toast,如下圖:
3.1 聲明內(nèi)部類
通過新增內(nèi)部類的形式實(shí)現(xiàn)OnClickListener
接口,代碼如下:
package com.emercy.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.bottom);
button.setOnClickListener(new EventHandle());
}
private class EventHandle implements View.OnClickListener {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Button被點(diǎn)擊了", Toast.LENGTH_LONG).show();
}
}
}
3.2 匿名內(nèi)部類
匿名內(nèi)部類的寫法會比較簡單直接,但是缺點(diǎn)是只能用一次,并且代碼會集中在方法體內(nèi),如果處理邏輯過于復(fù)雜會導(dǎo)致方法代碼冗余。所以通常在只需要使用一次并且內(nèi)部邏輯不太復(fù)雜的時候使用。
package com.emercy.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
// 創(chuàng)建匿名內(nèi)部類綁定點(diǎn)擊監(jiān)聽器
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 在回調(diào)中處理點(diǎn)擊事件
Toast.makeText(MainActivity.this, "Button被點(diǎn)擊了", Toast.LENGTH_LONG).show();
}
});
}
}
3.3 外部類
如果你的事件處理邏輯需要在多個類中使用,那么以上兩種方式都無法滿足,這時候就需要聲明一個外部類來實(shí)現(xiàn)OnClickListener
接口了:
package com.emercy.myapplication;
import android.content.Context;
import android.view.View;
import android.widget.Toast;
public class EventHandle implements View.OnClickListener {
Context mContext;
public EventHandle(Context context) {
mContext = context;
}
@Override
public void onClick(View v) {
// 點(diǎn)擊回調(diào)中處理事件
Toast.makeText(mContext.getApplicationContext(), "Button被點(diǎn)擊了", Toast.LENGTH_LONG).show();
}
}
由于需要彈 Toast,所以這里在構(gòu)造器中傳入了一個 Context 對象,這樣一來 MainActivity 就可以更整潔一些了:
package com.emercy.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
// 綁定點(diǎn)擊監(jiān)聽器
button.setOnClickListener(new EventHandle(this));
}
}
3.4 Activity 自身實(shí)現(xiàn)接口
我們也可以讓 Activity 去實(shí)現(xiàn) OnClickListener
接口,這樣就可以直接在 Activity 中覆寫 OnClick
方法,將所有的邏輯都封裝在了 Activity 內(nèi)部:
package com.emercy.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
// 直接綁定Activity即可
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// 在回調(diào)中處理點(diǎn)擊事件
Toast.makeText(MainActivity.this, "Button被點(diǎn)擊了", Toast.LENGTH_LONG).show();
}
}
3.5 通過 xml 標(biāo)簽指定
以上四種本質(zhì)上其實(shí)都是通過實(shí)現(xiàn)OnClickListener
接口去監(jiān)聽點(diǎn)擊事件的,除此之外還可以在通過布局文件中添加onClick
標(biāo)簽的方式靜態(tài)綁定點(diǎn)擊事件。這種寫法非常少見,在某些場景下可以幫助簡化很多代碼,但是它不太靈活,大家了解一下即可:
首先在 xml 中找到對應(yīng)的 <Button/>
標(biāo)簽,然后添加onClick
屬性:
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:onClick="eventHandle"
android:text="點(diǎn)擊事件處理">
</Button>
接著在 Activity 中聲明eventHandle
方法,這樣就不需要手動獲取 Button 實(shí)例,也不用綁定點(diǎn)擊事件了。(注意eventHandle的方法簽名必須是固定的)
package com.emercy.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// Java代碼中無需綁定,直接實(shí)現(xiàn)處理函數(shù)即可
public void eventHandle(View v) {
Toast.makeText(getApplicationContext(), "Button被點(diǎn)擊了", Toast.LENGTH_LONG).show();
}
}
4. 總結(jié)
本節(jié)介紹了 Android 的事件處理機(jī)制以及主要常用的集中事件,并以最常用的OnClickListener
為例詳細(xì)講解了集中實(shí)現(xiàn)方式,其他的集中事件幾乎都是照壺畫瓢,大家有興趣的也可以模仿本節(jié)示例自行實(shí)現(xiàn)一下。在本節(jié)之前的內(nèi)容大多是為 UI 布局為主,本節(jié)之后大家將會見到很多事件相應(yīng)及邏輯控制,只有將 UI 和事件處理和在一起,才能寫出各式各樣的App,你打算寫一個什么樣的呢?