Android 事件处理与手势
基于监听的事件处理方法与基于回调的事件处理方法的区别:
物理按键事件处理
按下而不松开则是onKeyDown()方法
松开手指就是onKeyUp()方法
长按不松开则为onKeyLongPress()
音量键对应的常量为KEYCODE_VOLUME_UP(声音增加)与KEYCODE_VOLUME_DOWN(声音减少)
电源键对应的常量为KWYCODE
返回键对应的常量为KEYCODE_BACK
主屏键对应的常量是KEYCODE_HOME
菜单键对应的常量是KEYCODE_MENU
双击两次退出键退出当前应用思路:
第一步:重写onKeyDown()方法来拦截用户单击后退按钮事件(在代码编辑区点击鼠标右键,选择Generate产生-Override Methods重写方法,选择onkeydown方法;第二步:创建退出方法exit()
if(keyCode==KeyEvent.KEYCODE_BACK){}//用if语句判断按下的是哪一个键
exit();//进行退出操作
return true;//屏蔽返回键
void为无返回值
public void exit(){}//创建退出方法exit()
if((System.currentTimeMillis()-exitTime)>2000){}在退出的方法中判断两次按下返回按键的时间差是否大于两秒,如果是就弹出消息提示框,如果否,就退出当前应用,当前系统时间-第一次按下返回按键的时间相减如果大于两秒
private long exitTime=0;定义一个全局变量,来记录第一次按下返回按键的时间,long为长整类型
定义全局变量:在public class MainActivity extends AppCompatActivity{的下方进行编写
System.currentTimeMillis()//获取当前系统时间
exitTime=System.currentTimeMillis();//将系统当前时间赋值给exitTime,初始化exitTime变量
用else来判断另一种情况,当前时间距离第一次按下返回按键的时间小于二秒
finish();//退出当前应用
System.exit(0);//完成退出应用的操作
代码如下:
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.widget.Toast;
public class MainActivity extends Activity {
private long exitTime=0;//定义一个exitTime变量
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode==KeyEvent.KEYCODE_BACK){//判断按下的键是否为返回键
exit();//实现exit方法
return true;
}
return super.onKeyDown(keyCode, event);
}
public void exit(){//重写exit方法
if((System.currentTimeMillis()-exitTime)>2000){//首先exittime值为0,肯定当前时间与exittime的时间差超过两秒。故会弹出消息提示框,然后当前的时间赋值给exittime,再进行判断,如果按下返回键,再调用exit方法,再进行判断,如果现在当前时间减去之前当前时间少于两秒,就执行退出按钮,如果没按下,就不会调用方法,如果超过两秒按下,再调调用exit方法,再进行判断,如果现在当前时间减去之前当前时间多于两秒,就会弹出消息提示框,简单来说就是exittime用当前的时间表示,前面的时间与后面的时间进行比较
Toast.makeText(MainActivity.this,"再按一次退出程序",Toast.LENGTH_SHORT).show();
exitTime=System.currentTimeMillis();}else{
finish();
System.exit(0);
}
}
}
触摸屏事件处理
单击事件
设置单击事件监听器的时候就要重写onclicklistener方法:OnClikListener接口含了一个onClick方法,接口的方法必须要实现,创建接口的实现类的对象的时候,就需要onclick方法,onclick方法是单击对象所触发的,一般情况下onclick方法中编写一些事件处理操作代码
setOnClickListener()传递的参数就是onclicklistener的接口的实现类对象,通过匿名内部类来实现
长按事件
需要长按至少两秒才会触发
setOnLongClickListener();//长按事件监听器
View.OnLongClickListener//接口的实现类对象
public static interface View.OnLongClickListener{//interface为接口,
public boolean onLongClick(Viewv);//声明一个onLongClick();方法,实现接口的时候要重写onLongClick方法,长按事件被触发就被调用
长按会弹出选择项方法:第一步:在MainActivity中重写onCreateContextMenu菜单,为菜单添加选项值(右键-Generate-Override Methods-onCreateContextMenu).。第二步:将长按事件注册到菜单中,并打开菜单
menu.add("")//对象.方法来为菜单添加选项值,
imageView.setOnLongClickListener(new View.OnLongClickListener())//设定一个长按事件监听器
在重写的onLongClick方法中进行编写
registerForContextMenu(v);将长按事件注册到菜单中,参数为上面传的v
openContextMenu(v);//打开菜单
主程序:
主程序:package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView imageView = findViewById(R.id.image);
imageView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
registerForContextMenu(v);
openContextMenu(v);
return false;
}
});
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.add("查看");
menu.add("删除");
}
}
布局管理器:
<ImageView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:src="@drawable/photo"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
触摸事件
setOnTouchListener()//触摸事件监听器
view.OnTouchListener//接口实现类对象
Public interface View.OnTouchListener{
public abstract boolean onTouch(View v,MotionEvent event);
}
实现OnTouchListener接口的话要重写onTouch方法,在onTouch中编写相关的代码,创建触摸事件监听器的时候创建MotionEvent事件对象
MotionEvent:保存发生触摸的位置时间等细节信息,保存触摸的xy坐标
java-com.xxx-右键-new-java class//创建一个自定义的view,用于绘制帽子
类(class)继承(extend)view,出现下划线,在下划线点击,按ait加回车,创建构造方法(create constructor matching super ),选择第一个带一个参数的构造方法(view(Context)))
public float bitmapX//设置一个全局变量,储存x坐标
public float bitmapY//设置一个全局变量,储存y坐标
在构造方法中编写帽子位置的初始值
bitmapX=65;//设定x位置的初始值
bitmapY=0;//设定y坐标的初始值
重写绘画方法(onDraw(canvas:Canvas):void),里面绘制帽子
Paint paint =new Paint();
Bitmap bitmap= BitmapFactory.decodeResource(this.getResources(),R.drawable.hat) //导入帽子图片并创建bitmap对象
canvas.drawBitmap(bitmap,bitmapX,bitmapY,paint);//为画布设置xy和画笔工具
if(bitmap.isReccycled()){
bitmap.recycle();
} //判断图片是否回收,没有回收图片的话,强制回收图片
第一步:创建自定义View,用来绘制帽子的,第二步:创建并实例化帽子类的一个对象,并为帽子添加触摸事件监听器,在重写的触摸方法中根据触摸的位置重绘帽子,第三步:把帽子添加到布局管理器中
HatView hat=new HatView(MainActivity.this);//创建帽子对象,参数是MainActivity.this
hat.setOnTouchListener(new View.OnTouchListener(){
@Override
public boolean onTouch(View v,MotionEvent event){
return ture;
}
})//为帽子设置触摸事件监听器,在重写的onTouch方法中编写改变帽子位置的代码
hat.bitmapX=event.getX()-80;//获取到事件的x坐标赋值给帽子的x坐标,而且要减去帽子一半的宽度,这样帽子才是正中的位置,根据我们手指进行移动
hat.bitmapY=event.getY()-80;//获取到事件的y坐标赋值给帽子的y坐标,而且要减去帽子一半的宽度,这样帽子才是正中的位置,根据我们手指进行移动
hat.invalidate();//重绘帽子组件
RelativeLayout rl=(RelativeLayout)findViewById(R.id.relativeLayout);//获取相对布局管理器
rl.addView(hat);//对象.方法来添加,参数为添加的内容
实例:图片随指尖移动:
主程序
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final HatView hat =new HatView(MainActivity.this);
hat.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
hat.bitmapX=event.getX()-80;
hat.bitmapY=event.getY()-80;
hat.invalidate();
return true;
}
});
RelativeLayout relativeLayout =findViewById(R.id.relativeLayout);
relativeLayout.addView(hat);
}
}
布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/relativeLayout"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/man"
tools:context=".MainActivity"></RelativeLayout>
帽子视图的类
package com.example.myapplication;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
public class HatView extends View {
public float bitmapX;
public float bitmapY;
public HatView(Context context) {
super(context);
bitmapX=65;
bitmapY=0;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint =new Paint();
Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(),R.drawable.hat);
canvas.drawBitmap(bitmap,bitmapX,bitmapY,paint);
if (bitmap.isRecycled()){
bitmap.recycle();
}
}
}
单击事件与触摸事件的区别
先触发触摸事件,再触发单击事件,如果ontouch事件没有完全消费掉事件,再触发单击事件
消费事件:一次ui操作是否相应,如果重写的方法返回true,那么就是消费了事件。如果返回false,就没消费事件,交给后面的事情进行处理
Log.i("","");//输出日志信息,如果出现错误就按alt+回车导入log类
if(event.getAction()==MotionEvent.ACTION_DOWN){}//判断获取事件动作(event.getAction())是否等于手指按下
else if(){}//符合另一个条件时进行执行
event.getAction()==MotionEvent.ACTION_UP//判断获取事件动作,是否等于手指抬起
实例
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button =(Button) findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("onClick","单击事件");
}
});
button.setOnTouchListener(new View.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN ){
Log.i("onTouch","按下");
}else if(event.getAction()==MotionEvent.ACTION_UP){
Log.i("onTouch","抬起");
}
return true;
}
});
}
}
布局
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="254dp"
android:text="触摸事件和单击事件的区别"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
}
单击事件触发一个动作,触摸事件触发两个动作(按下和抬起)
手势检测
GestureDetector,创建类的对象的时候需要创建GestureDetector类的OngestureListener接口的实例,一个接口代表一个监听器,需要重写onDown(触摸时按下触发) onFling(当手指在触摸屏时拖过时触发) onLongPress(当用户在屏幕长按时触发) onScroll onShowPress onSingleTapUp(当用户的手指在触摸屏轻击时触发) 的方法
布局管理器中中的ViewFlipper,是用动画控制多个组件的切换效果
第一步:让mainacitvity实现gesturedetector ongesturelistener接口,并实现其所有方法
public cldss MainActivityextends AppCompatActivity implements GestureDetector.OnGestureListener,在红色的灯泡中点击implement methods实现方法
Animation[] animation= new Animation[4];//设置动画数组,为viewflipper组件指定切换动画,数组的长度为4
final intdistance=50;//定义一个整型的变量,用于记录手势两点动作的最小距离
private int[] images =new int[]{R.drawble.img01}//将图片资源导入到数组中,记录要显示的图片资源
第二步,定义一个全局的手势检测器
定义一个gesturedetector类的对象
detector=new GestureDetector(MainActivity.this,this);自定义一个手势检测器,并初始化
第三步:将要显示的图片加载到ViewFLipper中,并且初始化动画数组
ViewFlipper flipper;定义一个全局的viewflipper对象,不进行初始化,方便之后的调用
flipper=(ViewFlipper)findViewById(R.id.flipper);//获取到布局管理器中的flipper组件
for(int i=0;i<images.length;i++){}//用for循环加载图片数组的图片
ImageView imageView=new ImageView(this);//创建对象并实例化imageview
imageView.setImageResource(images[i]);//指定imageview要显示的图片,参数是图片数组元素
flipper.addView(imageView);//设置flipper要加载的图片,设置flipper加载imageview里面的内容
animation[0]=AnimationUtils.loadAnimation(this.R.anim.slide_in_left )//初始化数组元素,并加载动画资源文件(loadAnimation),第一个参数是指定内容,第二个参数是指定动画资源
第四步:在onFling()方法中通过触摸事件的x左边判断是向左滑动还是向右滑动,并且为其设置动画
if(e1.getX()-e2.getX()>distance){}//判断是否从右向左进行滑动,若第一次触摸获取到的x坐标与第二次触摸获取到的x坐标的差值是自己设置的定值,则可以认定是从右向左滑动
flipper.setInAnimation(animation[2]);//为flipper设置进入动画,设置成第三个数组动画元素(淡入)
flipper.setInAnimation(animation[2]);//为flipper设置进入动画,设置成第三个数组动画元素(淡出)
flipper.showPrevious();显示上一张图片
else if(e2.getX()-e1.getX()>distance){}//判断是否从左向右进行滑动,若第二次触摸获取到的x坐标与第一次触摸获取到的x坐标的差值是自己设置的定值,则可以认定是从左向右滑动
flipper.showNext();显示下一张图片
第五步:将activity上的触摸事件交给gesturedetector处理
重写onTouchEvent 的方法,将super改成detector(手势)
手势添加
打开avd 打开菜单。打开Gestures Bui…
Reload为导入手势
点击Add gesture 添加手势。输入名称。在下面黑色空白处绘制手势,按done 来完成添加绘制的手势
打开android device monitor 双击 logcat打开面板
选中avd,在右边上面的面板选择File Explorer(文件管理器),找到storage,展开,展开由数字和英文组成的文件夹,选择gestures,选择pull a file from the device(红色减号的左边),选择要保存的位置,即可导出自定义手势
模拟微信手写输入实现识别用户输入手势的功能
在android studio-raw-中创建文件夹:在res中右键-new-Directory
将自定义的手势复制粘贴到raw目录
第一步:在布局文件中添加一个编辑框(EditText)和一个手势组件
<android.gesture.GestureOverlayView> 内容 </android.gesture.GestureOverlayView>
android:gestureStrokeType=“multiple”>//设置手指设置为多指设置
第二步:让MainActivity实现GestureOverlayView.onGesturePerformedListener接口,并重写onGesturePerformed()方法
public class MainActivity extends Activity implements GestureOverlayView.OnGesturePerformedListener{}//MainActivity实现GestureOverlayView.onGesturePerformedListener接口,在红色小灯泡中选择Implement methods来实现方法-ok
第三步:加载raw文件夹中的手势文件,如果加载失败退出应用
在onCreate方法上面定义的变量为全局变量
private GestureLibrary library//定义一个私有手势变量
private EditText editText;//定义一个私有编辑框的变量
library=GestureLibraries.fromRawResource(MainActivity.this,R.raw.gestures)//对象。方法来加载raw文件夹中的手势文件,第一个参数是上下文对象,第二个参数是要加载的手势资源,然后赋值给library
if(!library.load()){}//判断加载失败
第四步获得GestureOverlayView组件,并且为其设置属值和事件监听器
获得GestureOverlayView组件//GestureOverlayView gestureOverlayView=(GestureOverlayView)findViewById(R.id.gesture);//获得GestureOverlayView组件
gestureOverlayView.setGestureColor(color.BLACK);//设置手势的颜色为黑色
gestureOverlayView.setFadeOffset(1000);//设置淡出屏幕的间隔事件,为一秒
gestureOverlayView.addOnGesturePerformedListener(this);//为手势组件( gestureOverlayView),添加手势监听器
第五步:在重写的onGesturePerformed方法中获得最佳匹配进行显示,并更新编辑框
ArrayListgestures=library.recognize(gesture);//获得全部的运算结果
int index=0;//保存当前预测的索引号
double score=0.0;//保存当前预测的得分
for(int i=0;i<gestures.size();i++){}//通过for循环来获得匹配最佳匹配结果
Prediction result=gestures.get(i); //获得第一个运算结果
if(result.score>score){}//判断预测的结果是否大于当前的预测得分
index=i;//索引号就等于循环的次数
score=result.score;让分数等于新获得的运算结果
String text=editText.getText().toString();//获取到编辑框原有的内容,转化为字符串资源(.toString),传递给text变量
text+=gestures.get(index).name//将获取到的最佳匹配结果
(gestures.get(index).name)与之前的内容(text)进行连接
editText.setText(text);//更新编辑框内容,设置编辑框的内容为text,为最佳的匹配结果
打开manifests-AndroidManifest.xml,设置应用的主题
android:theme="@style/Theme.AppCompat.Ligth.DarkActionBar"//设置主题为浅色背景,深色Actionbar