1. 程式人生 > 其它 >android 後臺處理事情_Android事件處理機制

android 後臺處理事情_Android事件處理機制

技術標籤:android 後臺處理事情

與介面程式設計緊密相關的就是事件處理機制,當用戶在程式介面上執行各種操作時,應用程式必須為使用者動作提供響應動作,這種響應動作就需要通過事件處理來完成。

Android提供了兩種方式的事件處理:基於回撥的事件處理和基於監聽的事件處理。

一般來說,基於回撥的事件可用於處理一些具有通用性的事件,基於回撥的事件處理程式碼會顯得比較簡潔。但對於某些特定的事件,無法使用基於回撥的事件處理,只能採用基於監聽的事件處理。

一 基於監聽器的事件處理

相比於基於回撥的事件處理,這是更具“面向物件”性質的事件處理方式。在監聽器模型中,主要涉及三類物件:

1)事件源Event Source:產生事件的來源,通常是各種元件,如按鈕,視窗等。

2)事件Event:事件封裝了介面元件上發生的特定事件的具體資訊,如果監聽器需要獲取介面元件上所發生事件的相關資訊,一般通過事件Event物件來傳遞。

3)事件監聽器Event Listener:負責監聽事件源發生的事件,並對不同的事件做相應的處理。

基於監聽器的事件處理機制是一種委派式Delegation的事件處理方式,事件源將整個事件委託給事件監聽器,由監聽器對事件進行響應處理。這種處理方式將事件源和事件監聽器分離,有利於提供程式的可維護性。

舉例:

View類中的OnLongClickListener監聽器定義如下:(不需要傳遞事件)

public interface OnLongClickListener {

boolean onLongClick(View v);

}```

View類中的OnLongClickListener監聽器定義如下:(需要傳遞事件MotionEvent)

public interface OnTouchListener {

boolean onTouch(View v, MotionEvent event);

}```

二 基於回撥的事件處理

相比基於監聽器的事件處理模型,基於回撥的事件處理模型要簡單些,該模型中,事件源和事件監聽器是合一的,也就是說沒有獨立的事件監聽器存在。當用戶在GUI元件上觸發某事件時,由該元件自身特定的函式負責處理該事件。通常通過重寫Override元件類的事件處理函式實現事件的處理。

舉例:

View類實現了KeyEvent.Callback介面中的一系列回撥函式,因此,基於回撥的事件處理機制通過自定義View來實現,自定義View時重寫這些事件處理方法即可。

public interface Callback {

// 幾乎所有基於回撥的事件處理函式都會返回一個boolean型別值,該返回值用於

// 標識該處理函式是否能完全處理該事件

// 返回true,表明該函式已完全處理該事件,該事件不會傳播出去

// 返回false,表明該函式未完全處理該事件,該事件會傳播出去

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);

} ```

三、比對

基於監聽器的事件模型符合單一職責原則,事件源和事件監聽器分開實現;

Android的事件處理機制保證基於監聽器的事件處理會優先於基於回撥的事件處理被觸發;

某些特定情況下,基於回撥的事件處理機制會更好的提高程式的內聚性。

四、下面拿例子說一說兩種事件處理事件的不同流程:

1.基於監聽的事件:監聽的處理流程

基於監聽的事件處理主要涉及3個物件

Event Source(事件源):事件發生的場所,通常就是元件,每個元件在不同情況下發生的事件不盡相同,而且產生的事件物件也不相同

Event(事件):事件封裝了介面元件上發生的特定事件,通常是使用者的操作,如果程式需要獲得介面元件上發生的相關資訊,一般可通過Event物件來獲取

Event Listener(事件監聽器):負責監聽事件源所發生的事件,並對各種事件做出相應的處理

-

那麼我們怎麼把事件源與事件聯絡到一起呢?就需要為事件註冊監聽器了,就相當於把事件和監聽器繫結到一起,當事件發生後,系統就會自動通知事件監聽器來處理相應的事件.怎麼註冊監聽器呢,很簡單,就是實現事件對應的Listener介面。

1)為事件物件新增監聽

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-863760fba945ef6e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2)當事件發生時,系統會將事件封裝成相應型別的事件物件

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-bbeeac2cd7bf9587.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

3)當監聽器物件接收到事件物件之後,系統呼叫監聽器中相應的事件處理來處理事件

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-6320700750450857.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

注意:事件源可以是任何的介面元件,不太需要開發者參與,註冊監聽器葉只要一行程式碼就實現了,因此事件程式設計的重點是實現事件監聽器類

android裝置可用物理編碼按鍵及案件編碼

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-757430ca0e238b1f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

View.OnClickListener:單擊事件的事件監聽器必須要實現的介面

View.OnCreateContextMenuListener:建立上下文選單的事件監聽器必須要實現的介面

View.OnFocusChangedListener:焦點改變事件的事件監聽器必須實現的介面

View.OnKeyListener:按鈕事件的事件監聽器必須實現的介面

View.OnLongClickListener:長單擊事件的事件監聽器必須要實現介面

View.OnTouchListener:觸控事件的事件監聽器必須要實現的介面

與普通java方法呼叫不同的是:普通java程式裡的方法是由程式主動呼叫的,而事件處理中的初見處理器方法是由系統負責呼叫的

程式中實現監聽器有以下幾種方法

內部類形式

外部類形式

匿名內部類形式

Activity作為事件監聽器類形式(activity本身實現監聽器介面)

1>.內部類作為事件監聽器類

新建一個工程,介面如下 :

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-a9e630a6e844872f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

xml程式碼如下

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textSize="30dp"

android:text="helloworld" />

android:id="@+id/button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:hint="內部類形式"/>

```

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(), "內部類形式", Toast.LENGTH_SHORT).show();

}

}

}```

點選按鈕後

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-294a385ccbc06d3c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2>.外部類形式作為事件監聽器類

佈局介面如下

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-5d18320b0c2eb72b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

xml程式碼如下

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textSize="30dp"

android:text="運算元1" />

android:id="@+id/operator1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:hint="請輸入一個運算元"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textSize="30dp"

android:text="運算元2" />

android:id="@+id/operator2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:hint="請輸入一個運算元"/>

android:paddingTop="20dp"

android:id="@+id/result"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="結果"/>

```

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, "計算結果為:" + result, Toast.LENGTH_SHORT).show();

}

}```

看看結果

Paste_Image.png

3>.使用匿名內部類作為事件監聽器類

就在上面的基礎上直接改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));//是用外部類

//是用匿名內部類

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(), "計算結果為:" + 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作為事件監聽器

直接改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));//是用外部類

//是用匿名內部類

/*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(), "計算結果為:" + 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(), "計算結果為:" + result, Toast.LENGTH_SHORT).show();

}

}```

結果

Paste_Image.png

5>.繫結到元件事件屬性

就是在介面元件中為指定的元件通過屬性標籤定義監聽器類

剛剛那個xml檔案把button那個部分改一下

android:paddingTop="20dp"

android:id="@+id/result"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="結果"

android:onClick="getResult"/>```

然後,在MainActivity.java 裡面寫那個方法就行了,注意,必須是public void getResult(View view)這個格式

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(), "計算結果為:" + result, Toast.LENGTH_SHORT).show();

}```

結果

Paste_Image.png

2.基於回撥的事件

1>.回撥機制與監聽機制

如果說事件監聽機制是一種委託的事件處理,那麼回撥機制則與之相反,對於基於事件的處理模型來說,事件源與事件監聽器是統一的,或者說是事件監聽器完全消失了,當用戶在UI元件上觸發某個事件時,組建自己特定的方法將會負責處理事件

為了使回撥方法機制類處理UI元件上發生的事件,開發者需要為該元件提供對應的事件處理方法,而java是一種靜態語言,無法為某個物件動態的新增方法,因此只能繼續是用UI元件類,並通過重寫該類的事件處理的方法來實現

為了處理回撥機制的事件處理,android為所有UI元件提供了一些事件處理的回撥方法,以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).```

下面以一個小例子來說明一下,新建一個工程,佈局檔案很簡單,就一個textview,MainActivity.java中重寫了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("你按下了數字鍵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("你鬆開了數字鍵0");

break;

case KeyEvent.KEYCODE_BACK:

showToast("你鬆開了返回鍵");

break;

default:

break;

}

text.setText("你沒有按下任何鍵");

return super.onKeyUp(keyCode, event);

}

}```

執行截圖有四張,按下數字0和鬆開數字0,按下返回鍵和鬆開返回鍵

Paste_Image.png

Paste_Image.png

Paste_Image.png

Paste_Image.png

2>.基於回撥事件的傳播流程

幾乎所有基於回撥的事件都有一個boolean型別的返回值,發方法用於標識該處理方法是否能夠完全處理該事件

(1),如果處理事件的回撥方法返回的值為true,則表明該處理方法已完全處理該事件,且事件不會被傳播出去

(2),如果處理事件的回撥方法返回的值為false,則表明該處理方法並未完全處理該事件,且事件會被傳播出去

對於基於回撥的事件傳播而言,某元件上所發生的事件不僅能觸發該元件上的回撥方法,也會觸發該元件所在的activity類的回撥方法-只要事件傳播到該activity類

下面以一個小例子來說明android系統中的事件傳播流程,該程式重寫了EditText類的onKeyDown()方法,而且重寫了該EditText所在的Activity類的onKeyDown()方法,由於程式中沒有阻止事件的傳播,所以程式中可以看到事件從RditText傳播到Activity的全過程

自定義的元件類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類重寫了EditText類的onKeyDwon()方法,因此,當用戶在此元件上按下任意鍵時都會觸發OnKeyDown()方法,在該方法中返回false,即按鍵事件會繼續向外傳遞

佈局檔案挺簡單的,就是把上面那個自定義的元件包含進來就ok,不過此處包含進來的時候必須要完整包

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" >

android:tag="cn.aiyuan1996.huidiaoprocess.MyTestBox"

android:id="@+id/myTextBox"

android:layout_width="match_parent"

android:layout_height="match_parent"

>

```

然後就是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;

}

}```

然後執行程式,發現程式崩潰了,很好,對於這個問題我花了四個小時沒解決,後來我同學也花了半小時沒解決,後來他回宿舍看了一個他以前寫的,才發現問題,下面我們先來看看報錯資訊

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-e997abd9fee68d49.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

下面是報錯資訊

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: [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```

錯誤中你能一眼看到錯誤的地方

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```

但是看到這個資訊,你大概知道是你的自定義view出問題了,其實就是建構函式那塊出了問題,建構函式要用有兩個引數的那個,把上面那個建構函式改成這個就行了

public MyTestBox(Context context, AttributeSet attrs) {

super(context, attrs);

}```

為什麼必須要是這個建構函式呢,看看這三個建構函式先

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. ```

很明顯,兩個引數的那個建構函式是負責自定義元件的構造的

bug改好後,我們再執行一遍

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-199c50c3659e7bcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

隨便輸入一個東西,我們看看列印了什麼內容

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-45606c1af7d00bb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

主要是看這個順序,首先是觸發的是該元件的繫結的事件監聽器,然後是該元件所在的類提供的事件回撥方法,最後才是傳播給元件所在Activity類,如果在任何一個事件處理方法返回了true,那麼該事件將不會被繼續向外傳播

3>基於回撥觸控事件處理

螢幕事件的處理方法onTouchEvent(),該方法的返回值與鍵盤響應事件相同,都是當程式完整的處理的該事件,且不希望其他回撥方法再次處理該事件時返回true,否則返回false .

1)螢幕被按:下MotionEvent.getAction()==MotionEvent.ACTION_DOWN

2)離開螢幕:MotionEvent.getAction()==MotionEvent.ACTION_UP

3)在螢幕中拖動:MotionEvent.getAction()==MotionEvent.ACTION_MOVE

下面以一個小例子來說明沒有佈局檔案,直接上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("離開螢幕");

break;

case MotionEvent.ACTION_MOVE:

showToast("在螢幕中移動");

break;

default:

break;

}

return super.onTouchEvent(event);

}

}```

看看執行截圖

Paste_Image.png

Paste_Image.png

Paste_Image.png

再來說一說Handler訊息傳遞機制

出於效能優化考慮,android的ui執行緒操作是不安全的,這意味者如果多個執行緒併發操作UI元件,可能導致執行緒安全問題,為了解決這個問題,android制定了一條硬性的規則,只允許UI執行緒(主執行緒)修改android裡的UI元件

當一個程式第一次啟動時,android會同時啟動一條主執行緒,這執行緒主要負責與UI相關度事件,例如使用者的按鍵事件,使用者的觸控事件,以及螢幕繪圖事件,並非相關的時間分發到元件進行處理,所以主執行緒又叫UI執行緒,故android平臺只允許Ui執行緒修改activity的ui元件,新的程序需要動態改變介面元件的屬性值時,就需要用到Handler了

很多android初學者對android 中的handler不是很明白,其實Google參考了Windows的訊息處理機制,

在Android系統中實現了一套類似的訊息處理機制。在下面介紹handler機制前,首先得了解以下幾個概念:

1. Message

訊息,理解為執行緒間通訊的資料單元。例如後臺執行緒在處理資料完畢後需要更新UI,則可傳送一條包含更新資訊的Message給UI執行緒。

2. Message Queue

訊息佇列,用來存放通過Handler釋出的訊息,按照先進先出執行。

3. Handler

Handler是Message的主要處理者,負責將Message新增到訊息佇列以及對訊息佇列中的Message進行處理。

4. Looper

迴圈器,扮演Message Queue和Handler之間橋樑的角色,迴圈取出Message Queue裡面的Message,並交付給相應的Handler進行處理。

5. 執行緒

UI thread 通常就是main thread,而Android啟動程式時會替它建立一個Message Queue。

每一個執行緒裡可含有一個Looper物件以及一個MessageQueue資料結構。在你的應用程式裡,可以定義Handler的子類別來接收Looper所送出的訊息。```

來張圖解:

![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非同步訊息處理執行緒相關的概念。那麼什麼叫非同步訊息處理執行緒呢?

非同步訊息處理執行緒啟動後會進入一個無限的迴圈體之中,每迴圈一次,從其內部的訊息佇列中取出一個訊息,然後回撥相應的訊息處理函式,執行完成一個訊息後則繼續迴圈。若訊息佇列為空,執行緒則會阻塞等待。

說了這一堆,那麼和Handler 、 Looper 、Message有啥關係?其實Looper負責的就是建立一個MessageQueue,然後進入一個無限迴圈體不斷從該MessageQueue中讀取訊息,而訊息的建立者就是一個或多個Handler 。

Handler類主要作用就是這兩個:在新啟動的執行緒中傳送訊息,在主執行緒中獲取和處理訊息

只能通過回撥的方法來實現-開發者只需要重寫Handler類中處理的訊息的方法即可,當新啟動的執行緒傳送訊息時,訊息會發送到與之關聯的MessageQueue,而Handler會不斷的從MessageQueue中獲取並處理訊息-這將導致Handler中的處理訊息的方法被回撥

下面是Handler類中常用的幾個方法

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. ```

下面一個例項演示如何在介面中修改介面的元件,迴圈播放相簿中的照片

佈局檔案很簡單,就一個ImageView

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent" >

android:layout_width="match_parent"

android:layout_height="match_parent"

android:id="@+id/image"/>

```

然後是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){

//動態修改圖片

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類會啟動一個新執行緒,由於不允許在子執行緒中修改UI介面,所以該執行緒每隔1200毫秒會發送一個訊息,該訊息會傳遞到Activity中,再由Handler類進行處理,從而實現了動態切換的效果。

後話:

其實Handler不僅可以更新UI,你完全可以在一個子執行緒中去建立一個Handler,然後使用這個handler例項在任何其他執行緒中傳送訊息,最終處理訊息的程式碼都會在你建立Handler例項的執行緒中執行。

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());

};

};

   ```

Android不僅給我們提供了非同步訊息處理機制讓我們更好的完成UI的更新,其實也為我們提供了非同步訊息處理機制程式碼的參考~不僅能夠知道原理,最好還可以將此設計用到其他的非Android專案中去~今天先把Handler寫道這裡

總結

內部類:使用內部類作為事件監聽器,可以在當前類中重複使用,另外,由於監聽器是外部類的內部類,所以可以自由訪問外部類的所有介面元件

外部類,外部類作為事件監聽器的情況比較少見,原因兩點:

1.事件監聽器通常屬於特定的UI介面元件,定義成外部類不利於提高程式的內聚性。

2.外部類形式的監聽器,不能自由訪問UI介面元件所在類的元件,程式設計不夠簡潔。

但是如果某個事件監聽器確實需要被多個GUI介面所共享,而且主要是用來完成某種業務邏輯的實現,則可以考慮是用外部類的形式來定義事件監聽器類。

匿名內部類:我還是最喜歡是用匿名內部類,因為大多書監聽器都是一次性使用的,是用也蠻簡單,new 監聽器介面 就行了,java語法好點的人相對容易掌握

Activity作為事件監聽器:這種做法雖然形式簡單,但是有兩個缺點 :

1.造成程式的混亂,Activity的主要作用是完成初始化介面的工作,但是此時居然還要包含事件處理方法,可能會引起混亂 。

2.Activity實現監聽器介面,那麼他給開發者的感覺會比較奇怪

繫結到元件事件屬性:這種在介面中繫結元件的方式比較直觀

android事件攔截處理機制詳解:http://www.jianshu.com/p/f93244fbf667