您当前的位置: 首页 > 学无止境 > 心得笔记 网站首页心得笔记
79_android下的手势识别
发布时间:2021-03-21 18:13:01编辑:雪饮阅读()
经过前面两篇关于手势的介绍,终于迎来了最后手势篇的完结。
这次是对前面两篇的汇总介绍,通过手势库、手势识别、意图来匹配不同的手势以实现不同的操作。
那么一切还是从清单文件开始吧,这次因为有涉及单笔划、多笔划的识别以及手势库查看,所以总共分了3个activity,这样项目结构以及操作起来更加结构清晰。那么意图肯定要配置到清单文件,另外就是我们有一个识别手势是识别到打电话手势就拨打电话,所以拨打电话的权限也是必不可少。所以清单文件则如AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.gesture">
<uses-permission android:name="android.permission.CALL_PHONE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Gesture">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:label="手势识别"
android:name=".MainActivity2">
</activity>
<activity
android:label="手势识别库"
android:name=".MainActivity3">
</activity>
</application>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.gesture">
<uses-permission android:name="android.permission.CALL_PHONE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Gesture">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:label="手势识别"
android:name=".MainActivity2">
</activity>
<activity
android:label="手势识别库"
android:name=".MainActivity3">
</activity>
</application>
</manifest>
上面说到了清单文件,那么对于手势库由于之前我们开发的那个用于创建手势并保存到手势库文件中的项目其实是将手势保存到sdcard文件系统中的,所以原本这里清单文件应该也需要加入sdcard读取权限,以便本项目中手势库查看时候调用该文件。但是实际上这里我不打算用sdcard读取,而是采用将上次项目中产生的手势库文件复制到项目中如
那么接下来是主布局文件,主布局文件这里主要是各个界面的入口,所以布局也不复杂,就几个按钮activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="单笔划手势识别"
android:onClick="tapPage"
/>
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="多笔划手势识别"
android:onClick="tapPage"
/>
<Button
android:id="@+id/button3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查看手势列表"
android:onClick="tapPage"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
那么接下来是主程序实现,主要也就是处理下意图分发MainActivity.java:
package com.example.gesture;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void tapPage(View v) {
switch (v.getId()) {
case R.id.button:
intent = new Intent();
intent.setClassName("com.example.gesture", "com.example.gesture.MainActivity2");
intent.putExtra("actionType", "single");
startActivity(intent);
break;
case R.id.button2:
intent = new Intent();
intent.setClassName("com.example.gesture", "com.example.gesture.MainActivity2");
intent.putExtra("actionType", "multipart");
startActivity(intent);
break;
case R.id.button3:
intent = new Intent();
intent.setClassName("com.example.gesture", "com.example.gesture.MainActivity3");
startActivity(intent);
break;
}
}
}
接下来是单笔划、多笔划手势识别并处理给予对应操作/行为的逻辑,而对于多笔划手势识别,这里仅仅做了一下监听回调,了解下生命周期,实际并没有多余逻辑。MainActivity2.java:
package com.example.gesture;
import android.content.Intent;
import android.gesture.Gesture;
import android.gesture.GestureLibraries;
import android.gesture.GestureLibrary;
import android.gesture.GestureOverlayView;
import android.gesture.Prediction;
import android.net.Uri;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
public class MainActivity2 extends AppCompatActivity {
private GestureOverlayView mGestureOverlayView;
private Gesture gesture;
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String actionType=intent.getStringExtra("actionType");
if("single".equals(actionType)){
setContentView(R.layout.activity_main_single);
mGestureOverlayView = (GestureOverlayView) this.findViewById(R.id.gestures1);
mGestureOverlayView.addOnGesturePerformedListener(new MyGesturePerformedListener());
}
if("multipart".equals(actionType)){
setContentView(R.layout.activity_main_multipart);
mGestureOverlayView = (GestureOverlayView) this.findViewById(R.id.gestures2);
mGestureOverlayView.addOnGestureListener(new MyGestureListener());
}
}
//手势识别。接收一个手势,为这个手势从手势库找到匹配的列表,然后进行识别逻辑处理
private void recoginizeGesture(Gesture gesture, GestureLibrary mLibrary) {
ArrayList<Prediction> predictions = mLibrary.recognize(gesture);
//获取当前接收到的手势所匹配的列表中最匹配的一个
Prediction prediction = predictions.get(0);
// score为手势的匹配率,范围从0-10,10是完全匹配,0是完全不匹配
System.out.println("本次手势匹配得分:"+prediction.score);
if (prediction.score >= 5) {
if ("close".equals(prediction.name)) {
System.out.println("本次手势匹配:关闭");
finish();
} else if ("call".equals(prediction.name)) {
System.out.println("本次手势匹配:拨打电话 ");
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:5556"));
startActivity(intent);
} else if("exit".equals(prediction.name)){
System.out.println("本次手势匹配:退出");
finish();
}
} else {
System.out.println("手势不能被识别 ");
}
}
//单笔手势识别 监听
private class MyGesturePerformedListener implements GestureOverlayView.OnGesturePerformedListener {
public void onGesturePerformed(GestureOverlayView overlay,Gesture gesture) {
// 把已经定义好的手势 转化成一个手势库
GestureLibrary mLibrary = GestureLibraries.fromRawResource(MainActivity2.this, R.raw.mygestures);
// 把手势库加载到内存
mLibrary.load();
// 获取到手势的在手势库里面的匹配集合
recoginizeGesture(gesture, mLibrary);
}
}
//多笔手势识别 监听
private class MyGestureListener implements GestureOverlayView.OnGestureListener{
public void onGestureStarted(GestureOverlayView overlay,MotionEvent event) {
System.out.println("onGestureStarted");
}
public void onGesture(GestureOverlayView overlay, MotionEvent event) {
System.out.println("onGesture");
}
public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
System.out.println("onGestureEnded");
gesture = overlay.getGesture();
}
public void onGestureCancelled(GestureOverlayView overlay,MotionEvent event) {
System.out.println("onGestureCancelled");
}
}
public void btClick(View v) {
switch (v.getId()) {
case R.id.home:
setContentView(R.layout.activity_main);
intent = new Intent();
intent.setClassName("com.example.gesture", "com.example.gesture.MainActivity");
startActivity(intent);
break;
}
}
}
接下来是多笔划的手势识别布局activity_main_multipart.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
tools:context=".MainActivity2">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/home"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="返回首页"
android:onClick="btClick"
/>
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="请在下方空白区域进行手势操作" />
<android.gesture.GestureOverlayView
android:id="@+id/gestures2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1.0"
android:gestureStrokeType="multiple"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
然后是单笔划手势布局activity_main_single.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
tools:context=".MainActivity2">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/home"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="返回首页"
android:onClick="btClick"
/>
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="请在下方空白区域进行手势操作" />
<android.gesture.GestureOverlayView
android:id="@+id/gestures1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1.0"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
然后是手势库的实现逻辑,这里对前面项目中手势库实现多加了些美化,看起来更明了一些。这里实现手势库目的是在我们进行测试之前先生成好几个手势,然后怕自己忘了,则在这个项目中可以直接读取,当然你必须将生成的手势库文件拷贝到如上面的raw目录中。MainActivity3.java:
package com.example.gesture;
import android.content.Intent;
import android.gesture.Gesture;
import android.gesture.GestureLibraries;
import android.gesture.GestureLibrary;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class MainActivity3 extends AppCompatActivity {
private List<GestureData> gestureDatas;
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_gesture_list);
GestureLibrary library= GestureLibraries.fromRawResource(MainActivity3.this, R.raw.mygestures);
//把手势加载到内存,这里是关键,如果没有load,则直接从文件读取的手势库大小就是0
library.load();
Set<String> entries = library.getGestureEntries();
System.out.println("手势库大小:"+entries.size());
gestureDatas = new ArrayList<GestureData>();
int serialNumber=0;
for (String key : entries) {
System.out.println(key);
ArrayList<Gesture> gestures= library.getGestures(key);
for(Gesture gesture:gestures){
Bitmap bitmap=gesture.toBitmap(128,128,10,0xff0000ff);
GestureData gestureData = new GestureData();
gestureData.setBitmap(bitmap);
gestureData.setBitmapKey(key);
gestureData.setSerialNumber(serialNumber);
serialNumber++;
gestureDatas.add(gestureData);
}
}
ListView mListView = (ListView) this.findViewById(R.id.gestureList);
mListView.setAdapter(new MyAdatper());
}
public void btClick(View v) {
switch (v.getId()) {
case R.id.home:
setContentView(R.layout.activity_main);
intent = new Intent();
intent.setClassName("com.example.gesture", "com.example.gesture.MainActivity");
startActivity(intent);
break;
}
}
public class MyAdatper extends BaseAdapter {
public int getCount() {
return gestureDatas.size();
}
public Object getItem(int position) {
return gestureDatas.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
GestureData gestureData = gestureDatas.get(position);
//核心部分
TextView tv = new TextView(MainActivity3.this);
tv.setText("key:"+gestureData.getBitmapKey()+" serialNumber:"+gestureData.getSerialNumber());
ImageView imageView = new ImageView(MainActivity3.this);
//核心部分end
Bitmap bmp =gestureData.getBitmap();
imageView.setImageBitmap(bmp);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(400,400);
imageView.setLayoutParams(params);
ViewGroup.MarginLayoutParams margin = new ViewGroup.MarginLayoutParams(imageView.getLayoutParams());
margin.leftMargin=100;
LinearLayout.LayoutParams marginParams = new LinearLayout.LayoutParams(margin);
imageView.setLayoutParams(marginParams);
LinearLayout lv = new LinearLayout(MainActivity3.this);
lv.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lv.setLayoutParams(lp);//设置布局参数
lv.addView(tv);
lv.addView(imageView);
return lv;
}
}
}
接下来是手势库的布局activity_main_gesture_list.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
tools:context=".MainActivity3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/home"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="返回首页"
android:onClick="btClick"
/>
<ListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/gestureList"
>
</ListView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
最后是前面项目中所用到的bitmap中转实体类,当然这里不需要序列化了,因为我们直接读取上面如raw目录中的手势库文件,而不采用前面意图传递list的方式了。GestureData.java:
package com.example.gesture;
import android.graphics.Bitmap;
public class GestureData {
Bitmap bitmap;
Integer serialNumber;
String bitmapKey;
public Bitmap getBitmap() {
return this.bitmap;
}
public void setBitmap(Bitmap bitmap){
this.bitmap=bitmap;
}
public Integer getSerialNumber() {
return this.serialNumber;
}
public void setSerialNumber(Integer serialNumber) {
this.serialNumber = serialNumber;
}
public String getBitmapKey() {
return this.bitmapKey;
}
public void setBitmapKey(String bitmapKey) {
this.bitmapKey = bitmapKey;
}
}
那么最后我们部署到设备并勾选了相关权限,并建立手势库如主界面
手势库界面
这里是我自己创建的对应的call打电话的手势是z,退出程序的手势是l,关闭的手势是c
有时候一次性识别不成功,则可以多参照这个界面,看看你之前画的时候的某些细节,和原来的越像则识别成功率越高。
至此整个项目我这里测试是没有问题的。其它界面就不一一贴图了,图片很贵的。
关键字词:android,手势