Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android基礎知識(2)—事件處理

Android基礎知識(2)—事件處理

編輯:關於Android編程

我覺得應用程序需要處理最多的就是用戶動作,也就是需要為用戶動作提供響應,這種為用戶動作提供響應的機制就是事件處理。Android提供了兩套事件處理機制:

基於監聽的事件處理:主要做法是為Android界面組件綁定特定的事件監聽器;
基於回調的事件處理:主要做法是重寫Android組件特定的回調方法或者重寫Activity的回調方法;

一、基於監聽的事件處理:

(一)監聽的處理模型:

  1.Event Source(事件源):事件發生的組件;
  2.Event(事件):一次用戶的操作;
  3.Event Listener(事件監聽器):負責監聽事件源發生的事件,並對各事件做出相應的響應;

(注:我舉個例子來說明一下:當用戶單擊按鈕或單擊一個菜單項時,這些動作就會激發一個相應的事件,該事件就會觸發事件源上已注冊的事件監聽器,事件監聽器調用相應的事件處理器來做出相應的響應。)

 

(二)事件和事件監聽器:

1、事件源最容易創建,任意界面組件都可以作為事件源;
2、事件的產生無需程序員關心,它是由系統自動產生;
3、實現事件監聽器是整個事件處理的核心;

 

(三)在程序中實現事件監聽器,通常有以下幾種形式:

  第一種:內部類形式作為事件監聽器
【實例】
當單擊按鈕時,文本框內容改變

布局文件代碼如下:

 


    

 

Java文件代碼如下:

public class Event_Qs extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_qs);
        Button button = (Button) findViewById(R.id.btn1);
        button.setOnClickListener(new MyClickListener());
    }
    class MyClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {

            TextView textView = (TextView) findViewById(R.id.tv1);
            textView.setText("按鈕被單擊了");
        }
    }
}

 

  第二種:匿名內部類形式作為事件監聽器

大部分時候,事件處理器都沒有什麼復用價值,(可復用代碼通常都被抽象成了業務邏輯方法),因此大部分事件監聽器知識臨時使用一次,所以使用匿名內部類形式的事件監聽器更合適。實際上這種形式是目前使用最廣泛的事件監聽器。

 

public class Event_Qs extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_qs);
        Button button = (Button) findViewById(R.id.btn1);
        
        button.setOnClickListener(new View.OnClickListener() {
            //實現事件處理方法
            @Override
            public void onClick(View v) {
                TextView textView = (TextView) findViewById(R.id.tv1);
                textView.setText("按鈕被單擊了");
            }
        });
    }
}

 

第三種:Activity本身作為事件監聽器

這種形式使用Activity本身作為監聽器類,可以直接在Activity類中定義時間處理器方法。這種形式非常簡潔,但使用過程一定要注意程序結構。

public class Event_Qs extends Activity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_qs);
        Button btn1 = (Button) findViewById(R.id.btn1);
        Button btn2 = (Button) findViewById(R.id.btn2);

        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        TextView textView = (TextView) findViewById(R.id.tv1);
        switch (v.getId()){
            case R.id.btn1:
                textView.setText("按鈕1被單擊了");
                break;
            case R.id.btn2:
                textView.setText("按鈕2被單擊了");
                break;
        }
    }
}
第四種:直接綁定到標簽作為事件監聽器

 

Android還有一種更簡潔的綁定事件監聽器方式,那就是直接在界面布局文件為指定標簽綁定事件處理方法。

以下是布局文件代碼:

 



    
java文件代碼如下:

 

public class Event_Qs extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_qs);
        Button btn1 = (Button) findViewById(R.id.btn1);
    }
//定義了一個事件處理方法
 public void btnclick(View source) {
        TextView textView = (TextView) findViewById(R.id.tv1);
        textView.setText("按鈕被單擊了");
    }
}
上面程序中定義了一個btnclick(View source)方法,並在xml文件的按鈕中添加了android:onClick="btnclick"的屬性,當用戶單擊按鈕時,該方法就會被激發並處理btn1按鈕的單擊事件。

 

二、基於回調的事件處理

正如文章開頭所說基於回調的事件處理的主要做法是重寫Android組件特定的回調方法或者重寫Activity的回調方法,那麼相對於基於監聽事件處理來說,事件源與事件監聽器就合為一體。或者說,事件監聽器就消失了。

 

(一)回調與監聽:

 

為了實現回調機制的事件處理,Android為所有GUI組件都提供了一些事件處理的回調方法,以View為例,該類包含如下方法。

1、boolean onKeyDown(int,KeyEvent):當按下某組件時觸發;
2、boolean onKeyLongPress(int,KeyEvent):當長按某組件時觸發;
3、boolean onKeyShortcut(int,KeyEvent):當鍵盤快捷鍵事件發生時觸發;
4、boolean onKeyUp(int,KeyEvent):當組件上松開某個按鍵時觸發;
5、boolean onKeyTouchEvent(event):當用戶在組件上觸摸時觸發;
6、boolean onTrackball(event):當用戶在組件上觸發軌跡球事件時觸發;

【實例】通過自定義View來實現基於回調的事件處理機制,自定義View時重寫該View的事件處理方法。

 

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("songsong.com.eventtest", "The onkeydown in MyButton" );
        return true;
    }
}
在上面自定義了MyButton類中,重寫Button類的onKeyDown(int keyCode, KeyEvent event)方法。
接下來是在界面布局中使用自定義View:

    
    

 

運行上面程序,先把焦點定位到該按鈕上,接著單擊模擬器上任意按鈕就可以看到效果:

\
小結:

對於基於監聽的事件處理模型來說,事件源和事件監聽器是分離的。當事件源上發生特定事件時,該事件交給事件監聽器負責處理;
對於基於回調的事件處理模型來說,事件源和事件監聽器是統一的。當事件源發生特等事件時,該事件還是由事件源本身負責處理;

 

 

(二)基於回調的事件傳播:

 

幾乎所有基於回調的時間處理方法都有一個boolean類型的返回值,該返回值用於標識該處理方法是否能完全處理該事件:

如果處理事件的回調方法返回是true,表明該處理方法已經完成處理該事件,不會繼續傳播。如果處理事件的回調方法返回是false,表明處理方法並未完全處理該事件,會繼續傳播。

【實例】分別在Activity中重寫onKeyDown和Button類中重寫onKeyDown,最後為按鈕添加OnKeyListener處理OnKeyDown事件,返回值皆為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("songsong.com.eventtest", "The onkeydown in MyButton");
        return false;
    }
}

 

上面Mybutton子類重寫了OnKeyDown,由於返回了false,事件還將繼續傳播。

 

public class Event_Qs extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_qs);
        Button btn1 = (Button) findViewById(R.id.btn1);
        btn1.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                //只處理按下鍵的事件
                if (event.getAction() == KeyEvent.ACTION_DOWN)

                    Log.v("songsong.com.eventtest", "The onkeydown in OnKeyListener");

                return false;//繼續外傳
            }
        });
    }
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        super.onKeyDown(keyCode, event);
        Log.v("songsong.com.eventtest", "The onkeydown in Activity");
        return false;
    }
上面Activity中重寫了OnKeyDown,為按鈕添加OnKeyListener處理OnKeyDown事件,返回值皆為false。
效果圖:
\

 

當事件在傳播的過程,最先觸發的是按鈕上綁定的事件監聽器,然後才觸發組件提供的事件回調方法,最後才會傳播到該組件所在的Activity。當然,如果返回值是true,那麼就沒然後了。

 

 

三、響應系統設置的事件:

開Android應用時,有時候可能要讓應用程序隨系統設置而進行調整,比如系統的屏幕方向、判斷系統方向的導航設備等等,需要對系統設置的更改做出響應。

 

 

(一)Configuration類:

 

Configuration類專門用於描述手機設備上的配置信息,這些配置信息既包括用戶特定的配置項,也包括系統的動態設置配置。

 

 

Configuration cf = getResources().getConfiguration();

當獲取完Configuration對象就可以調用該對象提供的屬性來取得系統配置信息:

1、public int KeyboardHidden:該屬性返回布爾值來標識鍵盤是否可用,如果軟鍵盤可用 KEYBOARDHIDDEN_NO,硬軟鍵盤都不可用就YES;
2、public int mcc:獲取移動信號的國家碼;
3、public int mnc:獲取移動信號的網絡碼;
4、public int navigation:判斷系統上方向導航設備的類型;
5、public intorientation:獲取系統屏幕的方向,ORIENTATION_LANDSCAPE (橫向),ORIENTATION_PORTRAIT(豎向);
6、public inttouchscreen:獲取系統觸摸屏的觸摸方式;

【實例】獲取系統設備狀態

首先定義4個文本框來顯示系統信息,通過一個按鈕觸發。布局文件代碼因為太簡單以至於不貼出來了:

MainActivity.java代碼內容:

public class ConfigurationTest extends Activity {
    TextView ori, nav, touch, mnc;
    Button btncg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_configuration);
        ori = (TextView) findViewById(R.id.ori);  //屏幕方向
        nav = (TextView) findViewById(R.id.nav);  //方向導航設備類型
        touch = (TextView) findViewById(R.id.touch);  //觸摸方式
        mnc = (TextView) findViewById(R.id.mnc);    //移動信號的網絡碼
        btncg = (Button) findViewById(R.id.btncg);

        btncg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Configuration cf = getResources().getConfiguration();
                String ori_str = cf.orientation == Configuration.ORIENTATION_LANDSCAPE ? "橫向屏幕" : "縱向屏幕";

                String mnc_str = cf.mnc + "";

                String nav_str = cf.navigation == Configuration.NAVIGATION_NONAV ? "沒有方向控制" :
                        cf.navigation == Configuration.NAVIGATION_WHEEL ? "滾輪控制方向" :
                                cf.navigation == Configuration.NAVIGATION_DPAD ? "方向鍵控制方向" : "軌跡球控制方向";
                String touch_str = cf.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH ? "無觸摸屏" : "支持觸摸屏";

                ori.setText(ori_str);
                nav.setText(nav_str);
                touch.setText(touch_str);
                mnc.setText(mnc_str);
            }
        });
    }
}
效果如下:

\

 

(二)重寫onConfigurationChanged方法響應系統設置:

如果想監聽系統設置的更改,就可以重寫Activity的onConfigurationChanged()方法,該方法是一個基於回調的事件處理方法:當系統設置發送更改時,該方法會被自動觸發。

【實例】動態更改屏幕方向
布局文件中只有一個按鈕,通過對單擊按鈕事件動態修改系統屏幕的方向。

public class Changecfg extends Activity {
    Button btnchange;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_changecfg);
        btnchange = (Button) findViewById(R.id.btnchange);
        btnchange.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Configuration cfg = getResources().getConfiguration();
                if (cfg.orientation == Configuration.ORIENTATION_LANDSCAPE) {   //如果當前是橫屏的話
                    //設置為豎屏
                    Changecfg.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                }

                if (cfg.orientation == Configuration.ORIENTATION_PORTRAIT) {   //如果當前是豎屏的話
                    //設置為橫屏
                    Changecfg.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時加上android:configChanges="orientation|screenSize",為了就是要監聽屏幕方向改變的事件。
 
            
                

                
            
        
效果:
\

 

 

四、Handler消息傳遞機制:

出於性能優化的考慮,Android制定了一條簡單的規則:只允許UI線程修改Activity裡的UI組件。這樣的話就會導致新啟動的線程無法動態改變界面組件的屬性值,但在實際開發中,尤其是涉及動畫的游戲開發中,想要新啟動的線程周期性的改變界面組件的屬性值就要借助Handler的消息傳遞機制實現了。

(一)Handler類簡介:

 

根據書上所說,Handler類的主要作用有兩個:

在新啟動的線程中發送消息;
在主線程中獲取、處理消息;

 

那麼我們要解決的問題就是:

新啟動的線程何時發送消息?主線程何時去獲取並處理消息?

 

顯然,我們這章節的內容是事件處理,那麼我們通過回調的方式來實現。開發者只需重寫Handler類中處理消息的方法,當新啟動的線程發送消息時,消息會發送到與之關聯的MessageQueue,而Handler會不斷地從MessageQueue中獲取並處理消息。

Handler類包含如下方法用於發送、處理消息:
void handleMessage(Message msg) :進程通過重寫這個方法來處理消息。final boolean hasMessage(int what):檢查消息隊列中是否包含what屬性為指定值的消息。final boolean hasMessage(int what,Object object):檢查消息隊列中是否有指定值和指定對象的消息。Message obtainMessage(): 獲取消息,可被多種方式重載。sendEmptyMessage(int what): 發送空消息;final boolean sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒之後發送空消息。
final boolean sendMessage(Message msg):立即發送消息。final boolean sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒之後發送空消息

【實例】自動播放圖片
通過一個新的線程來周期性地修改ImageVIew所顯示的圖片,布局文件中只定義了ImageVIew組件。

 

public class HandlerTest extends Activity {
    int[] imagesID = new int[]{
            R.drawable.a1,
            R.drawable.a2,
            R.drawable.a3,
            R.drawable.a4,
    };
    int currentImageID = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_test);
        final ImageView show = (ImageView) findViewById(R.id.iv1);
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 0x1233) {
                    show.setImageResource(imagesID[currentImageID++ % imagesID.length]); //動態修改所顯示的圖片
                }
            }
        };
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                handler.sendEmptyMessage(0x1233);  //發送空消息
            }
        }, 0, 1200);
    }
}

 

上面程序中的代碼通過Timer周期性地執行指定任務。由於Android 不允許在新線程中訪問Activity裡的界面組件,因此程序只能在新的線程裡發送一條消息,通知系統更新ImageView組件。

\

 

 

(二)深入理解Handler的工作機制:

為了更好理解Handler的工作原理,先了解與Handler一起工作的幾個組件:

Looper:每個線程只有一個Looper,它負責管理MessageQueue,會不斷的從MessageQueue中取出消息,並將消息分給對應的Handler處理。
MessageQueue:由Looper負責管理,它采用先進先出FIFO的方式來管理Message。
Handler:它能把消息發給Looper管理的MessageQueue,並負責處理Looper分給它的消息。Message:Handler接收和處理的消息對象。

以下是工作流程圖:
\

在線程中使用Handler的步驟如下:

調用Looper的prepare()方法為當前線程創建Looper對象,創建Looper對象時,它的構造器會自動創建配套的MessageQueue。有了Looper之後,創建Handler子類的實例,重寫handleMessage()方法。調用Looper的loop()方法啟動Looper。

【實例】在線程中計算質數

為了防止在UI線程中執行一個耗時的操作,而導致UI線程被阻塞,令應用程序失去響應,讓新線程來計算該數值范圍內的所有質數。

 

import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;

public class Handler_Looper extends Activity {

    static final String UPPER_NUM = "upper";
    EditText etNum;
    CalThread calThread;

    // 定義一個線程類
    class CalThread extends Thread {
        public Handler myhandler;

        public void run() {
            Looper.prepare();   //創建Looper對象
            myhandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    if (msg.what == 0x123) {
                        int upper = msg.getData().getInt(UPPER_NUM);
                        List nums = new ArrayList();
                        outer:
                        for (int i = 2; i <= upper; i++) {
                            for (int j = 2; j <= Math.sqrt(i); j++) {
                                if (i != 2 && i % j == 0) {
                                    continue outer;
                                }
                            }
                            nums.add(i);
                        }
                        Toast.makeText(Handler_Looper.this, nums.toString(), Toast.LENGTH_LONG).show();
                    }
                }
            };
            Looper.loop();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler__looper);
        etNum = (EditText) findViewById(R.id.et1);
        calThread = new CalThread();
        calThread.start();
    }

    public void cal(View v) {
        Message msg = new Message();
        msg.what = 0x123;
        Bundle bundle = new Bundle();
        bundle.putInt(UPPER_NUM,Integer.parseInt(etNum.getText().toString()));
        msg.setData(bundle);
        calThread.myhandler.sendMessage(msg);
    }
}
\

 

五、異步任務:


 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved