Android 手势(Gesture)
2022-02-18 本文已影响0人
gaookey
image.png
public class MainActivity extends AppCompatActivity implements GestureDetector.OnGestureListener {
// 定义手势检测器变量
private GestureDetector detector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 创建手势检测器
detector = new GestureDetector(this, this);
}
// 将该Activity上的触碰事件交给GestureDetector处理
@Override
public boolean onTouchEvent(MotionEvent me) {
return detector.onTouchEvent(me);
}
// 当触碰事件按下时触发该方法
@Override
public boolean onDown(MotionEvent e) {
Toast.makeText(this, "onDown",
Toast.LENGTH_SHORT).show();
return false;
}
// 当用户手指在触摸屏上按下,而且还未移动和松开时触发该方法
@Override
public void onShowPress(MotionEvent e) {
Toast.makeText(this, "onShowPress",
Toast.LENGTH_SHORT).show();
}
// 用户手指在触摸屏上的轻击事件将会触发该方法
@Override
public boolean onSingleTapUp(MotionEvent e) {
Toast.makeText(this, "onSingleTapUp",
Toast.LENGTH_SHORT).show();
return false;
}
// 当用户手指在屏幕上“滚动”时触发该方法。
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Toast.makeText(this, "onScroll",
Toast.LENGTH_SHORT).show();
return false;
}
// 当用户手指在屏幕上长按时触发该方法
@Override
public void onLongPress(MotionEvent e) {
Toast.makeText(this, "onLongPress",
Toast.LENGTH_SHORT).show();
}
// 当用户手指在触摸屏上“拖过”时触发该方法。其中 velocityX、velocityY 代表“拖过”动作在横向、纵向上的速度。
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Toast.makeText(this, "onFling",
Toast.LENGTH_SHORT).show();
return false;
}
}
实例:通过手势缩放图片
public class MainActivity extends AppCompatActivity {
// 定义手势检测器变量
private GestureDetector detector;
private ImageView imageView;
// 初始的图片资源
private Bitmap bitmap;
// 定义图片的宽、高
private int width;
private int height;
// 记录当前的缩放比
private float currentScale = 1.0f;
// 控制图片缩放的Matrix对象
private Matrix matrix;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 创建手势检测器
detector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) // ②
{
float vx = velocityX > 4000 ? 4000f : velocityX;
vx = velocityX < -4000 ? -4000f : velocityX;
// 根据手势的速度来计算缩放比,如果vx>0,则放大图片;否则缩小图片
currentScale += currentScale * vx / 4000.0f;
// 保证currentScale不会等于0
currentScale = currentScale > 0.01 ? currentScale : 0.01f;
// 重置Matrix
matrix.reset();
// 缩放Matrix
matrix.setScale(currentScale, currentScale, 160f, 200f);
BitmapDrawable tmp = (BitmapDrawable) imageView.getDrawable();
// 如果图片还未回收,先强制回收该图片
if (!tmp.getBitmap().isRecycled()) // ①
{
tmp.getBitmap().recycle();
}
// 根据原始位图和Matrix创建新图片
Bitmap bitmap2 = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
// 显示新的位图
imageView.setImageBitmap(bitmap2);
return true;
}
});
imageView = findViewById(R.id.show);
matrix = new Matrix();
// 获取被缩放的源图片
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.flower);
// 获得位图宽
width = bitmap.getWidth();
// 获得位图高
height = bitmap.getHeight();
// 设置ImageView初始化时显示的图片
imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.flower));
}
@Override
public boolean onTouchEvent(MotionEvent me) {
// 将该Activity上的触碰事件交给GestureDetector处理
return detector.onTouchEvent(me);
}
}
实例:通过多点触碰缩放 TextView
public class TouchZoomView extends androidx.appcompat.widget.AppCompatTextView {
// 保存TextView当前的字体大小
private float textSize;
// 保存两个手指前一次的距离
private float prevDist;
public TouchZoomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public TouchZoomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TouchZoomView(Context context) {
super(context);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 只处理触碰点大于等于2(必须是多点触碰)的情形
if (event.getPointerCount() >= 2) {
// 获取该TextView默认的字体大小
if (textSize == 0) {
textSize = this.getTextSize();
}
// 对于多点触碰事件,需要使用getActionMasked来获取触摸事件类型
switch (event.getActionMasked()) {
// 处理手指按下的事件
case MotionEvent.ACTION_POINTER_DOWN:
// 计算两个手指之间的距离
prevDist = calSpace(event);
break;
// 处理手指移动的事件
case MotionEvent.ACTION_MOVE:
// 实时计算两个手指之间的距离
float curVDist = calSpace(event);
// 根据两个手指之间的距离计算缩放比
zoom(curVDist / prevDist);
// 为下一次移动的缩放做准备
prevDist = curVDist;
break;
}
}
return true;
}
// 缩放字体
private void zoom(float f) {
textSize *= f;
this.setTextSize(px2sp(getContext(), textSize));
}
// 将px值转换为sp值,保证文字大小不变
public static int px2sp(Context context, float pxValue) {
float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
// 计算两个手指之间的距离
private float calSpace(MotionEvent event) {
// 获取两个点之间X坐标的差值
float x = event.getX(0) - event.getX(1);
// 获取两个点之间Y坐标的差值
float y = event.getY(0) - event.getY(1);
// 计算两点距离
return (float) Math.sqrt(x * x + y * y);
}
}
实例:通过多点触碰缩放图片
public class TouchZoomImageView extends View {
// 定义手势检测器变量
private GestureDetector detector;
// 保存该组件所绘制的位图的变量
private Bitmap mBitmap;
// 定义对位图进行变换的Matrix
private Matrix matrix = new Matrix();
private float prevDist;
private float totalScaleRadio = 1.0f;
private float totalTranslateX = 0.0f;
private float totalTranslateY = 0.0f;
// 为TouchZoomImageView定义不同场景下的构造器
public TouchZoomImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initDetector();
}
public TouchZoomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initDetector();
}
public TouchZoomImageView(Context context) {
super(context);
initDetector();
}
public void initDetector() {
detector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
// 根据手势的滑动距离来计算图片的位移距离
float translateX = event2.getX() - event1.getX();
float translateY = event2.getY() - event1.getY();
totalTranslateX += translateX;
totalTranslateY += translateY;
postInvalidate();
return false;
}
});
}
// 根据传入的位图资源ID来设置位图
public void setImage(int resourceId) {
// 根据位图资源ID来解析图片
Bitmap bm = BitmapFactory.decodeResource(getResources(),
resourceId);
setImage(bm);
}
public void setImage(Bitmap bm) {
this.mBitmap = bm;
// 当传递过位图来之后,对位图进行初始化操作
postInvalidate();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 获取当前触摸点的个数,如果触碰点大于2,说明是缩放行为
if (event.getPointerCount() >= 2) {
// 使用getActionMasked来处理多点触碰事件
switch (event.getActionMasked()) { // ①
// 处理手指按下的事件
case MotionEvent.ACTION_POINTER_DOWN:
// 计算两个手指的距离
prevDist = calSpace(event);
break;
// 处理手指移动的事件
case MotionEvent.ACTION_MOVE:
float curDist = calSpace(event);
// 计算出当前的缩放值
float scaleRatio = curDist / prevDist;
totalScaleRadio *= scaleRatio;
// 调用onDraw方法,重新绘制界面
postInvalidate();
// 准备处理下一次缩放行为
prevDist = curDist;
break;
}
}
// 对于单点触碰,触碰事件交给GestureDetector处理
else {
detector.onTouchEvent(event); // ②
}
return true;
}
@Override
public void onDraw(Canvas canvas) {
matrix.reset();
// 处理缩放
matrix.postScale(totalScaleRadio, totalScaleRadio);
// 处理位移
matrix.postTranslate(totalTranslateX, totalTranslateY);
canvas.drawBitmap(mBitmap, matrix, null);
}
// 计算两个手指之间的距离
private float calSpace(MotionEvent event) {
// 获取两个点之间X坐标的差值
float x = event.getX(0) - event.getX(1);
// 获取两个点之间Y坐标的差值
float y = event.getY(0) - event.getY(1);
// 计算两点距离
return (float) Math.sqrt(x * x + y * y);
}
}
通过手势实现翻页效果
public class MainActivity extends AppCompatActivity {
// ViewFlipper实例
private ViewFlipper flipper;
// 定义手势检测器变量
private GestureDetector detector;
// 定义一个动画数组,用于为ViewFlipper指定切换动画效果
private Animation[] animations = new Animation[4];
// 定义手势动作两点之间的最小距离
private float flipDistance = 0f;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
flipDistance = getResources().getDimension(R.dimen.flip_distance);
// 创建手势检测器
detector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
// 如果第一个触点事件的X坐标大于第二个触点事件的X坐标超过flipDistance
// 也就是手势从右向左滑
if (event1.getX() - event2.getX() > flipDistance) {
// 为flipper设置切换的动画效果
flipper.setInAnimation(animations[0]);
flipper.setOutAnimation(animations[1]);
flipper.showPrevious();
return true;
}
// 如果第二个触点事件的X坐标大于第一个触点事件的X坐标超过flipDistance
// 也就是手势从左向右滑
else if (event2.getX() - event1.getX() > flipDistance) {
// 为flipper设置切换的动画效果
flipper.setInAnimation(animations[2]);
flipper.setOutAnimation(animations[3]);
flipper.showNext();
return true;
}
return false;
}
});
// 获得ViewFlipper实例
flipper = this.findViewById(R.id.flipper);
// 为ViewFlipper添加6个ImageView组件
flipper.addView(addImageView(R.drawable.java));
flipper.addView(addImageView(R.drawable.javaee));
flipper.addView(addImageView(R.drawable.ajax));
flipper.addView(addImageView(R.drawable.android));
flipper.addView(addImageView(R.drawable.html));
flipper.addView(addImageView(R.drawable.swift));
// 初始化Animation数组
animations[0] = AnimationUtils.loadAnimation(this, R.anim.left_in);
animations[1] = AnimationUtils.loadAnimation(this, R.anim.left_out);
animations[2] = AnimationUtils.loadAnimation(this, R.anim.right_in);
animations[3] = AnimationUtils.loadAnimation(this, R.anim.right_out);
}
// 定义添加ImageView的工具方法
private View addImageView(int resId) {
ImageView imageView = new ImageView(this);
imageView.setImageResource(resId);
imageView.setScaleType(ImageView.ScaleType.CENTER);
return imageView;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 将该Activity上的触碰事件交给GestureDetector处理
return detector.onTouchEvent(event);
}
}
anim/left_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="5000"
android:fromXDelta="100%p"
android:toXDelta="0" />
<alpha
android:duration="5000"
android:fromAlpha="0.1"
android:toAlpha="1.0" />
</set>
anim/left_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="5000"
android:fromXDelta="0"
android:toXDelta="-100%p" />
<alpha
android:duration="5000"
android:fromAlpha="0.1"
android:toAlpha="1.0" />
</set>
anim/right_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromXDelta="-100%p"
android:toXDelta="0" />
<alpha
android:duration="500"
android:fromAlpha="0.1"
android:toAlpha="1.0" />
</set>
anim/right_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromXDelta="0"
android:toXDelta="100%p" />
<alpha
android:duration="500"
android:fromAlpha="0.1"
android:toAlpha="1.0" />
</set>
增加手势
AndroidManifest.xml
添加权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
MainActivity
public class MainActivity extends AppCompatActivity {
private GestureOverlayView gestureView;
private Gesture gesture;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取手势编辑视图
gestureView = findViewById(R.id.gesture);
// 设置手势的绘制颜色
gestureView.setGestureColor(Color.RED);
// 设置手势的绘制宽度
gestureView.setGestureStrokeWidth(4f);
// 为gesture的手势完成事件绑定事件监听器
gestureView.addOnGesturePerformedListener((source, gesture) -> {
this.gesture = gesture;
// 请求访问写入SD卡的权限
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0x123);
});
}
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
// 如果确认允许访问
if (requestCode == 0x123 && grantResults != null && grantResults[0] == 0) {
// 加载dialog_save.xml界面布局代表的视图
LinearLayout saveDialog = (LinearLayout) getLayoutInflater().inflate(R.layout.dialog_save, null);
// 获取saveDialog里的show组件
ImageView imageView = saveDialog.findViewById(R.id.show);
// 获取saveDialog里的gesture_name组件
EditText gestureName = saveDialog.findViewById(R.id.gesture_name);
// 根据Gesture包含的手势创建一个位图
Bitmap bitmap = gesture.toBitmap(128, 128, 10, -0x10000);
imageView.setImageBitmap(bitmap);
// 使用对话框显示saveDialog组件
new AlertDialog.Builder(MainActivity.this).setView(saveDialog)
.setPositiveButton(R.string.bn_save, (dialog, which) -> {
// 获取指定文件对应的手势库
GestureLibrary gestureLib = GestureLibraries.fromFile(
Environment.getExternalStorageDirectory().getPath() + "/mygestures");
// 添加手势
gestureLib.addGesture(gestureName.getText().toString(), gesture);
// 保存手势库
gestureLib.save();
}).setNegativeButton(R.string.bn_cancel, null).show();
}
}
}
layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gestureTip" />
<!-- 使用手势绘制组件 -->
<android.gesture.GestureOverlayView
android:id="@+id/gesture"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gestureStrokeType="multiple" />
</LinearLayout>
layout/dialog_save.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="@string/gesture_name" />
<!-- 定义一个文本框来让用户输入手势名 -->
<EditText
android:id="@+id/gesture_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp" />
</LinearLayout>
<!-- 定义一个图片框来显示手势 -->
<ImageView
android:id="@+id/show"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_marginTop="10dp" />
</LinearLayout>
image.png
识别用户手势
public class MainActivity extends AppCompatActivity {
// 定义手势编辑组件
private GestureOverlayView gestureView;
// 记录手机上已有的手势库
private GestureLibrary gestureLibrary;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0x123);
}
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
// 如果用户授权访问SD卡
if (requestCode == 0x123 && grantResults != null
&& grantResults[0] == 0) {
// 读取上一个程序所创建的手势库
gestureLibrary = GestureLibraries.fromFile(
Environment.getExternalStorageDirectory().getPath() + "/mygestures");
if (gestureLibrary.load()) {
Toast.makeText(MainActivity.this, "手势文件装载成功!",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "手势文件装载失败!",
Toast.LENGTH_SHORT).show();
}
// 获取手势编辑组件
gestureView = findViewById(R.id.gesture);
// 为手势编辑组件绑定事件监听器
gestureView.addOnGesturePerformedListener((source, gesture) -> {
// 识别用户刚刚所绘制的手势
List<Prediction> predictions = gestureLibrary.recognize(gesture);
List<String> result = new ArrayList<>();
// 遍历所有找到的Prediction对象
for (Prediction pred : predictions) {
// 只有相似度大于2.0的手势才会被输出
if (pred.score > 2.0) {
result.add("与手势【" + pred.name + "】相似度为" + pred.score);
}
}
if (result.size() > 0) {
ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this,
android.R.layout.simple_dropdown_item_1line, result);
// 使用一个带List的对话框来显示所有匹配的手势
new AlertDialog.Builder(MainActivity.this).setAdapter(adapter, null)
.setPositiveButton("确定", null).show();
} else {
Toast.makeText(MainActivity.this, "无法找到能匹配的手势!",
Toast.LENGTH_SHORT).show();
}
});
}
}
}
摘抄至《疯狂Android讲义(第4版)》