目录
一,事件传递机制
1.事件传递机制的三个方法
(1)public boolean dispatchTouchEvent(MotionEvent event)
(2)public boolean onInterceptTouchEvent(MotionEvent event)
(3)public boolean onTouchEvent(MotionEvent event)
2.事件传递的流程
二,基于监听器的事件处理
1.基于监听器事件处理的三种实现方法
(1)匿名内部类实现
(2)内部类实现
(3)Activity直接实现接口
三,基于回调的事件处理
一,事件传递机制
在Android中,每触摸或点击一次,就会生成一个MotionEvent对象,代表一次触摸事件。Android的事件传递主要由三个方法来完成:
- 事件分发:dispatchTouchEvent();
- 事件拦截:onInterceptTouchEvent();
- 事件响应:onTouchEvent();
三种方法均返回boolean类型的值,表示是否解决事件。对于ViewGroup类,会进行事件分发,拦截,响应三种操作。但对于View来说,只有事件分发和响应,因为View没有子View,无法再向下传递,也就不需要事件拦截。
1.事件传递机制的三个方法
(1)public boolean dispatchTouchEvent(MotionEvent event)
事件分发方法,在Android中,事件首先到达Activity的顶层视图。在事件传递到Activity和View时,都会首先调用分发方法,如果子View的分发方法或当前View的响应方法能够成功解决事件,则返回true,表示当前事件已自我消化,否则返回false。
(2)public boolean onInterceptTouchEvent(MotionEvent event)
事件拦截方法,View调用拦截方法后,会接着调用自己的响应方法去处理事件,并且如果某个View调用了拦截方法,那么它的父类View就不会再调用拦截方法,子类View成功处理了事件,父类View同样不会调用拦截方法。
(3)public boolean onTouchEvent(MotionEvent event)
事件处理方法,成功处理返回true,否则返回false,子类View成功处理事件后,父类View不会再调用处理方法。
三种方法的关系用伪代码表示即为:
public boolean dispatchTouchEvent(MotionEvent event) {
boolean isSolve = false;
if(onInterceptTouchEvent(event)) {
isSolve = onTouchEvent(event);
} else {
isSolve = child.dispatchTouchEvent(event);
}
return isSolve;
}
2.事件传递的流程
上图为事件传递的流程,由图可知事件传递的顺序为:Activity->ViewGroup->View。事件首先到达Activity的顶层视图,然后调用Activity的事件分发方法,随后事件被传递给最顶层ViewGroup,并调用ViewGroup的分发方法,若ViewGroup拦截事件,则交给ViewGroup的处理方法。若不拦截则继续调用子View的分发方法。如果最终事件没有被任何View处理,则返回给Activity处理。
二,基于监听器的事件处理
Android提供了两种方式的事件处理机制:基于监听器的事件处理,基于回调的事件处理。
基于监听器的事件处理,涉及到三个核心对象:
- 事件源:事件发生的场所(View或ViewGroup);
- 事件:用户界面上发生的具体事件;
- 事件监听器:负责监听事件源所发生的事件,并作出响应;
基于监听器的原理就是把事件源和事件处理器(监听器)分离,让监听器去处理事件。实现方法就是为视图控件绑定特定的事件监听器。
1.基于监听器事件处理的三种实现方法
实现监听器处理事件,或者说是给View绑定监听器的三种常用方法为:
(1)匿名内部类实现
private Button btn_login = findViewById(R.id.btn_login);
btn_login.setOnClickListener( v ->{
//获取输入内容,用对话框提示出来
String name = edt_name.getText().toString(); //获取用户名
String passWard = edt_passward.getText().toString(); //获取密码
//对话框提示
Toast.makeText(
LoginActivity.this,
"登陆成功:" + name,
Toast.LENGTH_LONG).show();
//在哪显示 + 显示内容 + 显示时长
});
(2)内部类实现
使用匿名内部类需要一个控件实现一个匿名内部类,使用成员内部类,可实现代码复用
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_practice);
Button btn_login = findViewById(R.id.btn_login);
//2.内部类 便于代码复用 建议使用
Listener listener = new Listener();
btn_login.setOnClickListener(listener); //绑定监听器
btn_enroll.setOnClickListener(listener);
}
class Listener implements View.OnClickListener{
@Override
public void onClick(View v) {
//因为同一对象被多个方法调用,所以需要用id来区分
int id = v.getId();
if(id == R.id.btn_login){
String userName = edt_name.getText().toString();
String passward = edt_passward.getText().toString();
Toast.makeText(PracticeActivity.this, "欢迎登录!" + userName, Toast.LENGTH_SHORT).show();
}
else{
String userName = edt_name.getText().toString();
Toast.makeText(PracticeActivity.this, "注册成功!" + userName, Toast.LENGTH_SHORT).show();
//日志打印
Log.i("LoginActivity", "用户注册");
}
}
}
(3)Activity直接实现接口
直接让Activity实现监听器接口,实现监听方法,控件绑定监听器,通过this调用onClick方法。
public class PracticeActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_practice);
Button btn_login = findViewById(R.id.btn_login);
Button btn_enroll = findViewById(R.id.btn_enroll);
btn_enroll.setOnClickListener(this);
btn_login.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int id = v.getId();
if(id == R.id.btn_login){
String userName = edt_name.getText().toString();
String passward = edt_passward.getText().toString();
Toast.makeText(PracticeActivity.this, "欢迎登录!" + userName, Toast.LENGTH_SHORT).show();
}
else{
String userName = edt_name.getText().toString();
Toast.makeText(PracticeActivity.this, "注册成功!" + userName, Toast.LENGTH_SHORT).show();
//日志打印
Log.i("LoginActivity", "用户注册");
}
}
}
三,基于回调的事件处理
基于监听器的事件处理方法是让监听器去处理View组件产生的事件,在基于回调的事件处理机制中,事件源与事件监听器是统一的,或者说事件源本身就是事件监听器,事件源产生的事件由事件源自己去解决,解决的方法就是调用重写的回调方法。
Android为视图组件提供了一些事件处理的回调方法,例如View类的包含:
boolean onKeyDown(int keyCode, KeyEvent event) | 按下按键时触发 |
boolean onKeyLongPress(int keyCode, KeyEvent event) | 长按按键时触发 |
boolean onKeyShortcut(int keyCode, KeyEvent event) | 键盘快捷键事件发生时触发 |
boolean onKeyShortcut(int keyCode, KeyEvent event) | 松开按键时触发 |
boolean onTouchEvent(MotionEvent event) | 触发触摸屏事件时触发 |
boolean onTrackballEvent(MotionEvent event) | 触发轨迹球事件时触发 |
void onFocusChanged(boolean gainFocus, int direction, Rect previously FocusedRect) | 组件焦点发生改变时触发,该方法只能在View中重写 |
回调方法会接收一个MotionEvent对象,这个对象包含了触摸动作的具体信息,有以下几种常用类型:
ACTION_DOWN | 手指首次触摸到屏幕时触发,触发一次 |
ACTION_UP | 手指抬起时触发,触发一次 |
ACTION_MOVE | 手指在屏幕上滑动时触发,可以触发多次 |
ACTION_OUTSIDE | 触摸事件发生在视图边界时发生 |
基于回调的事件处理步骤为:(1)自定义视图类继承需要的View类,(2)重写回调方法,(3)在XML文件中使用自定义的视图组件。
1.自定义视图类继承需要的View类,并重写回调方法(在实现构造方法时,必须使用两个参数的构造方法,否则app会奔溃,原因尚不清楚),回调方法返回boolean类型的值,表示是否成功解决事件,成功返回true,否则返回false。
public class MyButton extends androidx.appcompat.widget.AppCompatButton {
//构造方法必须使用Context,AttributeSet两个参数的
public MyButton(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
//重写回调方法,写入自己的逻辑
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("btn", "btn1 onTouch");
switch (event.getAction()){
case MotionEvent.ACTION_UP:
Log.i("btn", "bnt1 Up");
break;
case MotionEvent.ACTION_DOWN:
Log.i("btn", "bnt1 Down");
break;
}
//返回是否成功解决事件
return true;
}
}
2.在XML文件中使用自定义的视图组件。
<com.example.practice.MyButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按钮"
tools:ignore="SpeakableTextPresentCheck">
</com.example.practice.MyButton>