前言
你的時間有限捍壤,不要浪費于重復(fù)別人的生活槽畔。不要讓別人的觀點淹沒了你內(nèi)心的聲音栈妆。
Android事件處理概述
Android提供了兩套強大的事件處理機制:
- 基于監(jiān)聽的事件處理
- 基于回調(diào)的事件處理
基于監(jiān)聽的事件處理
基于監(jiān)聽的事件處理是一種更“面向?qū)ο蟆钡氖录幚恚谑录O(jiān)聽的處理模型中竟痰,主要涉及如下三類對象签钩。
EventSource(事件源):事件發(fā)生的場所,通常就是各個組件坏快,例如按鈕铅檩、窗口、菜單等莽鸿。
Event(事件):事件封裝了界面組件上發(fā)生的特定事情昧旨,如果程序需要獲得界面組件上所發(fā)生事件的相關(guān)信息,一般通過Event對象來取得祥得。
Event Listener(事件監(jiān)聽器):負責(zé)監(jiān)聽事件源所發(fā)生的事件兔沃,并對各種事件做出相應(yīng)的響應(yīng)。
下面以一個簡單的入門程序來示范基于監(jiān)聽的事件處理模型级及。
代碼示例
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button bn = (Button) findViewById(R.id.bn);
bn.setOnClickListener(new MyClickListener());
}
class MyClickListener implements View.OnClickListener
{
@Override
public void onClick(View v) {
TextView txt = (TextView) findViewById(R.id.txt);
txt.setText("bn被單擊了");
}
}
}
效果
Screenshot_20171025-151034.png
事件源:程序中的bn按鈕乒疏。
事件監(jiān)聽器:程序中的MyClickListener類。
注冊監(jiān)聽器:setXxxxListener(XxxListener)方法饮焦。
如果事件源觸發(fā)的事件足夠簡單怕吴,事件里封裝的信息比較有限,那就無須封裝事件對象县踢,將事件對象傳入事件監(jiān)聽器即可转绷。但對于鍵盤事件、觸摸屏事件等硼啤,此時程序需要獲取事件發(fā)生的詳細信息议经。例如,鍵盤事件需要獲取是哪個鍵觸發(fā)的時間谴返,觸摸屏事件需要獲取事件發(fā)生的位置等煞肾,對于這種包含更多信息的時間,Android同樣會將事件信息封裝成XxxEvent對象嗓袱,并把該對象作為參數(shù)傳入事件處理器扯旷。
下面以一個簡單的移動圖片來介紹鍵盤事件的監(jiān)聽。屏幕中的圖片會隨用戶單擊鍵的動作而移動索抓。
代碼示例
PictureView.java
public class PictureView extends View {
public float currentX;
public float currentY;
Bitmap picture;
public PictureView(Context context) {
super(context);
//定義圖片
picture = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
setFocusable(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//創(chuàng)建畫筆
Paint p = new Paint();
//繪制圖片
canvas.drawBitmap(picture, currentX, currentY, p);
}
}
該程序不需要布局文件,直接使用PictureView作為Activity顯示的內(nèi)容,并為PictureView增加鍵盤事件監(jiān)聽器逼肯。
MainActivity.java
public class MainActivity extends Activity {
//定義移動的速度
private int speed = 10;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//去掉窗口標(biāo)題
requestWindowFeature(Window.FEATURE_NO_TITLE);
//全屏顯示
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
//創(chuàng)建PictureView組件
final PictureView pv = new PictureView(this);
setContentView(pv);
//獲取窗口管理器
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
//獲得屏幕寬和高
display.getMetrics(metrics);
//設(shè)置圖片的初始位置
pv.currentX = metrics.widthPixels / 2;
pv.currentY = metrics.heightPixels - 40;
//為PictureView組件的鍵盤事件綁定監(jiān)聽器
pv.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
//獲取由哪個鍵觸發(fā)的事件
switch (keyCode) {
//控制圖片下移
case KeyEvent.KEYCODE_S:
pv.currentY += speed;
break;
//控制圖片上移
case KeyEvent.KEYCODE_W:
pv.currentY -= speed;
break;
//控制圖片左移
case KeyEvent.KEYCODE_A:
pv.currentX -= speed;
break;
//控制圖片右移
case KeyEvent.KEYCODE_D:
pv.currentX += speed;
break;
}
//通知PictureView組件重繪
pv.invalidate();
return true;
}
});
}
}
效果
你可以通過鍵入鍵盤上的'A'耸黑、'S'、'D'篮幢、'W'鍵來實現(xiàn)移動圖片大刊。
提示
在程序中實現(xiàn)事件監(jiān)聽器,通常有5種形式三椿。
- 內(nèi)部類形式
上述的第一個程序就是內(nèi)部類形式缺菌。 - 外部類形式
- Activity本身作為事件監(jiān)聽器類
setOnXxxxListenner(this); - 匿名內(nèi)部類形式
上述移動圖片的程序就是匿名內(nèi)部類形式。 - 直接綁定在XML文件
android:onClick=""
基于回調(diào)的事件處理
如果說事件監(jiān)聽機制是一種委托式的事件處理搜锰,那么回調(diào)機制則恰好相反:對于基于回調(diào)的事件處理模型來說伴郁,事件源與事件監(jiān)聽器是統(tǒng)一的,或者說事件監(jiān)聽器完全消失了蛋叼。
下面的程序示范了基于回調(diào)的事件處理機制焊傅。正如前面所提到的,基于回調(diào)的事件處理機制可通過自定義View來實現(xiàn)狈涮,自定義View時重寫該View的時事件處理方法即可狐胎。
代碼示例
MyButton.java
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Log.v("-eventdemo", "任意鍵按下" + keyCode);
//返回true表明事件不會向外擴散
return true;
}
}
callback.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!-- 使用自定義View時應(yīng)使用全限定類名 -->
<com.張敦鋒.eventdemo1.MyButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="單擊"
/>
</LinearLayout>
效果
單擊模擬器上的任意鍵
event1.PNG
提示
對于基于監(jiān)聽的事件模型來說,事件源和事件監(jiān)聽器是分離的歌馍,當(dāng)事件源上發(fā)生特定事件時握巢,該事件交給事件監(jiān)聽器負責(zé)處理;對于基于回調(diào)的時事件處理模型來說松却,事件源和事件監(jiān)聽器是統(tǒng)一的暴浦,當(dāng)事件源發(fā)生特定事件時,該事件還是由事件源本身負責(zé)處理玻褪。
基于回調(diào)的事件傳播
幾乎所有基于回調(diào)的事件處理方法都有一個boolean類型的返回值肉渴,該返回值用于標(biāo)識該處理方法是否能完全處理該事件。
- 如果處理事件的回調(diào)方法返回true带射,表明該處理方法已完全處理該事件同规,該事件不會傳播出去。
- 如果處理事件的回調(diào)方法返回false窟社,表明該處理方法并未完全處理該事件券勺,該事件會傳播出去。
接下里我舉個例子來說明事件是如何傳播的灿里。
代碼示例
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Log.v("-MyButton", "任意鍵按下");
//返回true表明事件不會向外擴散
return false;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.callback);
Button bt = (Button) findViewById(R.id.bt);
bt.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Log.v("Listener", "任意鍵按下");
return false;
}
});
}
//重寫onKeyDown方法关炼,該方法可監(jiān)聽它所包含的所有組件的按鍵被按下事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Log.v("Activity", "任意鍵按下");
return false;
}
效果
全部返回false的結(jié)果。
event2.1.PNG
把監(jiān)聽器里的返回值設(shè)為true匣吊,事件將不傳播儒拂。
event2.2.PNG
響應(yīng)系統(tǒng)設(shè)置的事件
在開發(fā)Android應(yīng)用時寸潦,有時候可能需要讓應(yīng)用程序隨系統(tǒng)設(shè)置而進行調(diào)整,比如判斷系統(tǒng)的屏幕方向社痛、判斷系統(tǒng)方向的方向?qū)Ш皆O(shè)備等见转。
程序可調(diào)用Activity的如下方法來獲取系統(tǒng)的Configuration對象:
Configuration cfg = getResources().getConfiguration();
一旦獲得了系統(tǒng)的Configuration對象,就可以使用該對象提供的如下常用屬性來獲取系統(tǒng)的配置信息蒜哀。
- public float fontScale:獲取當(dāng)前用戶設(shè)置的字體的縮放因子斩箫。
- public int keyboard:獲取當(dāng)前設(shè)備所關(guān)聯(lián)的鍵盤類型。
- public int keyboardHidden:該屬性返回一個boolean值用于標(biāo)識當(dāng)前鍵盤是否可用撵儿。
- public Locale locale:獲取用戶當(dāng)前的Local乘客。
- public int mcc:獲取移動信號的國家碼。
- public int mnc:獲取移動信號的網(wǎng)絡(luò)碼淀歇。
- public int navigation:判斷系統(tǒng)上方向?qū)Ш皆O(shè)備的類型易核。
- public int orientation:獲取系統(tǒng)屏幕的方向。
- public int touchscreen:獲取系統(tǒng)觸摸屏的觸摸方式房匆。
代碼示例
MainAcivity.java
public class MainActivity extends Activity {
TextView ori;
TextView navigation;
TextView touch;
TextView mnc;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.configuration);
//獲取應(yīng)用界面中的界面組件
ori = (TextView) findViewById(R.id.ori);
navigation = (TextView) findViewById(R.id.navigation);
touch = (TextView) findViewById(R.id.touch);
mnc = (TextView) findViewById(R.id.mnc);
}
public void show(View v)
{
Configuration cfg = getResources().getConfiguration();
String screen = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ? "橫向屏幕":"豎向屏幕";
String mncCode = cfg.mnc + "";
String naviName = cfg.navigation ==
Configuration.NAVIGATION_NONAV
?"沒有控制方向":
cfg.navigation == Configuration.NAVIGATION_WHEEL
?"滾輪控制方向":
cfg.navigation == Configuration.NAVIGATION_DPAD
?"方向鍵控制方向":"軌跡球控制方向";
String touchName = cfg.touchscreen ==
Configuration.TOUCHSCREEN_NOTOUCH
?"無觸摸屏":"支持觸摸屏";
ori.setText(screen);
mnc.setText(mncCode);
navigation.setText(naviName);
touch.setText(touchName);
}
}
效果
Screenshot_20171026-101005.png
重寫onConfigurationChanged方法響應(yīng)系統(tǒng)設(shè)置更改耸成。
下面的程序主要調(diào)用Activity的setRequestedOrientation(int)方法來動態(tài)更改屏幕方向。
代碼示例
MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.changeconfiguration);
}
public void change(View v) {
Configuration cfg = getResources().getConfiguration();
// 如果當(dāng)前是橫屏
if (cfg.orientation == Configuration.ORIENTATION_LANDSCAPE) {
// 設(shè)為豎屏
MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
// 如果當(dāng)前是豎屏
if (cfg.orientation == Configuration.ORIENTATION_PORTRAIT) {
// 設(shè)為橫屏
MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
String screen = newConfig.orientation ==
Configuration.ORIENTATION_LANDSCAPE?"橫向屏幕":"豎向屏幕";
Toast.makeText(this, screen, Toast.LENGTH_SHORT).show();
}
}
除此之外浴鸿,為了讓Activity能監(jiān)聽屏幕方向更改的事件井氢,還需要在配置該Activity時指定android:configChanges屬性。
<activity
android:configChanges="orientation|screenSize"
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>