上一节UI组件的学习和练习主要是让我们对绘制有一定的了解,接下来主要谈论UI组件对触摸、滑动。。。等动作的响应和处理部分,而这就涉及到android中的事件处理机制。android系统中提供2种事件处理机制:
- 基于监听的事件处理:绑定事件监听器,事件发生则调用对应方法处理。使用设计模式中的观察者模式。
- 基于回调的事件处理:重写组件的回调方法,代码相对简洁,使用设计模式中的外观模式。
1 基于监听的事件处理
监听模式关键三要素:事件源(事件发生场所,通常为各个组件)、事件(封装了一次用户操作)、事件监听器(监听事件源发生的事件)。
基于监听的事件处理机制是一种委派式(Delegation)事件处理方式。普通组件将整个事件处理委托给事件监听器,当事件源发生指定的事件时,就通知所委托的事件监听器,由事件监听器来处理该事件。事件流程如下:
监听器实现的4种方式:匿名内部类、外部类实现、接口实现、xml文件中配置。
@1 匿名内部类。当只有一套UI组件使用该监听器时适合使用该方式。代码实现如下:
public class ActivityTest extends AppCompatActivity {
private TextView textview;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(layout.activity_main);
textview=(TextView)findViewById(id.tv);
textview.setText("start");
button=(Button)findViewById(id.bt);
//一气呵成,创建监听器,实现监听方法,UI组件注册监听器
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textview.setText("clicked");
}
});
}
}
@2 内/外部类实现。当很多UI组件都使用同一套监听器时,适合使用该方式。这里以内部类实现为例(外部类实现是在ActivityTest类之外实现该类,除此之外一致),代码实现如下:
public class ActivityTest extends AppCompatActivity {
private TextView textview;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(layout.activity_main);
textview=(TextView)findViewById(id.tv);
textview.setText("start");
button=(Button)findViewById(id.bt);
//关键点2,UI组件注册监听器
button.setOnClickListener(new ButtonListener());
}
//关键点1,创建一个实现监听接口的类并实现监听
class ButtonListener implements View.OnClickListener{
public void onClick(View v){
textview.setText("clicked");
}
}
}
@3 接口实现。ActivityTest类不但要继承Activity,还要实现监听器的接口,代码实现如下:
//关键点1,接口实现,实现监听器
public class ActivityTest extends AppCompatActivity implements View.OnClickListener {
private TextView textview;
private Button bn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textview = (TextView)findViewById(R.id.tv);
bn = (Button)findViewById(R.id.bn);
bn.setOnClickListener(this);//关键点3,UI组件注册监听器
}
@Override//关键点2,UI组件实现监听
public void onClick(View v) {
textview.setText("clicked");
}
}
@4 xml配置。Layout中UI组件中定义 onClick="XXX",如下所示:
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
...
android:onClick="ClickFunc"/>
然后在该布局对应的Activity中定义一个 void XXX(void source)方法即可。代码如下所示:
public class ActivityTest extends AppCompatActivity {
private TextView textview ;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(layout.activity_main);
textview = (TextView)findViewById(R.id.tv);
textview.setText("start");
}
public void ClickFunc(View v){
textview.setText("clicked");
}
}
这里只是以onClickListener为例来解读了监听器的4种实现方式,关于更多监听器的API,参照文档:Android 组件之View各种监听器API总结
2 基于回调的事件处理
在 Android系统中,除了可以使用监听器进行事件处理之外,还可以通过回调机制进行事件处理。回调事件的处理模式中,用户在UI组件上触发某个事件时,UI组件方法会负责处理该事件。为了使用回调机制处理UI组件中的事件,一般是自定义新UI组件,之后重写该类事件的处理方法来实现。这里以自定义Button方式为例,代码如下:
public class MainActivity extends AppCompatActivity {
private TestButton tbt;//定义自定义Button对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tbt = findViewById(R.id.testbutton);
}
}
class TestButton extends androidx.appcompat.widget.AppCompatButton {
private String TAG = "TestButton UI";
public TestButton(Context context) {
super(context);
}
//触摸屏事件触发该方法
//重写Button组件的onTouchEvent方法
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
Log.d(TAG, "onTouchEvent in TestButton");
return true;//返回值true,表示该事件已消费
}
//...
}
xml文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<com.ags.myapplication.TestButton
android:id="@+id/testbutton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TestButton" />
</LinearLayout>
以上案例主要解读了自定义UI组件 重写方法的模式,该过程便是实现回调的方式(注意:UI组件回调方法(比如onTouchEvent)中返回值为true表示事件已处理,不会再传播;为false表示会继续传播并处理)。
关于事件的传播顺序,一般事件是先触发UI组件的监听器,之后触发处理方法。在重写方法上处上是先触发子类处理方法,之后:
- 如果子类方法返回false,继续触发父类处理方法。
- 如果子类方法返回true,事件消费掉,后面不再处理。
对比监听方式 和 回调方式 两者的区别:
- 基于监听的事件处理模型,事件源和事件监听器是分离的,事件源出发事件时交给事件监听器负责。监听模型用的是观察者模式。
- 基于回调的事件处理模型,当事件源发生特定事件时,该事件由事件源本身处理。回调方法用的是外观模式。
3 Configuration获取系统设置
这是一种 当系统设置改变时应用也可以跟着系统配置改变而改变的机制(比如屏幕方向、触摸屏的类型等)。关于Configuration的XML属性和方法使用参照文档:Android之Configuration类
4 handler Message机制
handler机制设计的关键类有:
- handler:主要2个作用,新线程中发送消息,主线程中接收&处理消息。
- Message:handler接收和处理的消息对象。
- MessageQueue:消息队列,先进先出,提供消息处理方法,比如消息的读取和发送。
- Looper:每个线程只有一个Looper,负责读取MessageQueue中的消息,读到后交给handler的handleMessage方法中处理。
关于更多handler相关原理内容可以查看文章:
android系统核心机制 基础(04)handler message机制 java
android系统核心机制 基础(06)handler message机制 Native
handler使用案例可查看文章:
android系统核心机制 基础(05)handler使用案例(Java)
5 AsyncTask机制
目前官方已经不推荐使用。AsyncTask机制主要用于执行一些不太长的异步任务。作为用来替代Thread+Handler的辅助类,AsyncTask可以很轻松地执行异步任务并更新UI,但由于context泄露,回调遗漏,configuration变化导致崩溃,平台差异性等原因,已被弃用(在API 30,Android 11版本中AsyncTask被正式废弃,在API 29,Android10.0 平台上已经不推荐使用)。
关于AsyncTask被弃用,说的比较全面的一篇文章是:AsyncTask将被弃用? CSDN博客,感兴趣的朋友可以更深入、更系统地了解原因。
总结
- 了解监听模式和回调模式的概念和使用方法。了解Configuration类及使用方法。
- 熟练使用监听器、了解常见的组件回调方法。深入理解Handler Message机制和内部原理(java层和native层),能够自己写出handler的常见使用案例。
- 了解AsyncTask 被弃用的根本原因。