與界面編程緊密相關(guān)的就是事件處理機(jī)制齐佳,當(dāng)用戶在程序界面上執(zhí)行各種操作時(shí)藻三,應(yīng)用程序必須為用戶動(dòng)作提供響應(yīng)動(dòng)作,這種響應(yīng)動(dòng)作就需要通過(guò)事件處理來(lái)完成。
Android提供了兩種方式的事件處理:基于回調(diào)的事件處理和基于監(jiān)聽(tīng)的事件處理娜饵。
一般來(lái)說(shuō),基于回調(diào)的事件可用于處理一些具有通用性的事件官辈,基于回調(diào)的事件處理代碼會(huì)顯得比較簡(jiǎn)潔箱舞。但對(duì)于某些特定的事件,無(wú)法使用基于回調(diào)的事件處理拳亿,只能采用基于監(jiān)聽(tīng)的事件處理晴股。
一 基于監(jiān)聽(tīng)器的事件處理
相比于基于回調(diào)的事件處理,這是更具“面向?qū)ο蟆毙再|(zhì)的事件處理方式肺魁。在監(jiān)聽(tīng)器模型中电湘,主要涉及三類(lèi)對(duì)象:
1)事件源Event Source:產(chǎn)生事件的來(lái)源,通常是各種組件鹅经,如按鈕寂呛,窗口等。
2)事件Event:事件封裝了界面組件上發(fā)生的特定事件的具體信息瘾晃,如果監(jiān)聽(tīng)器需要獲取界面組件上所發(fā)生事件的相關(guān)信息贷痪,一般通過(guò)事件Event對(duì)象來(lái)傳遞。
3)事件監(jiān)聽(tīng)器Event Listener:負(fù)責(zé)監(jiān)聽(tīng)事件源發(fā)生的事件蹦误,并對(duì)不同的事件做相應(yīng)的處理劫拢。
基于監(jiān)聽(tīng)器的事件處理機(jī)制是一種委派式Delegation的事件處理方式,事件源將整個(gè)事件委托給事件監(jiān)聽(tīng)器强胰,由監(jiān)聽(tīng)器對(duì)事件進(jìn)行響應(yīng)處理舱沧。這種處理方式將事件源和事件監(jiān)聽(tīng)器分離,有利于提供程序的可維護(hù)性偶洋。
舉例:
View類(lèi)中的OnLongClickListener監(jiān)聽(tīng)器定義如下:(不需要傳遞事件)
public interface OnLongClickListener {
boolean onLongClick(View v);
}```
View類(lèi)中的OnLongClickListener監(jiān)聽(tīng)器定義如下:(需要傳遞事件MotionEvent)
public interface OnTouchListener {
boolean onTouch(View v, MotionEvent event);
}```
二 基于回調(diào)的事件處理
相比基于監(jiān)聽(tīng)器的事件處理模型熟吏,基于回調(diào)的事件處理模型要簡(jiǎn)單些,該模型中涡真,事件源和事件監(jiān)聽(tīng)器是合一的分俯,也就是說(shuō)沒(méi)有獨(dú)立的事件監(jiān)聽(tīng)器存在肾筐。當(dāng)用戶在GUI組件上觸發(fā)某事件時(shí)哆料,由該組件自身特定的函數(shù)負(fù)責(zé)處理該事件缸剪。通常通過(guò)重寫(xiě)Override組件類(lèi)的事件處理函數(shù)實(shí)現(xiàn)事件的處理。
舉例:
View類(lèi)實(shí)現(xiàn)了KeyEvent.Callback接口中的一系列回調(diào)函數(shù)东亦,因此杏节,基于回調(diào)的事件處理機(jī)制通過(guò)自定義View來(lái)實(shí)現(xiàn),自定義View時(shí)重寫(xiě)這些事件處理方法即可典阵。
public interface Callback {
// 幾乎所有基于回調(diào)的事件處理函數(shù)都會(huì)返回一個(gè)boolean類(lèi)型值奋渔,該返回值用于
// 標(biāo)識(shí)該處理函數(shù)是否能完全處理該事件
// 返回true,表明該函數(shù)已完全處理該事件壮啊,該事件不會(huì)傳播出去
// 返回false嫉鲸,表明該函數(shù)未完全處理該事件,該事件會(huì)傳播出去
boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyLongPress(int keyCode, KeyEvent event);
boolean onKeyUp(int keyCode, KeyEvent event);
boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
} ```
三歹啼、比對(duì)
基于監(jiān)聽(tīng)器的事件模型符合單一職責(zé)原則玄渗,事件源和事件監(jiān)聽(tīng)器分開(kāi)實(shí)現(xiàn);
Android的事件處理機(jī)制保證基于監(jiān)聽(tīng)器的事件處理會(huì)優(yōu)先于基于回調(diào)的事件處理被觸發(fā)狸眼;
某些特定情況下藤树,基于回調(diào)的事件處理機(jī)制會(huì)更好的提高程序的內(nèi)聚性。
四拓萌、下面拿例子說(shuō)一說(shuō)兩種事件處理事件的不同流程:
1.基于監(jiān)聽(tīng)的事件:監(jiān)聽(tīng)的處理流程
基于監(jiān)聽(tīng)的事件處理主要涉及3個(gè)對(duì)象
Event Source(事件源):事件發(fā)生的場(chǎng)所岁钓,通常就是組件,每個(gè)組件在不同情況下發(fā)生的事件不盡相同微王,而且產(chǎn)生的事件對(duì)象也不相同
Event(事件):事件封裝了界面組件上發(fā)生的特定事件屡限,通常是用戶的操作,如果程序需要獲得界面組件上發(fā)生的相關(guān)信息炕倘,一般可通過(guò)Event對(duì)象來(lái)獲取
Event Listener(事件監(jiān)聽(tīng)器):負(fù)責(zé)監(jiān)聽(tīng)事件源所發(fā)生的事件囚霸,并對(duì)各種事件做出相應(yīng)的處理
-
那么我們?cè)趺窗咽录磁c事件聯(lián)系到一起呢?就需要為事件注冊(cè)監(jiān)聽(tīng)器了激才,就相當(dāng)于把事件和監(jiān)聽(tīng)器綁定到一起拓型,當(dāng)事件發(fā)生后,系統(tǒng)就會(huì)自動(dòng)通知事件監(jiān)聽(tīng)器來(lái)處理相應(yīng)的事件.怎么注冊(cè)監(jiān)聽(tīng)器呢瘸恼,很簡(jiǎn)單劣挫,就是實(shí)現(xiàn)事件對(duì)應(yīng)的Listener接口。
1)為事件對(duì)象添加監(jiān)聽(tīng)
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-863760fba945ef6e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
2)當(dāng)事件發(fā)生時(shí)东帅,系統(tǒng)會(huì)將事件封裝成相應(yīng)類(lèi)型的事件對(duì)象
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-bbeeac2cd7bf9587.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
3)當(dāng)監(jiān)聽(tīng)器對(duì)象接收到事件對(duì)象之后压固,系統(tǒng)調(diào)用監(jiān)聽(tīng)器中相應(yīng)的事件處理來(lái)處理事件
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-6320700750450857.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
注意:事件源可以是任何的界面組件,不太需要開(kāi)發(fā)者參與靠闭,注冊(cè)監(jiān)聽(tīng)器葉只要一行代碼就實(shí)現(xiàn)了帐我,因此事件編程的重點(diǎn)是實(shí)現(xiàn)事件監(jiān)聽(tīng)器類(lèi)
android設(shè)備可用物理編碼按鍵及案件編碼
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-757430ca0e238b1f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
View.OnClickListener:單擊事件的事件監(jiān)聽(tīng)器必須要實(shí)現(xiàn)的接口
View.OnCreateContextMenuListener:創(chuàng)建上下文菜單的事件監(jiān)聽(tīng)器必須要實(shí)現(xiàn)的接口
View.OnFocusChangedListener:焦點(diǎn)改變事件的事件監(jiān)聽(tīng)器必須實(shí)現(xiàn)的接口
View.OnKeyListener:按鈕事件的事件監(jiān)聽(tīng)器必須實(shí)現(xiàn)的接口
View.OnLongClickListener:長(zhǎng)單擊事件的事件監(jiān)聽(tīng)器必須要實(shí)現(xiàn)接口
View.OnTouchListener:觸摸事件的事件監(jiān)聽(tīng)器必須要實(shí)現(xiàn)的接口
與普通java方法調(diào)用不同的是:普通java程序里的方法是由程序主動(dòng)調(diào)用的坎炼,而事件處理中的初見(jiàn)處理器方法是由系統(tǒng)負(fù)責(zé)調(diào)用的
程序中實(shí)現(xiàn)監(jiān)聽(tīng)器有以下幾種方法
內(nèi)部類(lèi)形式
外部類(lèi)形式
匿名內(nèi)部類(lèi)形式
Activity作為事件監(jiān)聽(tīng)器類(lèi)形式(activity本身實(shí)現(xiàn)監(jiān)聽(tīng)器接口)
1>.內(nèi)部類(lèi)作為事件監(jiān)聽(tīng)器類(lèi)
新建一個(gè)工程,界面如下 :
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-a9e630a6e844872f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
xml代碼如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="helloworld" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="內(nèi)部類(lèi)形式"/>
</LinearLayout>```
MainActivity.Java代碼如下
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
private Button Button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button = (android.widget.Button) findViewById(R.id.button);
Button.setOnClickListener(new EnterClickListener());
}
class EnterClickListener implements android.view.View.OnClickListener{
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "內(nèi)部類(lèi)形式", Toast.LENGTH_SHORT).show();
}
}
}```
點(diǎn)擊按鈕后
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-294a385ccbc06d3c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
2>.外部類(lèi)形式作為事件監(jiān)聽(tīng)器類(lèi)
布局界面如下
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-5d18320b0c2eb72b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
xml代碼如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="操作數(shù)1" />
<EditText
android:id="@+id/operator1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="請(qǐng)輸入一個(gè)操作數(shù)"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="操作數(shù)2" />
<EditText
android:id="@+id/operator2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="請(qǐng)輸入一個(gè)操作數(shù)"/>
<Button
android:paddingTop="20dp"
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="結(jié)果"/>
</LinearLayout>```
MainActivity.java代碼如下
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.view.View.OnClickListener;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
private EditText etNumber1;
private EditText etNumber2;
private Button btnResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
btnResult.setOnClickListener(new Claculator(this,etNumber1,etNumber2));
}
private void findView() {
etNumber1 = (EditText) findViewById(R.id.operator1);
etNumber2 = (EditText) findViewById(R.id.operator2);
btnResult = (Button) findViewById(R.id.result);
}
}```
其中拦键,Claculator.java 代碼如下
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
public class Claculator implements OnClickListener {
private Activity activity;
private EditText etNumber1;
private EditText etNumber2;
public Claculator(Activity activity,EditText editText,EditText editText2){
this.activity = activity;
this.etNumber1 = editText;
this.etNumber2 = editText2;
}
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(activity, "計(jì)算結(jié)果為:" + result, Toast.LENGTH_SHORT).show();
}
}```
看看結(jié)果
3>.使用匿名內(nèi)部類(lèi)作為事件監(jiān)聽(tīng)器類(lèi)
就在上面的基礎(chǔ)上直接改MainActivity.java
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
private EditText etNumber1;
private EditText etNumber2;
private Button btnResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
//btnResult.setOnClickListener(new Claculator(this,etNumber1,etNumber2));//是用外部類(lèi)
//是用匿名內(nèi)部類(lèi)
btnResult.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "計(jì)算結(jié)果為:" + result, Toast.LENGTH_SHORT).show();
}
});
}
private void findView() {
etNumber1 = (EditText) findViewById(R.id.operator1);
etNumber2 = (EditText) findViewById(R.id.operator2);
btnResult = (Button) findViewById(R.id.result);
}
}```
4>.Activity作為事件監(jiān)聽(tīng)器
直接改MainActivity.java
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity implements OnClickListener{
private EditText etNumber1;
private EditText etNumber2;
private Button btnResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
//btnResult.setOnClickListener(new Claculator(this,etNumber1,etNumber2));//是用外部類(lèi)
//是用匿名內(nèi)部類(lèi)
/*btnResult.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "計(jì)算結(jié)果為:" + result, Toast.LENGTH_SHORT).show();
}
});*/
btnResult.setOnClickListener(this);
}
private void findView() {
etNumber1 = (EditText) findViewById(R.id.operator1);
etNumber2 = (EditText) findViewById(R.id.operator2);
btnResult = (Button) findViewById(R.id.result);
}
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "計(jì)算結(jié)果為:" + result, Toast.LENGTH_SHORT).show();
}
}```
結(jié)果
5>.綁定到組件事件屬性
就是在界面組件中為指定的組件通過(guò)屬性標(biāo)簽定義監(jiān)聽(tīng)器類(lèi)
剛剛那個(gè)xml文件把button那個(gè)部分改一下
<Button
android:paddingTop="20dp"
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="結(jié)果"
android:onClick="getResult"/>```
然后谣光,在MainActivity.java 里面寫(xiě)那個(gè)方法就行了,注意芬为,必須是public void getResult(View view)這個(gè)格式
public void getResult(View view){
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "計(jì)算結(jié)果為:" + result, Toast.LENGTH_SHORT).show();
}```
結(jié)果
2.基于回調(diào)的事件
1>.回調(diào)機(jī)制與監(jiān)聽(tīng)機(jī)制
如果說(shuō)事件監(jiān)聽(tīng)機(jī)制是一種委托的事件處理萄金,那么回調(diào)機(jī)制則與之相反,對(duì)于基于事件的處理模型來(lái)說(shuō)媚朦,事件源與事件監(jiān)聽(tīng)器是統(tǒng)一的氧敢,或者說(shuō)是事件監(jiān)聽(tīng)器完全消失了,當(dāng)用戶在UI組件上觸發(fā)某個(gè)事件時(shí)询张,組建自己特定的方法將會(huì)負(fù)責(zé)處理事件
為了使回調(diào)方法機(jī)制類(lèi)處理UI組件上發(fā)生的事件孙乖,開(kāi)發(fā)者需要為該組件提供對(duì)應(yīng)的事件處理方法,而java是一種靜態(tài)語(yǔ)言份氧,無(wú)法為某個(gè)對(duì)象動(dòng)態(tài)的添加方法唯袄,因此只能繼續(xù)是用UI組件類(lèi),并通過(guò)重寫(xiě)該類(lèi)的事件處理的方法來(lái)實(shí)現(xiàn)
為了處理回調(diào)機(jī)制的事件處理半火,android為所有UI組件提供了一些事件處理的回調(diào)方法钮糖,以view為例
public boolean onKeyDown(int keyCode,
KeyEvent event)
Default implementation of KeyEvent.Callback.onKeyMultiple(): perform press of the view
when KeyEvent.KEYCODE_DPAD_CENTER or KeyEvent.KEYCODE_ENTER
is released, if the view is enabled and clickable.```
public boolean onKeyShortcut(int keyCode,
KeyEvent event)
Called when an unhandled key shortcut event occurs.```
public boolean onKeyUp(int keyCode,
KeyEvent event)
Default implementation of KeyEvent.Callback.onKeyMultiple(): perform clicking of the view
when KeyEvent.KEYCODE_DPAD_CENTER or
KeyEvent.KEYCODE_ENTER is released.```
public boolean onTouchEvent(MotionEvent event)
Implement this method to handle touch screen motion events.```
public boolean onTrackballEvent(MotionEvent event)
Implement this method to handle trackball motion events. The
relative movement of the trackball since the last event
can be retrieve with MotionEvent.getX() and
MotionEvent.getY(). These are normalized so
that a movement of 1 corresponds to the user pressing one DPAD key (so
they will often be fractional values, representing the more fine-grained
movement information available from a trackball).```
下面以一個(gè)小例子來(lái)說(shuō)明一下逞带,新建一個(gè)工程遇汞,布局文件很簡(jiǎn)單,就一個(gè)textview看尼,MainActivity.java中重寫(xiě)了onKeyDown和onKeyUp方法
代碼如下
package cn.aiyuan1996.huidiao;
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
}
public void showText(String string){
text.setText(string);
}
public void showToast(String string){
Toast toast = Toast.makeText(this, string, Toast.LENGTH_LONG);
toast.setGravity(Gravity.TOP, 0, 100);
toast.show();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_0:
showText("你按下了數(shù)字鍵0");
break;
case KeyEvent.KEYCODE_BACK:
showText("你按下了返回鍵");
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_0:
showToast("你松開(kāi)了數(shù)字鍵0");
break;
case KeyEvent.KEYCODE_BACK:
showToast("你松開(kāi)了返回鍵");
break;
default:
break;
}
text.setText("你沒(méi)有按下任何鍵");
return super.onKeyUp(keyCode, event);
}
}```
運(yùn)行截圖有四張,按下數(shù)字0和松開(kāi)數(shù)字0小压,按下返回鍵和松開(kāi)返回鍵
2>.基于回調(diào)事件的傳播流程
幾乎所有基于回調(diào)的事件都有一個(gè)boolean類(lèi)型的返回值镀娶,發(fā)方法用于標(biāo)識(shí)該處理方法是否能夠完全處理該事件
(1)立膛,如果處理事件的回調(diào)方法返回的值為true,則表明該處理方法已完全處理該事件,且事件不會(huì)被傳播出去
(2)宝泵,如果處理事件的回調(diào)方法返回的值為false好啰,則表明該處理方法并未完全處理該事件,且事件會(huì)被傳播出去
對(duì)于基于回調(diào)的事件傳播而言儿奶,某組件上所發(fā)生的事件不僅能觸發(fā)該組件上的回調(diào)方法框往,也會(huì)觸發(fā)該組件所在的activity類(lèi)的回調(diào)方法-只要事件傳播到該activity類(lèi)
下面以一個(gè)小例子來(lái)說(shuō)明android系統(tǒng)中的事件傳播流程,該程序重寫(xiě)了EditText類(lèi)的onKeyDown()方法闯捎,而且重寫(xiě)了該EditText所在的Activity類(lèi)的onKeyDown()方法椰弊,由于程序中沒(méi)有阻止事件的傳播,所以程序中可以看到事件從RditText傳播到Activity的全過(guò)程
自定義的組件類(lèi)MyTestBox.java
package cn.aiyuan1996.huidiaoprocess;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.EditText;
public class MyTestBox extends EditText{
public MyTestBox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Log.i("MyTestBox", "這里是MyTestBox的onKeyDown");
return false;
}
}```
上面的MyTextBox類(lèi)重寫(xiě)了EditText類(lèi)的onKeyDwon()方法瓤鼻,因此秉版,當(dāng)用戶在此組件上按下任意鍵時(shí)都會(huì)觸發(fā)OnKeyDown()方法,在該方法中返回false茬祷,即按鍵事件會(huì)繼續(xù)向外傳遞
布局文件挺簡(jiǎn)單的清焕,就是把上面那個(gè)自定義的組件包含進(jìn)來(lái)就ok橄唬,不過(guò)此處包含進(jìn)來(lái)的時(shí)候必須要完整包
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res/cn.aiyuan1996.huidiaoprocess"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<cn.aiyuan1996.huidiaoprocess.MyTestBox
android:tag="cn.aiyuan1996.huidiaoprocess.MyTestBox"
android:id="@+id/myTextBox"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</cn.aiyuan1996.huidiaoprocess.MyTestBox>
</LinearLayout>```
然后就是MainActivity了
package cn.aiyuan1996.huidiaoprocess;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnKeyListener;
public class MainActivity extends Activity {
private MyTestBox myTestBox;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myTestBox = (MyTestBox) findViewById(R.id.myTextBox);
myTestBox.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(event.getAction() == KeyEvent.ACTION_DOWN)
Log.i("onCreate", "這里是MyTextBox的OnKeyListener");
return false;
}
});
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Log.i("OnKeyDown", "這里是Activity的onKeyDown");
return false;
}
}```
然后運(yùn)行程序诱桂,發(fā)現(xiàn)程序崩潰了,很好榆芦,對(duì)于這個(gè)問(wèn)題我花了四個(gè)小時(shí)沒(méi)解決沃粗,后來(lái)我同學(xué)也花了半小時(shí)沒(méi)解決粥惧,后來(lái)他回宿舍看了一個(gè)他以前寫(xiě)的,才發(fā)現(xiàn)問(wèn)題最盅,下面我們先來(lái)看看報(bào)錯(cuò)信息
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-e997abd9fee68d49.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
下面是報(bào)錯(cuò)信息
10-25 15:51:20.378: W/IInputConnectionWrapper(1411): showStatusIcon on inactive InputConnection
10-25 16:50:33.068: I/MainActivity(1463): ------>>>>1
10-25 16:50:33.368: D/AndroidRuntime(1463): Shutting down VM
10-25 16:50:33.378: W/dalvikvm(1463): threadid=1: thread exiting with uncaught exception (group=0x409961f8)
10-25 16:50:33.398: E/AndroidRuntime(1463): FATAL EXCEPTION: main
10-25 16:50:33.398: E/AndroidRuntime(1463): java.lang.RuntimeException: Unable to start activity ComponentInfo{cn.aiyuan1996.huidiaoprocess/cn.aiyuan1996.huidiaoprocess.MainActivity}: android.view.InflateException: Binary XML file line #8: Error inflating class cn.aiyuan1996.huidiaoprocess.MyTestBox
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1955)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1980)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.access$600(ActivityThread.java:122)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1146)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.os.Handler.dispatchMessage(Handler.java:99)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.os.Looper.loop(Looper.java:137)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.main(ActivityThread.java:4340)
10-25 16:50:33.398: E/AndroidRuntime(1463): at java.lang.reflect.Method.invokeNative(Native Method)
10-25 16:50:33.398: E/AndroidRuntime(1463): at java.lang.reflect.Method.invoke(Method.java:511)
10-25 16:50:33.398: E/AndroidRuntime(1463): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
10-25 16:50:33.398: E/AndroidRuntime(1463): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
10-25 16:50:33.398: E/AndroidRuntime(1463): at dalvik.system.NativeStart.main(Native Method)
10-25 16:50:33.398: E/AndroidRuntime(1463): Caused by: android.view.InflateException: Binary XML file line #8: Error inflating class cn.aiyuan1996.huidiaoprocess.MyTestBox
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.createView(LayoutInflater.java:589)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:680)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.rInflate(LayoutInflater.java:739)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
10-25 16:50:33.398: E/AndroidRuntime(1463): at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:251)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.Activity.setContentView(Activity.java:1835)
10-25 16:50:33.398: E/AndroidRuntime(1463): at cn.aiyuan1996.huidiaoprocess.MainActivity.onCreate(MainActivity.java:19)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.Activity.performCreate(Activity.java:4465)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1919)
10-25 16:50:33.398: E/AndroidRuntime(1463): ... 11 more
10-25 16:50:33.398: E/AndroidRuntime(1463): Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet]
10-25 16:50:33.398: E/AndroidRuntime(1463): at java.lang.Class.getConstructorOrMethod(Class.java:460)
10-25 16:50:33.398: E/AndroidRuntime(1463): at java.lang.Class.getConstructor(Class.java:431)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.createView(LayoutInflater.java:561)
10-25 16:50:33.398: E/AndroidRuntime(1463): ... 22 more
10-25 16:50:43.078: I/Process(1463): Sending signal. PID: 1463 SIG: 9```
錯(cuò)誤中你能一眼看到錯(cuò)誤的地方
Unable to start activity ComponentInfo{cn.aiyuan1996.huidiaoprocess/cn.aiyuan1996.huidiaoprocess.MainActivity}: android.view.InflateException: Binary XML file line #8: Error inflating class cn.aiyuan1996.huidiaoprocess.MyTestBox```
但是看到這個(gè)信息突雪,你大概知道是你的自定義view出問(wèn)題了,其實(shí)就是構(gòu)造函數(shù)那塊出了問(wèn)題檩禾,構(gòu)造函數(shù)要用有兩個(gè)參數(shù)的那個(gè)挂签,把上面那個(gè)構(gòu)造函數(shù)改成這個(gè)就行了
public MyTestBox(Context context, AttributeSet attrs) {
super(context, attrs);
}```
為什么必須要是這個(gè)構(gòu)造函數(shù)呢,看看這三個(gè)構(gòu)造函數(shù)先
public View(Context context)//Simple constructor to use when creating a view from code. ```
public View(Context context,AttributeSet attrs)//Constructor that is called when inflating a view from XML. This is called when a view is being constructed from an XML file,
supplying attributes that were specified in the XML file. This version uses a default style of 0, so the only attribute values applied are those in the Context's Theme and the given AttributeSet.
The method onFinishInflate() will be called after all children have been added. ```
public View(Context context,AttributeSet attrs,int defStyle)//Perform inflation from XML and apply a class-specific base style. This constructor of View allows subclasses to use their own base style when they are inflating.
//For example, a Button class's constructor would call this version of the super class constructor and supply R.attr.buttonStyle for defStyle; this allows the theme's button style to modify all of the base view attributes (in particular its background) as well as the Button class's attributes. ```
很明顯盼产,兩個(gè)參數(shù)的那個(gè)構(gòu)造函數(shù)是負(fù)責(zé)自定義組件的構(gòu)造的
bug改好后饵婆,我們?cè)龠\(yùn)行一遍
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-199c50c3659e7bcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
隨便輸入一個(gè)東西,我們看看打印了什么內(nèi)容
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-45606c1af7d00bb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
主要是看這個(gè)順序戏售,首先是觸發(fā)的是該組件的綁定的事件監(jiān)聽(tīng)器侨核,然后是該組件所在的類(lèi)提供的事件回調(diào)方法,最后才是傳播給組件所在Activity類(lèi)灌灾,如果在任何一個(gè)事件處理方法返回了true搓译,那么該事件將不會(huì)被繼續(xù)向外傳播
3>基于回調(diào)觸摸事件處理
屏幕事件的處理方法onTouchEvent(),該方法的返回值與鍵盤(pán)響應(yīng)事件相同锋喜,都是當(dāng)程序完整的處理的該事件些己,且不希望其他回調(diào)方法再次處理該事件時(shí)返回true豌鸡,否則返回false .
1)屏幕被按:下MotionEvent.getAction()==MotionEvent.ACTION_DOWN
2)離開(kāi)屏幕:MotionEvent.getAction()==MotionEvent.ACTION_UP
3)在屏幕中拖動(dòng):MotionEvent.getAction()==MotionEvent.ACTION_MOVE
下面以一個(gè)小例子來(lái)說(shuō)明沒(méi)有布局文件,直接上MainActivity.java
package cn.aiyuan1996.touchevent;
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.widget.LinearLayout;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
setContentView(layout);
}
public void showToast(String string){
Toast toast = Toast.makeText(this, string, Toast.LENGTH_LONG);
toast.setGravity(Gravity.TOP, 0, 100);
toast.show();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
showToast("觸摸屏幕");
break;
case MotionEvent.ACTION_UP:
showToast("離開(kāi)屏幕");
break;
case MotionEvent.ACTION_MOVE:
showToast("在屏幕中移動(dòng)");
break;
default:
break;
}
return super.onTouchEvent(event);
}
}```
看看運(yùn)行截圖
再來(lái)說(shuō)一說(shuō)Handler消息傳遞機(jī)制
出于性能優(yōu)化考慮段标,android的ui線程操作是不安全的涯冠,這意味者如果多個(gè)線程并發(fā)操作UI組件,可能導(dǎo)致線程安全問(wèn)題逼庞,為了解決這個(gè)問(wèn)題蛇更,android制定了一條硬性的規(guī)則,只允許UI線程(主線程)修改android里的UI組件
當(dāng)一個(gè)程序第一次啟動(dòng)時(shí)赛糟,android會(huì)同時(shí)啟動(dòng)一條主線程派任,這線程主要負(fù)責(zé)與UI相關(guān)度事件,例如用戶的按鍵事件璧南,用戶的觸摸事件掌逛,以及屏幕繪圖事件,并非相關(guān)的時(shí)間分發(fā)到組件進(jìn)行處理穆咐,所以主線程又叫UI線程颤诀,故android平臺(tái)只允許Ui線程修改activity的ui組件字旭,新的進(jìn)程需要?jiǎng)討B(tài)改變界面組件的屬性值時(shí)对湃,就需要用到Handler了
很多android初學(xué)者對(duì)android 中的handler不是很明白,其實(shí)Google參考了Windows的消息處理機(jī)制遗淳,
在Android系統(tǒng)中實(shí)現(xiàn)了一套類(lèi)似的消息處理機(jī)制拍柒。在下面介紹handler機(jī)制前,首先得了解以下幾個(gè)概念:
1. Message
消息屈暗,理解為線程間通訊的數(shù)據(jù)單元拆讯。例如后臺(tái)線程在處理數(shù)據(jù)完畢后需要更新UI,則可發(fā)送一條包含更新信息的Message給UI線程养叛。
2. Message Queue
消息隊(duì)列种呐,用來(lái)存放通過(guò)Handler發(fā)布的消息,按照先進(jìn)先出執(zhí)行弃甥。
3. Handler
Handler是Message的主要處理者爽室,負(fù)責(zé)將Message添加到消息隊(duì)列以及對(duì)消息隊(duì)列中的Message進(jìn)行處理。
4. Looper
循環(huán)器淆攻,扮演Message Queue和Handler之間橋梁的角色阔墩,循環(huán)取出Message Queue里面的Message,并交付給相應(yīng)的Handler進(jìn)行處理瓶珊。
5. 線程
UI thread 通常就是main thread啸箫,而Android啟動(dòng)程序時(shí)會(huì)替它建立一個(gè)Message Queue。
每一個(gè)線程里可含有一個(gè)Looper對(duì)象以及一個(gè)MessageQueue數(shù)據(jù)結(jié)構(gòu)伞芹。在你的應(yīng)用程序里忘苛,可以定義Handler的子類(lèi)別來(lái)接收Looper所送出的消息。```
來(lái)張圖解:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-c2e3c6b58d4a5e8f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
Handler 、 Looper 扎唾、Message 這三者都與Android異步消息處理線程相關(guān)的概念蜀肘。那么什么叫異步消息處理線程呢?
異步消息處理線程啟動(dòng)后會(huì)進(jìn)入一個(gè)無(wú)限的循環(huán)體之中稽屏,每循環(huán)一次扮宠,從其內(nèi)部的消息隊(duì)列中取出一個(gè)消息,然后回調(diào)相應(yīng)的消息處理函數(shù)狐榔,執(zhí)行完成一個(gè)消息后則繼續(xù)循環(huán)坛增。若消息隊(duì)列為空,線程則會(huì)阻塞等待薄腻。
說(shuō)了這一堆收捣,那么和Handler 、 Looper 庵楷、Message有啥關(guān)系罢艾?其實(shí)Looper負(fù)責(zé)的就是創(chuàng)建一個(gè)MessageQueue,然后進(jìn)入一個(gè)無(wú)限循環(huán)體不斷從該MessageQueue中讀取消息尽纽,而消息的創(chuàng)建者就是一個(gè)或多個(gè)Handler 咐蚯。
Handler類(lèi)主要作用就是這兩個(gè):在新啟動(dòng)的線程中發(fā)送消息,在主線程中獲取和處理消息
只能通過(guò)回調(diào)的方法來(lái)實(shí)現(xiàn)-開(kāi)發(fā)者只需要重寫(xiě)Handler類(lèi)中處理的消息的方法即可弄贿,當(dāng)新啟動(dòng)的線程發(fā)送消息時(shí)春锋,消息會(huì)發(fā)送到與之關(guān)聯(lián)的MessageQueue,而Handler會(huì)不斷的從MessageQueue中獲取并處理消息-這將導(dǎo)致Handler中的處理消息的方法被回調(diào)
下面是Handler類(lèi)中常用的幾個(gè)方法
public void handleMessage(Message msg)Subclasses must implement this to receive messages.
public final boolean hasMessages(int what)Check if there are any pending posts of messages with code 'what' in the message queue. ```
public final Message obtainMessage()Returns a new Message from the global message pool. More efficient than creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this). If you don't want that facility, just call Message.obtain() instead. ```
public final boolean sendEmptyMessage(int what)Sends a Message containing only the what value. ```
public final boolean sendEmptyMessageDelayed(int what,
long delayMillis)Sends a Message containing only the what value, to be delivered after the specified amount of time elapses. ```
public final boolean sendMessage(Message msg)Pushes a message onto the end of the message queue after all pending messages before the current time. It will be received in handleMessage(android.os.Message), in the thread attached to this handler. ```
下面一個(gè)實(shí)例演示如何在界面中修改界面的組件差凹,循環(huán)播放相冊(cè)中的照片
布局文件很簡(jiǎn)單期奔,就一個(gè)ImageView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/image"/>
</LinearLayout>```
然后是MainActivity.java
package cn.aiyuan1996.handler;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;
public class MainActivity extends Activity {
int[] imgList = new int[]{R.drawable.a,R.drawable.b,R.drawable.c,R.drawable.d,R.drawable.e,R.drawable.f};
int currentIndex = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ImageView imageView = (ImageView) findViewById(R.id.image);
final Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what == 0x123){
//動(dòng)態(tài)修改圖片
currentIndex++;
imageView.setImageResource(imgList[currentIndex%imgList.length]);
}
}
};
if(currentIndex <= imgList.length){
new Timer().schedule(new TimerTask() {
@Override
public void run() {
handler.sendEmptyMessage(0x123);
}
}, 0,1200);
}
}
}```
上面代碼中的Timer類(lèi)會(huì)啟動(dòng)一個(gè)新線程,由于不允許在子線程中修改UI界面危尿,所以該線程每隔1200毫秒會(huì)發(fā)送一個(gè)消息呐萌,該消息會(huì)傳遞到Activity中,再由Handler類(lèi)進(jìn)行處理谊娇,從而實(shí)現(xiàn)了動(dòng)態(tài)切換的效果肺孤。
后話:
其實(shí)Handler不僅可以更新UI,你完全可以在一個(gè)子線程中去創(chuàng)建一個(gè)Handler邮绿,然后使用這個(gè)handler實(shí)例在任何其他線程中發(fā)送消息渠旁,最終處理消息的代碼都會(huì)在你創(chuàng)建Handler實(shí)例的線程中運(yùn)行。
new Thread()
{
private Handler handler;
public void run()
{
Looper.prepare();
handler = new Handler()
{
public void handleMessage(android.os.Message msg)
{
Log.e("TAG",Thread.currentThread().getName());
};
};<pre code_snippet_id="445431" snippet_file_name="blog_20140808_19_1943618" name="code" class="java"> ```
Android不僅給我們提供了異步消息處理機(jī)制讓我們更好的完成UI的更新船逮,其實(shí)也為我們提供了異步消息處理機(jī)制代碼的參考~不僅能夠知道原理顾腊,最好還可以將此設(shè)計(jì)用到其他的非Android項(xiàng)目中去~今天先把Handler寫(xiě)道這里
總結(jié)
內(nèi)部類(lèi):使用內(nèi)部類(lèi)作為事件監(jiān)聽(tīng)器,可以在當(dāng)前類(lèi)中重復(fù)使用挖胃,另外杂靶,由于監(jiān)聽(tīng)器是外部類(lèi)的內(nèi)部類(lèi)梆惯,所以可以自由訪問(wèn)外部類(lèi)的所有界面組件
外部類(lèi),外部類(lèi)作為事件監(jiān)聽(tīng)器的情況比較少見(jiàn)吗垮,原因兩點(diǎn):
1.事件監(jiān)聽(tīng)器通常屬于特定的UI界面組件垛吗,定義成外部類(lèi)不利于提高程序的內(nèi)聚性。
2.外部類(lèi)形式的監(jiān)聽(tīng)器烁登,不能自由訪問(wèn)UI界面組件所在類(lèi)的組件怯屉,編程不夠簡(jiǎn)潔。
但是如果某個(gè)事件監(jiān)聽(tīng)器確實(shí)需要被多個(gè)GUI界面所共享饵沧,而且主要是用來(lái)完成某種業(yè)務(wù)邏輯的實(shí)現(xiàn)锨络,則可以考慮是用外部類(lèi)的形式來(lái)定義事件監(jiān)聽(tīng)器類(lèi)。
匿名內(nèi)部類(lèi):我還是最喜歡是用匿名內(nèi)部類(lèi)狼牺,因?yàn)榇蠖鄷?shū)監(jiān)聽(tīng)器都是一次性使用的羡儿,是用也蠻簡(jiǎn)單,new 監(jiān)聽(tīng)器接口 就行了是钥,java語(yǔ)法好點(diǎn)的人相對(duì)容易掌握
Activity作為事件監(jiān)聽(tīng)器:這種做法雖然形式簡(jiǎn)單掠归,但是有兩個(gè)缺點(diǎn) :
1.造成程序的混亂,Activity的主要作用是完成初始化界面的工作悄泥,但是此時(shí)居然還要包含事件處理方法虏冻,可能會(huì)引起混亂 。
2.Activity實(shí)現(xiàn)監(jiān)聽(tīng)器接口码泞,那么他給開(kāi)發(fā)者的感覺(jué)會(huì)比較奇怪
綁定到組件事件屬性:這種在界面中綁定組件的方式比較直觀
android事件攔截處理機(jī)制詳解:http://www.reibang.com/p/f93244fbf667