Android学习读书笔记之事件处理监听和回调

Android学习读书笔记之事件处理

一.Android事件处理概述

Android提供了两套强大的事件处理机制:
1.基于监听的事件处理
主要的做法就是为Android界面组件绑定特定的事件监听器。
2.基于回调的事件处理
主要的做法就是重写Android组件特定的回调方法,或者重写Activity的回调方法。Android为绝大部分界面组件都提供了事件响应的回调方法,开发者只要重写他们即可。

二.基于监听的事件处理

1.监听的处理模型
Event Source(事件源):
事件发生的场所,通常就是各个组件,例如按钮,窗口,菜单等。
Event(事件):
事件封装了界面组件上发生的特定事件(通常就是一次用户操作),如果程序需要获得界面组件上所发生事件的相关信息,一般通过Event对象来取得。
Event Listener(事件监听器):
负责监听事件源所发生的事件,并对各种事件做出相应响应。

实例:
该界面布局中只是定义了两个组件:一个文本框和一个按钮。
main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:gravity="center_horizontal">
	<EditText
		android:id="@+id/txt"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:editable="false"
		android:cursorVisible="false"
		android:textSize="12pt"/>
	<!-- 定义一个按钮,该按钮将作为事件源 -->
	<Button
		android:id="@+id/bn"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="单击我"/>
</LinearLayout>
	

MainActivty.java

package org.crazyit.event;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;


public class MainActivity extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		// 获取应用程序中的bn按钮
		Button bn = (Button) findViewById(R.id.bn);
		// 为按钮绑定事件监听器
		bn.setOnClickListener(new MyClickListener()); // ①
	}
	// 定义一个单击事件的监听器
	class MyClickListener implements View.OnClickListener
	{
		// 实现监听器类必须实现的方法,该方法将会作为事件处理器
		@Override
		public void onClick(View v)
		{
			EditText txt = (EditText) findViewById(R.id.txt);
			txt.setText("bn按钮被单击了!");
		}
	}
}

事件源:button按钮
事件监听器:MyClickListener类,监听器类必须由程序员负责实现,实现监听器类的关键就是实现处理器方法。
注册监听器:只要调用事件源的SetxxxListener(xxxListener)方法即可。

对于键盘事件和触摸屏事件等,此时程序需要获取事件发生的详细信息。例如键盘事件需要获取是那个键触发的事件,触摸屏事件需要获取事件发生的位置等,对于这种包含更多信息的事件,Android同样会将事件信息封装成XXXexent对象,并把该对象作为参数传入事件处理器。

以View类为例,它包含了如下几个内部接口:
1> View.OnClickListener: 单击事件的事件监听器必须实现的接口;
2> View.OnCreateContextMenuListener: 创建上下文菜单事件的事件监听器必须实现的接口;
3> View.onFocusChangeListener: 焦点改变事件的事件监听器必须实现的接口;
4> View.OnKeyListener: 按键事件的事件监听器必须实现的接口;
5> View.OnLongClickListener: 长按事件的事件监听器必须实现的接口;
6> View.OnTouchListener: 触摸事件的事件监听器必须实现的接口。

在程序中实现事件监听器,主要方式:
1.内部类形式
2.外部类形式
3.Activity本身作为事件监听器类
4.匿名内部类形式

2.外部类作为事件监听器类

如果某个事件监听器确实需要被多个GUI界面所共享,而且主要是完成某种业务逻辑的实现,则可以考虑使用外部类形式来定义事件监听器类。
SendSmsListener.java

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.telephony.SmsManager;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.widget.EditText;
import android.widget.Toast;

public class SendSmsListener implements OnLongClickListener
{
	private Activity act;
	private EditText address;
	private EditText content;
	public SendSmsListener(Activity act, EditText address
		, EditText content)
	{
		this.act = act;
		this.address = address;
		this.content = content;
	}
	@Override
	public boolean onLongClick(View source)
	{
		String addressStr = address.getText().toString();
		String contentStr = content.getText().toString();
		// 获取短信管理器
		SmsManager smsManager = SmsManager.getDefault();
		// 创建发送短信的PendingIntent
		PendingIntent sentIntent = PendingIntent.getBroadcast(act
				, 0, new Intent(), 0);
		// 发送文本短信
		smsManager.sendTextMessage(addressStr, null, contentStr
				, sentIntent, null);
		Toast.makeText(act, "短信发送完成", Toast.LENGTH_LONG).show();
		return false;
	}
}

MainActivity.java

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.EditText;


public class MainActivity extends Activity
{
	EditText address;
	EditText content;
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		// 获取页面中收件人地址、短信内容
		address = (EditText)findViewById(R.id.address);
		content = (EditText)findViewById(R.id.content);
		Button bn = (Button)findViewById(R.id.send);
		// 使用外部类的实例作为事件监听器
		bn.setOnLongClickListener(new SendSmsListener(
			this , address, content));
	}
}

3.Activity本身作为事件监听器类

可以直接在Activity类中定义事件处理器的方法。但是存在两个缺点,
3.1 这种形式可能造成程序结构混乱,Activity的主要职责应该是完成界面初始化工作,但此时还需包含事件处理器方法,从而引起混乱;
3.2 如果Activity界面类需要实现监听器接口,让人感觉怪异;

MainActivity.java

// 实现事件监听器接口
public class MainActivity extends Activity
		implements View.OnClickListener
{
	EditText show;
	Button bn;
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		show = (EditText) findViewById(R.id.show);
		bn = (Button) findViewById(R.id.bn);
		// 直接使用Activity作为事件监听器
		bn.setOnClickListener(this);
	}
	// 实现事件处理方法
	@Override
	public void onClick(View v)
	{
		show.setText("bn按钮被单击了!");
	}
}

4.匿名内部类作为事件监听器类
大部分时事件监听器只是临时使用一次,所以使用匿名内部类形式会更加合适些。

ManActivity.java

public class MainActivity extends Activity
{
	EditText show;
	Button bn;
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		show = (EditText) findViewById(R.id.show);
		bn = (Button) findViewById(R.id.bn);
		// 使用匿名内部类的实例作为事件监听器
		bn.setOnClickListener(new OnClickListener()
		{
			// 实现事件处理方法
			@Override
			public void onClick(View v)
			{
				show.setText("bn按钮被单击了!");
			}
		});
	}
}

5.直接绑定到标签上
Android还有一种更为简单的绑定事件监听器的方式,那就是直接在界面布局文件中为指定标签绑定事件处理方法。

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:gravity="center_horizontal">
	<EditText
		android:id="@+id/show"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:editable="false"
		android:cursorVisible="false"/>
	<!-- 在标签中为按钮绑定事件处理方法 -->
	<Button
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="单击我"
		android:onClick="clickHandler"/>
</LinearLayout>

MainActivity.java

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;


public class MainActivity extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}
	// 定义一个事件处理方法
	// 其中source参数代表事件源
	public void clickHandler(View source)
	{
		EditText show = (EditText) findViewById(R.id.show);
		show.setText("bn按钮被单击了");
	}
}

三.基于回调的事件处理

Android为所有GUI组件都提供了一些事件处理的回调方法。如View为例,该类包含如下方法:
在这里插入图片描述
下面程序示范了基于回调的事件处理机制,正如前面所提到的,基于回调的事件处理机制可通过自定义View来实现,自定义View时重写该View的事件处理方法即可。
下面是一个自定义按钮的实现类。

MyButton.java

public class MyButton extends Button
{
	public MyButton(Context context, AttributeSet set)
	{
		super(context, set);
	}
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event)
	{
		super.onKeyDown(keyCode, event);
		Log.v("the onKeyDown in MyButton");
		// 返回true,表明该事件不会向外扩散
		return true;
	}
}

接下来在界面布局文件中使用这个自定义View,界面布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
			  android:orientation="vertical"
			  android:layout_width="match_parent"
			  android:layout_height="match_parent">
	<!-- 使用自定义View时应使用全限定类名 -->
	<org.crazyit.event.MyButton
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:text="单击我"/>
</LinearLayout>

通过上述分析,对于基于监听的事件处理模型来说,事件源和事件监听器是分离的,当事件源上发生特定事件时,该事件交给事件监听器负责处理;对于基于回调的事件处理模型来说,事件源和事件监听器是统一的,当事件源发生特定事件时,该事件还是由事件源本身负责处理。

1.基于回调的事件传播
1.1 几乎所有的基于回调的事件处理都有一个boolean的返回值,如果返回true,表明该处理方法已完全处理该事件,该事件不会传播出去。
1.2 如果处理事件的回调方法返回false,表明该处理方法并未完全处理该事件,该事件会传播出去的。

下面的程序示范了Android系统中的事件传播,该程序重写了Button类的onKeyDown(int keyCode, KeyEvent event)方法,而且重写了该Button所在Activity的onKeyDown(int keyCode, KeyEvent event)方法—程序没有阻止事件传播,因此程序可以看到事件从Button传播到Activity的情形中。

MyButton.java

package org.crazyit.event;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Button;

public class MyButton extends Button
{
	public MyButton(Context context , AttributeSet set)
	{
		super(context , set);
	}
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event)
	{
		super.onKeyDown(keyCode , event);
		Log.v("-MyButton-", "the onKeyDown in MyButton");
		// 返回false,表明并未完全处理该事件,该事件依然向外扩散
		return false;
	}
}

MainActivity.java

package org.crazyit.event;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.Button;

public class MainActivity extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		Button bn = (Button) findViewById(R.id.bn);
		// 为bn绑定事件监听器
		bn.setOnKeyListener(new OnKeyListener() {
			@Override
			public boolean onKey(View source
					, int keyCode, KeyEvent event) {
				// 只处理按下键的事件
				if (event.getAction() == KeyEvent.ACTION_DOWN) {
					Log.v("-Listener-", "the onKeyDown in Listener");
				}
				// 返回false,表明该事件会向外传播
				return true; // ①
			}
		});
	}
	// 重写onKeyDown方法,该方法可监听它所包含的所有组件的按键被按下事件
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event)
	{
		super.onKeyDown(keyCode , event);
		Log.v("-Activity-" , "the onKeyDown in Activity");
		//返回false,表明并未完全处理该事件,该事件依然向外扩散
		return false;
	}
}

当该组件上发生某个按键被按下的事件时,Android系统最小触发的应该是该按键上绑定的事件监听器,然后才触发该组件提供的事件回调方法,最后还会传播到该组件所在的Activity,如果我们让任何一个事件处理方法返回了true,那么该事件将不会继续向外传播。

2.重写onTouchEvent方法响应触摸屏事件
对比Android提供的两种事件处理模型,不难发现基于监听的事件处理模型具有更大的优势。
a.基于监听的事件处理模型分工更明确,事件源,事件监听器由两个类分开实现,因此具有更好的可维护性;
b.Android的事件处理机制保证基于监听的事件监听器会被优先触发。

四.响应系统设置的事件

在开发Android应用时,有时候可能需要让应用程序随系统设置而进行调整,如判断系统的屏幕方向,有时需要让应用程序监听系统设置的更改,对系统设置的更改做出响应。

1.Configuration类简介

Configuration cfg = getResources().getConfiguration();

一旦获取了系统的Configuration对象,就可以使用该对象提供的如下常用属性来获取系统的配置信息。
在这里插入图片描述

实例:获取系统设备状态

public class MainActivity extends Activity
{
	EditText ori;
	EditText navigation;
	EditText touch;
	EditText mnc;
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		// 获取应用界面中的界面组件
		ori = (EditText)findViewById(R.id.ori);
		navigation = (EditText)findViewById(R.id.navigation);
		touch = (EditText)findViewById(R.id.touch);
		mnc = (EditText)findViewById(R.id.mnc);
		Button bn = (Button)findViewById(R.id.bn);
		bn.setOnClickListener(new OnClickListener()
		{
			// 为按钮绑定事件监听器
			@Override
			public void onClick(View source)
			{
				// 获取系统的Configuration对象
				Configuration cfg = getResources().getConfiguration();
				String screen = cfg.orientation ==
					Configuration.ORIENTATION_LANDSCAPE
					? "横向屏幕": "竖向屏幕";
				String mncCode = cfg.mnc + "";
				String naviName = cfg.orientation ==
					Configuration.NAVIGATION_NONAV
					? "没有方向控制" :
					cfg.orientation == Configuration.NAVIGATION_WHEEL
						? "滚轮控制方向" :
						cfg.orientation == Configuration.NAVIGATION_DPAD
						? "方向键控制方向" : "轨迹球控制方向";
				navigation.setText(naviName);
				String touchName = cfg.touchscreen ==
					Configuration.TOUCHSCREEN_NOTOUCH
					? "无触摸屏" : "支持触摸屏";
				ori.setText(screen);
				mnc.setText(mncCode);
				touch.setText(touchName);
			}
		});
	}
}

2.重写onConfigurationChanged方法响应系统设置更改

如果程序需要监听系统设置的更改,则可以考虑重写Activity的onConfigurationChanged方法,该方法是一个基于回调的事件处理方法:当系统设置发生更改时,该方法会被自动触发。

实例:监听屏幕方向的改变
该程序的java代码主要会调用Activity的setRequestedOrientation的方法来动态更改屏幕方向。除此之外,我们还重写了Activity的onConfigurationChanged(Configuration newConfig)方法,该方法可用于监听系统设置的更改。

MainActivity.java

public class MainActivity extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		Button bn = (Button) findViewById(R.id.bn);
		// 为按钮绑定事件监听器
		bn.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View source) {
				Configuration config = getResources().getConfiguration();
				// 如果当前是横屏
				if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
					// 设为竖屏
					MainActivity.this.setRequestedOrientation(
						ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
				}
				// 如果当前是竖屏
				if (config.orientation == Configuration.ORIENTATION_PORTRAIT) {
					// 设为横屏
					MainActivity.this.setRequestedOrientation(
						ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
				}
			}
		});
	}
	// 重写该方法,用于监听系统设置的更改,主要是监控屏幕方向的更改
	@Override
	public void onConfigurationChanged(Configuration newConfig)
	{
		super.onConfigurationChanged(newConfig);
		String screen = newConfig.orientation ==
			Configuration.ORIENTATION_LANDSCAPE ? "横向屏幕" : "竖向屏幕";
		Toast.makeText(this, "系统的屏幕方向发生改变" + "\n修改后的屏幕方向为:"
				+ screen, Toast.LENGTH_LONG).show();
	}
}

AndroidManifest.xml
为了让该Activity能监听屏幕方向更改的事件,需要在配置该Activity时指定android:configChanges属性。该属性可以支持mcc,mnc, locale, touchscreen, keyboard, keyboardHidden, navigatin, orientation, screenLayout, uiMode,screenSize,smallestScreenSize,fontScale属性值,其中orientation|screenSize属性值指定该Activity可以监听屏幕方向改变的事件。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="org.example.event" >
	<application
		android:allowBackup="true"
		android:icon="@drawable/ic_launcher"
		android:label="@string/app_name"
		android:theme="@style/AppTheme" >
		<!-- 设置Activity可以监听屏幕方向改变的事件 -->
		<activity
			android:configChanges="orientation|screenSize"
			android:name=".MainActivity"
			android:label="@string/app_name" >
			<intent-filter>
				<action android:name="android.intent.action.MAIN" />
				<category android:name="android.intent.category.LAUNCHER" />
			</intent-filter>
		</activity>
	</application>
</manifest>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值