Android事件處理機制

前言

你的時間有限捍壤,不要浪費于重復(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種形式三椿。
  1. 內(nèi)部類形式
    上述的第一個程序就是內(nèi)部類形式缺菌。
  2. 外部類形式
  3. Activity本身作為事件監(jiān)聽器類
    setOnXxxxListenner(this);
  4. 匿名內(nèi)部類形式
    上述移動圖片的程序就是匿名內(nèi)部類形式。
  5. 直接綁定在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>
這樣當(dāng)程序改變手機屏幕方向時岳链,Activity的onConfigurationChanged()方法就會被回調(diào)花竞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市掸哑,隨后出現(xiàn)的幾起案子约急,更是在濱河造成了極大的恐慌,老刑警劉巖苗分,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厌蔽,死亡現(xiàn)場離奇詭異,居然都是意外死亡摔癣,警方通過查閱死者的電腦和手機奴饮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來择浊,“玉大人戴卜,你說我怎么就攤上這事∽裂遥” “怎么了投剥?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長担孔。 經(jīng)常有香客問我江锨,道長吃警,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任泳桦,我火速辦了婚禮汤徽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘灸撰。我一直安慰自己,他們只是感情好拼坎,可當(dāng)我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布浮毯。 她就那樣靜靜地躺著,像睡著了一般泰鸡。 火紅的嫁衣襯著肌膚如雪债蓝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天盛龄,我揣著相機與錄音饰迹,去河邊找鬼。 笑死余舶,一個胖子當(dāng)著我的面吹牛啊鸭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播匿值,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼赠制,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了挟憔?” 一聲冷哼從身側(cè)響起钟些,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绊谭,沒想到半個月后政恍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡达传,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年篙耗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趟大。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹤树,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逊朽,到底是詐尸還是另有隱情罕伯,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布叽讳,位于F島的核電站追他,受9級特大地震影響坟募,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜邑狸,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一懈糯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧单雾,春花似錦赚哗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渐逃,卻和暖如春够掠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茄菊。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工疯潭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人面殖。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓竖哩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親畜普。 傳聞我的和親對象是個殘疾皇子期丰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 與界面編程緊密相關(guān)的就是事件處理機制,當(dāng)用戶在程序界面上執(zhí)行各種操作時吃挑,應(yīng)用程序必須為用戶動作提供響應(yīng)動作钝荡,這種響...
    GB_speak閱讀 2,319評論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)舶衬,斷路器埠通,智...
    卡卡羅2017閱讀 134,714評論 18 139
  • 一.基于監(jiān)聽的事件處理 在事件監(jiān)聽的處理模型中,主要涉及如下三類對象: Event Source(事件源):事件發(fā)...
    哇樓主閱讀 981評論 0 0
  • 一陣邪風(fēng)吹過逛犹,仿佛失去了理智端辱,頭腦眩暈,感覺靈魂漸漸浮出身體虽画,就在自己陷在空白之際舞蔽,我的靈魂似乎把我?guī)У搅碎L安城樓...
    7827ea344541閱讀 911評論 0 0
  • 愛是一場博弈渗柿,必須保持永遠與對方不分伯仲、勢均力敵,才能長此以往地相依相息朵栖。因為過強的對手讓人疲憊颊亮,太弱的對手令人...
    小人物的狂想曲閱讀 423評論 2 4