書名: Android經(jīng)典應(yīng)用程序開發(fā)作者名: 韓超編著本章字?jǐn)?shù): 2326字更新時(shí)間: 2019-01-09 15:18:46
2.3 設(shè)備事件的響應(yīng)
在應(yīng)用程序的控制方面,在大多數(shù)場(chǎng)合下使用的是屏幕上的控件,控件本身也是包含了事件處理的載體。
在某些情況下也需要直接響應(yīng)由輸入設(shè)備發(fā)送過來的事件。在Android系統(tǒng)中,輸入設(shè)備主要為鍵盤、觸摸屏、軌跡球和鼠標(biāo),在Android中,鍵盤響應(yīng)的是KeyEvent,后三者通常響應(yīng)的是MotionEvent。
2.3.1 鍵盤事件的響應(yīng)
鍵盤是Android中的主要輸入設(shè)備,對(duì)按鍵事件的處理是在程序中使用鍵盤的核心內(nèi)容。
鍵盤的事件通常用KeyEvent來表示,KeyEvent是android.view包中的一個(gè)類,主要包含以下一些方法:
final int getKeyCode() // 獲得按鍵碼 final int getAction() // 獲得按鍵的動(dòng)作 final int getFlags() // 獲得標(biāo)志 final int getRepeatCount() // 獲得重復(fù)的信息 final int getScanCode() // 獲得掃描碼
通過KeyEvent接口,可以獲得按鍵相關(guān)的詳細(xì)信息,這些信息都是用整數(shù)值來表示的。按鍵相關(guān)的信息中,其中最主要的是KeyCode,用于標(biāo)識(shí)哪一個(gè)按鍵發(fā)生了事件,Action表示按鍵的動(dòng)作(抬起、按下),RepeatCount表示同一個(gè)按鍵被按下的次數(shù),ScanCode(掃描碼)表示底層按鍵的原始標(biāo)識(shí)。
android.view.KeyEvent.Callback是一個(gè)接口,用于表示發(fā)生按鍵事件后的回調(diào),其中包含了如下幾個(gè)方法:
public abstract boolean onKeyDown(int keyCode, KeyEvent event) public abstract boolean onKeyUp(int keyCode, KeyEvent event) public abstract boolean onKeyMultiple(int keyCode, int count, KeyEvent event) public abstract boolean onKeyLongPress(int keyCode, KeyEvent event)
KeyEvent.Callback接口中的幾個(gè)方法,均具有整型和KeyEvent類型兩個(gè)參數(shù),KeyEvent類型中實(shí)際上包含整型的信息。
以上幾個(gè)方法的返回類型均為boolean類型,如果返回true,表示自己完成這個(gè)事件的處理,如果返回false,表示由下一個(gè)接收器處理這個(gè)事件。
View和Activity都已經(jīng)實(shí)現(xiàn)KeyEvent.Callback接口。在這兩個(gè)類的繼承者中,可以通過重新實(shí)現(xiàn)以上的幾個(gè)方法來響應(yīng)按鍵的事件。
通常情況下,按鍵事件屬于整個(gè)屏幕,因此在Activity中響應(yīng)即可。按照一般的邏輯,當(dāng)鍵盤上一個(gè)按鍵被按下的時(shí)候,不會(huì)去區(qū)分這個(gè)按鍵屬于哪一個(gè)控件。
Activity中的dispatchKeyEvent()方法用于截獲一個(gè)按鍵的事件:
public boolean dispatchKeyEvent (KeyEvent event)
這個(gè)方法將在按鍵到達(dá)窗口之前被調(diào)用,因此通過這個(gè)方法可以阻止系統(tǒng)的其他部分不獲得按鍵事件。
以下的示例需要實(shí)現(xiàn)的內(nèi)容是通過鍵盤來控制一個(gè)圖片的Alpha值,使用上鍵和右鍵增加圖片的Alpha值,使用下鍵和左鍵減少圖片的Alpha值。
這個(gè)按鍵事件響應(yīng)程序的運(yùn)行效果如圖2-4所示。

圖2-4 按鍵事件響應(yīng)程序運(yùn)行效果(左:初始化;中:中間Alpha值;右:Alpha值為0)
本例的布局文件testkeyevent.xml如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/screen" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/alphavalue" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:id="@+id/image" android:src="@drawable/robot" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
從以上的布局文件可以看到,本例包含了一個(gè)文本框和一個(gè)顯示圖片的控件,這樣文本框可用來顯示當(dāng)前Alpha的比例值,顯示圖片的控件ImageView用于顯示一個(gè)圖片。
本例的源代碼的核心部分實(shí)現(xiàn)如下所示:
public class TestKeyEvent extends Activity { private static final String TAG = "TestKeyEvent"; private ImageView mImage; // 界面中的圖片 private TextView mAlphavalueText; private int mAlphavalue; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.testkeyevent);
mImage = (ImageView) findViewById(R.id.image); mAlphavalueText = (TextView) findViewById(R.id.alphavalue); mAlphavalue = 100; mImage.setAlpha(mAlphavalue); // 設(shè)置初始化的透明數(shù)值 mAlphavalueText.setText("Alpha = " + mAlphavalue*100/0xff + "%"); } @Override public boolean onKeyDown(int keyCode, KeyEvent msg){ // 按下事件的處理 Log.v(TAG, "onKeyDown: keyCode = "+ keyCode); // 打印事件信息 Log.v(TAG, "onKeyDown: String = " + msg.toString()); switch (keyCode) { case KeyEvent.KEYCODE_DPAD_UP: // 上鍵和右鍵的處理 case KeyEvent.KEYCODE_DPAD_RIGHT: mAlphavalue += 20; break; case KeyEvent.KEYCODE_DPAD_DOWN: // 下鍵和左鍵的處理 case KeyEvent.KEYCODE_DPAD_LEFT: mAlphavalue -= 20; break; default: break; } if(mAlphavalue>=0xFF)mAlphavalue = 0xFF; // 控制Alpha數(shù)值的范圍 if(mAlphavalue<=0x0)mAlphavalue = 0x0; mImage.setAlpha(mAlphavalue); mAlphavalueText.setText("Alpha = " + mAlphavalue*100/0xff + "%"); return super.onKeyDown(keyCode, msg); } @Override public boolean onKeyUp(int keyCode, KeyEvent msg){ // 抬起事件的處理 Log.v(TAG, "onKeyUp: keyCode = "+ keyCode); // 打印事件信息 Log.v(TAG, "onKeyUp: String = " + msg.toString()); return super.onKeyUp(keyCode, msg); } }
本例使用onKeyDown()方法來獲得按鍵的事件,同類的方法還包括onKeyUp()方法,其參數(shù)int keyCode為按鍵碼,KeyEvent msg表示按鍵事件的消息(其中包含了更詳細(xì)的內(nèi)容)。
通過keyCode(int)可以獲得是哪一個(gè)按鍵響應(yīng),而通過msg(KeyEvent)除了按鍵碼之外,可以獲得更多的信息,例如按鍵的動(dòng)作、重復(fù)信息、掃描碼等內(nèi)容。
本程序打出的一段Log信息如下所示:
VERBOSE/TestKeyEvent(771): onKeyDown: keyCode = 20 VERBOSE/TestKeyEvent(771): onKeyDown: String = KeyEvent{action=0 code=20 repeat=7 meta=0 scancode=108 mFlags=8} VERBOSE/TestKeyEvent(771): onKeyDown: keyCode = 20 VERBOSE/TestKeyEvent(771): onKeyDown: String = KeyEvent{action=0 code=20 repeat=8 meta=0 scancode=108 mFlags=8} VERBOSE/TestKeyEvent(771): onKeyDown: keyCode = 20 VERBOSE/TestKeyEvent(771): onKeyDown: String = KeyEvent{action=1 code=20 repeat=0 meta=0 scancode=108 mFlags=8}
由此可見,按鍵事件KeyEvent可以獲得重復(fù)按鍵的次數(shù)和這個(gè)歷史過程相關(guān)的數(shù)據(jù)信息,在一個(gè)按鍵的響應(yīng)過程中,通常是先發(fā)送一個(gè)或若干個(gè)Down事件,最后是一個(gè)Up事件。
按鍵事件的響應(yīng)有以下幾個(gè)需要注意的細(xì)節(jié):
(1)對(duì)于普通按鍵,通常響應(yīng)onKeyUp()事件,表示按下的時(shí)候不再處理,按鍵抬起時(shí)才有效果;
(2)onKeyDown()可以連續(xù)響應(yīng)一個(gè)按鍵按下的情況,并且通過KeyEvent的getRepeatCount()可以為重復(fù)按鍵增加特殊的處理。
(3)在按鍵處理方法的最后,調(diào)用super.onKeyDown()和super.onKeyUp()表示由父類進(jìn)行默認(rèn)的按鍵處理。
2.3.2 運(yùn)動(dòng)事件的處理
觸摸屏(TouchScreen)和軌跡球(TrackBall)是Android中除鍵盤之外的主要輸入設(shè)備。如果需要使用觸摸屏和軌跡球,可以通過使用運(yùn)動(dòng)事件(MotionEvent)來接收它們的信息。
對(duì)于觸摸屏,一般通過絕對(duì)坐標(biāo)描述它的事件;對(duì)于軌跡球,一般通過相對(duì)坐標(biāo)描述它的事件。在Android系統(tǒng)中,鼠標(biāo)設(shè)備的處理方式類似于軌跡球。
MotionEvent是android.view包中的類,用于表示運(yùn)動(dòng)事件的坐標(biāo)、動(dòng)作等信息。MotionEvent中主要的幾個(gè)方法如下所示:
public final float getX() // 獲得X坐標(biāo) public final float getY() // 獲得Y坐標(biāo) public final int getAction() // 獲得動(dòng)作 public final float getRawX() // 獲得原始的X坐標(biāo) public final float getRawY() // 獲得原始的Y坐標(biāo)
對(duì)于這種運(yùn)動(dòng)事件,最重要的屬性為運(yùn)動(dòng)事件發(fā)生的坐標(biāo)(X,Y),運(yùn)動(dòng)的動(dòng)作表示按下、按住移動(dòng)、抬起等信息。getRawX()和getRawY()表示沒有根據(jù)窗口或者控件做出調(diào)整的原始坐標(biāo)。
運(yùn)動(dòng)事件有其歷史屬性,MotionEvent中相關(guān)的方法如下所示:
public final int getHistorySize() // 獲得歷史的點(diǎn)數(shù) public final float getHistoricalX(int pos) // 獲得歷史的X坐標(biāo) public final float getHistoricalY(int pos) // 獲得歷史的Y坐標(biāo)
在Android API級(jí)別5版本之后,MotionEvent類中還包含了多點(diǎn)觸摸的相關(guān)內(nèi)容,當(dāng)有多個(gè)觸點(diǎn)同時(shí)起作用的時(shí)候,可以獲得觸點(diǎn)的數(shù)目和每一個(gè)觸點(diǎn)的坐標(biāo)。
public final int getPointerCount() // 獲得觸點(diǎn)的數(shù)目 public final float getX(int pointerIndex) // 獲得某個(gè)觸點(diǎn)的X坐標(biāo) public final float getY(int pointerIndex) // 獲得某個(gè)觸點(diǎn)的Y坐標(biāo)
MotionEvent可以獲得多點(diǎn)的基礎(chǔ)信息,而沒有包含手勢(shì)解釋的功能。
在Activity和View中,都具有以下兩個(gè)方法,用于接收運(yùn)動(dòng)事件:
public boolean onTouchEvent(MotionEvent event) public boolean onTrackballEvent(MotionEvent event)
在以上兩個(gè)方法中,MotionEvent類作為參數(shù)傳入,在這個(gè)參數(shù)中可以獲得運(yùn)動(dòng)事件的各種信息。對(duì)于觸摸屏設(shè)備的事件,通常使用onTouchEvent()獲取信息,對(duì)于軌跡球、鼠標(biāo)的事件,使用onTrackballEvent()獲取信息。
Activity中的以下兩個(gè)方法用于階段觸摸屏和軌跡球的事件:
public boolean dispatchTouchEvent (MotionEvent ev) public boolean dispatchTrackballEvent (MotionEvent ev)
通過dispatchTouchEvent()和dispatchTrackballEvent()兩個(gè)方法,可以使得運(yùn)動(dòng)事件不再向窗口中傳遞。
1.在活動(dòng)中響應(yīng)運(yùn)動(dòng)事件
運(yùn)動(dòng)事件可以屬于整個(gè)窗口,在活動(dòng)中響應(yīng)運(yùn)動(dòng)事件表示的就是對(duì)全窗口的運(yùn)動(dòng)事件進(jìn)行處理。其方法為在Activity中重新實(shí)現(xiàn)的onTouchEvent()和onTrackballEvent()方法,接收觸摸屏和軌跡球等事件。
下面是處理簡(jiǎn)單的運(yùn)動(dòng)事件的處理程序,這個(gè)程序在UI的界面中,顯示當(dāng)前的MotionEvent的動(dòng)作和位置。觸摸事件程序的運(yùn)行效果如圖2-5所示。

圖2-5 觸摸事件程序運(yùn)行效果
Action=0
為ACTION_DOWN,按下動(dòng)作
Action=1
為ACTION_UP,抬起動(dòng)作
Action=2
為ACTION_MOVE,移動(dòng)動(dòng)作
布局文件testmotionevent.xml中包含了兩個(gè)簡(jiǎn)單的TextView,分別用于顯示運(yùn)動(dòng)事件的坐標(biāo)和動(dòng)作。本例程序的Java代碼如下所示:
import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.MotionEvent; import android.widget.TextView; public class TestMotionEvent extends Activity { private static final String TAG = "TestMotionEvent"; TextView mAction; TextView mPostion; @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.testmotionevent); mAction = (TextView)findViewById(R.id.action); mPostion = (TextView)findViewById(R.id.postion); } @Override public boolean onTouchEvent(MotionEvent event) { // 觸摸屏事件的處理 int Action = event.getAction(); float X = event.getX(); float Y = event.getY(); mAction.setText("Action = "+ Action); // 顯示觸摸的動(dòng)作 mPostion.setText("Postion = ("+X+","+Y+")"); // 顯示觸摸的位置 return true; } @Override public boolean onTrackballEvent(MotionEvent event) { // 軌跡球事件的處理 int Action = event.getAction(); float X = event.getX(); float Y = event.getY(); mAction.setText("Trackball Action = "+ Action); mPostion.setText("Trackball Postion = ("+X+","+Y+")"); return true; }}
對(duì)于運(yùn)行事件中的信息,軌跡球提供的是相對(duì)坐標(biāo),信息比較單一。對(duì)于觸摸屏,有以下幾個(gè)注意點(diǎn):
(1)Activity的onTouchEvent()中獲得的MotionEvent,是相對(duì)硬件屏幕的坐標(biāo),因此getX()和getRawX()及getY()和getRawY()的含義一般是相同的。
(2)Activity是包含標(biāo)題欄的,因此對(duì)于這種響應(yīng)方式,觸摸標(biāo)題欄也可以觸發(fā)Activity的觸摸事件。
(3)狀態(tài)欄不屬于一個(gè)Activity,由于狀態(tài)欄的存在,在一個(gè)Activity中獲得的最小y值,可能不是0。
2.控件響應(yīng)運(yùn)動(dòng)事件
除了響應(yīng)整個(gè)活動(dòng)的運(yùn)動(dòng)事件,也有專門屬于某一個(gè)區(qū)域的運(yùn)動(dòng)事件的情況。在這種情況下運(yùn)動(dòng)事件屬于一個(gè)控件。為了實(shí)現(xiàn)這種情況,就需要在Vi e w類中進(jìn)行處理。
其中一種方式是實(shí)現(xiàn)View.OnTouchListener()接口,然后將其設(shè)置給某個(gè)控件,表示由其中的onTouch()方法處理這個(gè)控件內(nèi)的觸摸事件。
以下程序接收一個(gè)控件內(nèi)部的觸摸事件,并將結(jié)果顯示到文本框和標(biāo)題欄當(dāng)中。程序運(yùn)行效果如圖2-6所示。

圖2-6 控件內(nèi)的觸摸屏運(yùn)行效果
Action=0
為ACTION_DOWN,按下動(dòng)作
Action=1
為ACTION_UP,抬起動(dòng)作
Action=2
為ACTION_MOVE,移動(dòng)動(dòng)作
本例在布局文件中留出邊緣,白色的區(qū)域?yàn)橐粋€(gè)控件,它并未充滿整個(gè)屏幕,當(dāng)其發(fā)生觸摸事件的時(shí)候,程序界面中的文本框顯示的是當(dāng)前觸摸事件的坐標(biāo);而標(biāo)題欄中顯示的是觸摸時(shí)間的原始坐標(biāo)。從中可以看出坐標(biāo)的原點(diǎn)是控件的左上角,原始坐標(biāo)的原點(diǎn)是屏幕的左上角。
上述程序?qū)崿F(xiàn)的核心內(nèi)容如下所示:
public class TestMotionEvent1 extends Activity implements View.OnTouchListener{ private static final String TAG = "TestMotionEvent1"; private TextView mAction; private TextView mPosition; private View mView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.testmotionevent1); mAction = (TextView) findViewById(R.id.action); mPosition = (TextView) findViewById(R.id.position); mView = findViewById(R.id.touchview); mView.setOnTouchListener(this); // 設(shè)置監(jiān)聽器為當(dāng)前的Activity } public boolean onTouch(View v, MotionEvent event) { // OnTouchListener的方法 int action = event.getAction(); float x = event.getX(); // 獲得坐標(biāo) float y = event.getY(); float rawx = event.getRawX(); // 獲得原始坐標(biāo) float rawy = event.getRawY(); Log.v(TAG, "Action = "+ action ); // 獲得原始坐標(biāo) Log.v(TAG, "("+x+","+y+")"); Log.v(TAG, "("+rawx+","+rawy+")"); setTitle("A = " + action + " Raw ["+ rawx +","+ rawy +"]"); // 顯示原始坐標(biāo) mAction.setText("Action = "+ action); mPosition.setText( "Position = ("+x+","+y+")"); // 顯示坐標(biāo) return true; } }
控件僅僅對(duì)其區(qū)域內(nèi)的觸摸事件做出響應(yīng),因此在上述程序中,只有白色區(qū)域的部分能做出響應(yīng)。一般情況下,對(duì)于一個(gè)控件,使用getX()和getY()得到相對(duì)其左上角的坐標(biāo)的情況比較多,getRawX()和getRawY()沒有必要使用。
控件響應(yīng)事件的另外一種方式,就是讓控件自己去實(shí)現(xiàn)onTouchEvent()和onTrackballEvent()兩個(gè)方法。
以下示例程序用標(biāo)題欄不同顏色的點(diǎn)表示運(yùn)動(dòng)事件的類型,程序的結(jié)果如圖2-7所示。

圖2-7 控件自己實(shí)現(xiàn)并響應(yīng)觸摸事件的運(yùn)行結(jié)果(按下、移動(dòng)、抬起)
當(dāng)觸摸屏按下、移動(dòng)、抬起的時(shí)候,在坐標(biāo)處繪制不同顏色的點(diǎn),在標(biāo)題欄中顯示當(dāng)時(shí)的動(dòng)作和坐標(biāo)。
這里使用的程序的核心內(nèi)容如下所示:
public class TestMotionEvent2 extends Activity { private static final String TAG = "TestMotionEvent2"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new TestMotionView(this)); // 設(shè)置View為Activity的內(nèi)容 } public class TestMotionView extends View { // 繼承實(shí)現(xiàn)一個(gè)新的View private Paint mPaint = new Paint(); private int mAction; private float mX; private float mY; public TestMotionView(Context c) { super(c); mAction = MotionEvent.ACTION_UP; // 初始化一些數(shù)值 mX = 0; mY = 0; } @Override protected void onDraw(Canvas canvas) { Paint paint = mPaint; canvas.drawColor(Color.WHITE); if(MotionEvent.ACTION_MOVE == mAction) { // 移動(dòng)動(dòng)作 paint.setColor(Color.RED); }else if(MotionEvent.ACTION_UP == mAction) { // 抬起動(dòng)作 paint.setColor(Color.GREEN); }else if(MotionEvent.ACTION_DOWN == mAction) { // 按下動(dòng)作 paint.setColor(Color.BLUE); } canvas.drawCircle(mX, mY,10, paint); // 繪制一個(gè)點(diǎn)
setTitle("A = " + mAction + " ["+ mX +","+ mY +"]"); // 標(biāo)題欄顯示 } @Override public boolean onTouchEvent(MotionEvent event) { mAction = event.getAction(); // 獲得動(dòng)作 mX = event.getX(); // 獲得坐標(biāo) mY = event.getY(); Log.v(TAG, "Action = "+ mAction ); Log.v(TAG, "("+mX+","+mY+")"); invalidate(); // 重新繪制 return true; } } }
本程序使用了“自定義控件”的方法,重新實(shí)現(xiàn)控件內(nèi)部onTouchEvent()方法來接收觸摸事件,接收到它,并且記錄發(fā)生事件的坐標(biāo)(x,y)和動(dòng)作(action)。調(diào)用invalidate()重新進(jìn)行繪制。繪制在onDraw()中完成,根據(jù)不同的事件,繪制不同顏色的點(diǎn),并設(shè)置標(biāo)題欄。
在這個(gè)例子中,雖然自定義的Vi e w充滿了窗口,但是依然不包括標(biāo)題欄的部分。標(biāo)題欄不在控件范圍內(nèi),不會(huì)產(chǎn)生這個(gè)控件的觸摸事件。
- 無線網(wǎng)絡(luò)優(yōu)化分析
- 液晶顯示器維修實(shí)踐技術(shù)
- 移動(dòng)通信技術(shù)與網(wǎng)絡(luò)優(yōu)化(第2版)
- 天地一體化信息網(wǎng)絡(luò)通信服務(wù)技術(shù)
- 全程圖解變頻器應(yīng)用與檢測(cè)技能
- 一本書讀懂5G
- 電子組裝先進(jìn)工藝
- 電子元器件應(yīng)用實(shí)戰(zhàn)
- 衛(wèi)星互聯(lián)網(wǎng)微波通信關(guān)鍵技術(shù)(精裝版)
- 移動(dòng)增值業(yè)務(wù)網(wǎng)絡(luò)及其運(yùn)營(yíng)
- 電磁兼容原理與技術(shù)
- RFID標(biāo)簽所有權(quán)安全轉(zhuǎn)換
- 電視機(jī)原理與實(shí)訓(xùn)
- GSM網(wǎng)絡(luò)優(yōu)化:原理與工程(第2版)
- 雷達(dá)裝備管理概論