Android的事件机制:触屏,按键,图片拖动,点击两次返回按键才退出

事件机制

简介

提出问题:
	Android是如何响应对其中的某个视图的触控操作呢?
	Android是如何响应对手机的按键的操作呢?

解决方案:
	MotionEvent机制(触屏)
	KeyEvent机制(按键)

触屏

触屏的操作:
	最基本的操作类型:
	down : 手指按下
	move : 手指在屏幕上移动
	up : 手指从屏幕上离开
触屏操作的顺序:  
	down-->move-->move-->-->up
对屏幕的任何一个操作, 系统都会创建一个MotionEvent对象来对应这个操作
点击和长按也类似,就是down,然后移动的位置还是在整个视图对象的范围

触屏API

MotionEvent : 触屏事件
	int ACTION_DOWN=0 : 代表down
	Int ACTION_MOVE=2 ; 代表move
	Int ACTION_UP=1 : 代表up
	getAction() :  得到事件类型值
	getX() : 得到事件发生的x轴坐标(相对于当前视图)
	getRawX() :得到事件发生的x轴坐标(相对于屏幕左顶点)
	getY() : 得到事件发生的y轴坐标(相对于当前视图)
	getRawY() :得到事件发生的y轴坐标(相对于屏幕左顶点)
Activity
	boolean dispatchTouchEvent(MotionEvent event) : 分发事件
	boolean onTouchEvent(MotionEvent event) : 处理事件的回调
View
	boolean dispatchTouchEvent(MotionEvent event) : 分发事件
	boolean onTouchEvent(MotionEvent event) : 处理事件的回调方法
	void setOnTouchListener(OnTouchListener l) : 设置事件监听器
	void setOnClickListener(OnClickListener l) : 设置点击监听
	void setOnLongClickListener(OnLongClickListener l) : 设置长按监听
	void setOnCreateContextMenuListener(OnCreateContextMenuListener l) : 用于创建菜单
ViewGroup
	boolean dispatchTouchEvent(MotionEvent ev) : 分发事件
	boolean onInterceptTouchEvent(MotionEvent ev) : 拦截事件的回调方法

具体的理解

首先产生这些事件的最终缘由还是我们手指触摸了屏幕
而且触摸屏幕只是触摸当前的Activity
如果从最底层开始说起,那就是屏幕了,首先现在我们手机使用的是电容屏幕
我们触摸屏幕就会有电压变化,Android的底层就会根据这个变化
产生事件对象,这个对象首先从底层驱动一直向上传,最终我们关注的是
这个事件传到Activity,这里就要仔细看下了
首先Android的视图的层次管理就是父视图对象只是管理子视图对象
子视图的子视图对象父视图不管理,只是将它交给子视图对象管理
由上面的API可以知道Activity有两个回调方法
dispatchTouchEvent(分发事件)
onTouchEvent(处理事件)
那么事件已经传输到Activity这里了,那么Activity就开始分发事件
如果这个事件是因为触摸Activity里面的某个子视图对象产生的
就会分发这个事件到那个子视图对象,如果还是因为触发子视图的子视图
产生的,就继续这样分发,一直分发.
分发到最后的视图对象,视图对象也会进行分发,但是发现没有子视图分发了
就看看是否要处理了,处理有两种方式:
视图对象的回调方法
	dispatchTouchEvent(分发事件)
	onTouchEvent(处理事件)
监听器对象的回调方法:
	View.OnTouchListener()里面的onTouch(View v, MotionEvent event)
监听器的方法会先执行
监听器看看要不要处理
然后返回值boolean就是要不要消费这个事件,
这个事件只能被消费一次,如果有人消费了这个事件
那么这个事件就不会继续向上面传送回去进行处理了
如果一直没有人来处理和消费这个事件,最后Activity就会处理和消费它
还有得注意的是:
	如果传入的ACTION_DOWN事件到子视图,但是子视图没有处理和消费这个事件
	而是返回到父视图这里进行处理和消费
	那么以后有ACTION_MOVE,ACTION_UP事件,这个父视图都不会将这个
	事件传送到子视图里面,而是自己进行处理,
	因为连ACTION_DOWN事件都没有处理和消费,其他的事件就不会用到或者说没有

练习图片拖动

功能描述:
	通过手指移动来拖动图片
	控制图片不能超出屏幕显示区域
int getLeft()
	得到当前视图左顶点相对父视图的X轴坐标
int getTop()
	得到当前视图左顶点相对父视图的Y轴坐标
int getRight()
	得到当前视图右下角点相对父视图的X轴坐标
int getBottom()
	得到当前视图右下角点相对父视图的Y轴坐标
layout(int left, int top, int right, int bottom) :
	动态指定当前视图在父视图中的定位, 参数为相对父视图的坐标
ViewParent getParent() :
	得到当前View的父视图对象
<RelativeLayout 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"
    tools:context="${relativePackage}.${activityClass}" >

    <ImageView
        android:id="@+id/iv_main"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/btn_star"/>
</RelativeLayout>
package com.jane.motionevent;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;

public class MainActivity extends Activity implements OnTouchListener 
{
	private ImageView iv_main;
	private RelativeLayout parentView;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) 
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		iv_main = (ImageView) findViewById(R.id.iv_main);
		
		parentView = (RelativeLayout) iv_main.getParent();
		/*
		int right = parentView.getRight(); //0
		int bottom = parentView.getBottom();   //0
		Toast.makeText(this, right+"---"+bottom, 1).show();
		*/
		//设置touch监听 
		iv_main.setOnTouchListener(this);
	}
	
	private int lastX;
	private int lastY;
	private int maxRight;
	private int maxBottom;
	
	@Override
	public boolean onTouch(View v, MotionEvent event) 
	{
		//得到事件的坐标
		int eventX = (int) event.getRawX();
		int eventY = (int) event.getRawY();
		switch (event.getAction()) 
		{
			case MotionEvent.ACTION_DOWN:
				//得到父视图的right/bottom
				if(maxRight==0) 
				{
					//保证只赋一次值
					maxRight = parentView.getRight();
					maxBottom = parentView.getBottom();
				}
				
				//第一次记录lastX/lastY
				lastX =eventX;
				lastY = eventY;
				break;
			case MotionEvent.ACTION_MOVE:
				//计算事件的偏移
				int dx = eventX-lastX;
				int dy = eventY-lastY;
				//根据事件的偏移来移动imageView
				int left = iv_main.getLeft()+dx;
				int top = iv_main.getTop()+dy;
				int right = iv_main.getRight()+dx;
				int bottom = iv_main.getBottom()+dy;
				
				//限制left  >=0
				if (left < 0)
				{
					right += -left;
					left = 0;
				}
				// 限制top
				if (top < 0)
				{
					bottom += -top;
					top = 0;
				}
				// 限制right <=maxRight
				if (right > maxRight)
				{
					left -= right - maxRight;
					right = maxRight;
				}
				// 限制bottom <=maxBottom
				if (bottom > maxBottom)
				{
					top -= bottom - maxBottom;
					bottom = maxBottom;
				}
	
				iv_main.layout(left, top, right, bottom);
				//再次记录lastX/lastY
				lastX = eventX;
				lastY = eventY;
				break;
	
			default:
				break;
		}
		return true;//所有的motionEvent都交给imageView处理
	}
}

按键

操作的基本类型
	down : 手指按下
	up : 手指从按键上离开
按键操作的顺序:  down-->down-->down-->-->up
对按键的任何一个操作, 系统都会创建一个KeyEvent对象来对应这个操作
按键的长按监听: down之后一定时间还没有up时会触发长按监听回调

按键API

KeyEvent  
	int ACTION_DOWN = 0  : 标识down的常量
	int ACTION_UP = 1 : 标识up的常量
	int getAction() : 得到事件类型
	int getKeyCode() : 得到按键的keycode(唯一标识)
	startTracking() : 追踪事件, 用于长按监听
Activity
	boolean dispatchKeyEvent(KeyEvent event) : 分发事件
	boolean onKeyDown(int keyCode, KeyEvent event) : 按下按键的回调
	boolean onKeyUp(int keyCode, KeyEvent event) : 松开按键的回调
	boolean onKeyLongPress(int keyCode, KeyEvent event) : 长按按键的回调

例子

package com.jane.event;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;

public class KeyEventTestActivity extends Activity 
{
	@Override
	protected void onCreate(Bundle savedInstanceState) 
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_key);
	}
	
	@Override
	public boolean dispatchKeyEvent(KeyEvent event) 
	{
		Log.e("TAG", "dispatchKeyEvent() action="+event.getAction()+" code="+event.getKeyCode());
		return super.dispatchKeyEvent(event);
	}
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) 
	{
		Log.e("TAG", "onKeyDown() action="+event.getAction()+" code="+event.getKeyCode());
		/*
		 * 追踪事件, 用于长按监听
		 * 如果是black按键,默认是可以接收到长按的事件的
		 * 但是如果其他的按键想有长按事件,就必须event.startTracking();
		 */
		event.startTracking();
		
		return true; //返回必须是true
	}
	
	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) 
	{
		Log.e("TAG", "onKeyUp() action="+event.getAction()+" code="+event.getKeyCode());
		
		return super.onKeyUp(keyCode, event); //不执行super就可以不退出界面
		//return true;
	}
	
	@Override
	public boolean onKeyLongPress(int keyCode, KeyEvent event) 
	{
		Log.i("TAG", "onKeyLongPress() action="+event.getAction()+" code="+event.getKeyCode());
		return super.onKeyLongPress(keyCode, event);
	}
}

点击两次返回按键才退出

第一次按back键, 只Toast提示, 不退出应用
2S内连按两次back键才退出应用
技术点:
	KeyEvent处理
	延迟消息的处理

先做个简单点的,先显示dialog提示的

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) 
	{
		//监听back键
		if(event.getKeyCode()==KeyEvent.KEYCODE_BACK) 
		{
			//显示确定的dialog
			new AlertDialog.Builder(this)
				.setMessage("你确定退出吗?")
				.setPositiveButton("退出", new DialogInterface.OnClickListener() 
				{
					@Override
					public void onClick(DialogInterface dialog, int which) 
					{
						//退出
						finish();
					}
				})
				.setNegativeButton("不退出", null)
				.show();
			return true;//不会退出了
		}
		return super.onKeyUp(keyCode, event);
	}

再实现之前的功能

package com.jane.keyevent;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
import android.widget.Toast;

public class MainActivity extends Activity
{
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	private boolean exit = false;// 标识是否可以退出
	private Handler handler = new Handler()
	{
		public void handleMessage(android.os.Message msg)
		{
			if (msg.what == 1)
			{
				exit = false;
			}
		}
	};

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event)
	{
		if (event.getKeyCode() == KeyEvent.KEYCODE_BACK)
		{
			if (!exit)
			{
				exit = true;
				Toast.makeText(this, "再按一次就退出应用", 0).show();
				// 发消息延迟2s将exit=false
				handler.sendEmptyMessageDelayed(1, 2000);
				return true;// 不退出
			}
		}
		return super.onKeyUp(keyCode, event);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ReflectMirroring

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值