自 SDK 1.6 开始,Android手机已支持内置 Gesture Builder 程序,若是被Google签署(Signed)过出厂的手机应会内置此程序。
Gesture Builder 提供了手写识别的功能,让用户以类似于涂鸦的方式绘制一个手写符号,对应一个字符串名称,存储在 /mnt/sdcard/gestures
文件中。
为了练手,编写了Gesture Builder的应用程序,运行效果如下:
界面布局如下:
gesture_builder.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/gesture_builder_layout_top">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:textSize="18sp"
android:textColor="@android:color/white"
android:text="名称:"/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:background="@android:color/white"
android:id="@+id/gesture_builder_txt_name"/>
</LinearLayout>
<android.gesture.GestureOverlayView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gestureStrokeType="multiple"
android:gestureColor="#FFFF00"
android:layout_below="@+id/gesture_builder_layout_top"
android:layout_above="@+id/gesture_builder_layout_bottom"
android:layout_marginRight="30dp"
android:id="@+id/gesture_builder_gesture_overlay_view" >
</android.gesture.GestureOverlayView>
<SlidingDrawer
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:content="@+id/gesture_builder_content"
android:handle="@+id/gesture_builder_handler"
android:id="@+id/gesture_builder_sliding_drawer">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="SlidingDrawer"
android:src="@drawable/thumb_select"
android:id="@+id/gesture_builder_handler"/>
<ListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:choiceMode="singleChoice"
android:background="@drawable/listview_bg"
android:divider="@drawable/listview_divider_style"
android:dividerHeight="2dp"
android:id="@+id/gesture_builder_content"></ListView>
</SlidingDrawer>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:weightSum="2"
android:layout_alignParentBottom="true"
style="@android:style/ButtonBar"
android:orientation="horizontal"
android:id="@+id/gesture_builder_layout_bottom">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Done"
android:id="@+id/gesture_builder_btn_done"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Discard"
android:id="@+id/gesture_builder_btn_discard"/>
</LinearLayout>
</RelativeLayout>
gesture_builder_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/gesture_builder_list_image"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:lines="1"
android:textColor="#f69900"
android:textSize="18sp"
android:textStyle="bold"
android:id="@+id/gesture_builder_list_text"/>
</LinearLayout>
样式风格如下:
listview_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#1C86EE"
android:centerColor="#FFFFFF"
android:endColor="#1C86EE"
android:centerX="0.1"
android:angle="135" />
</shape>
listview_divider_style.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#eeee00"
android:centerColor="#ff4500"
android:endColor="#eeee00"/>
</shape>
适配器如下:
GestureBuilderAdapter.java
package com.xx.xx;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
public class GestureBuilderAdapter extends BaseAdapter {
private Context context;
private List<String> names;
private List<Bitmap> bitmaps;
public GestureBuilderAdapter(Context context, List<String> gestureNames, List<Bitmap> gestureBitmaps){
this.context = context;
this.names = gestureNames;
this.bitmaps = gestureBitmaps;
}
@Override
public int getCount() {
return this.names.size();
}
@Override
public Object getItem(int position) {
return names.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(context).inflate(R.layout.gesture_builder_list_item, null);
ImageView imageView = (ImageView)convertView.findViewById(R.id.gesture_builder_list_image);
imageView.setImageBitmap(bitmaps.get(position));
TextView textView = (TextView)convertView.findViewById(R.id.gesture_builder_list_text);
textView.setText(names.get(position));
return convertView;
}
}
Activity如下:
GestureBuilderActivity.java
package com.xx.xx;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.gesture.Gesture;
import android.gesture.GestureLibraries;
import android.gesture.GestureLibrary;
import android.gesture.GestureOverlayView;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Environment;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.SlidingDrawer;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class GestureBuilderActivity extends Activity {
private LinearLayout top;
private EditText txtName;
private LinearLayout bottom;
private Button btnDone;
private GestureOverlayView gestureOverlayView;
private SlidingDrawer slidingDrawer;
private ListView listView;
private ImageView imageView;
/**
* 当前手势
*/
private Gesture gesture;
/**
* 手势文件的存储路径
*/
private String gesPath;
/**
* 手势名称列表
*/
private List<String> gestureNames = new ArrayList<>();
/**
* 手势图片列表
*/
private List<Bitmap> gestureBitmaps = new ArrayList<>();
/**
* 适配器
*/
private GestureBuilderAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gesture_builder);
/**
* 检测sdcard是否存在
*/
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
Toast.makeText(this, "sdcard不存在!", Toast.LENGTH_SHORT).show();
finish();
}
/**
* 手势文件默认存储在“/mnt/sdcard/gestures”文件中
*/
gesPath = new File(Environment.getExternalStorageDirectory(), "gestures").getAbsolutePath();
/**
* ListView适配器
*/
adapter = new GestureBuilderAdapter(this, gestureNames, gestureBitmaps);
top = (LinearLayout)findViewById(R.id.gesture_builder_layout_top);
txtName = (EditText)findViewById(R.id.gesture_builder_txt_name);
gestureOverlayView = (GestureOverlayView)findViewById(R.id.gesture_builder_gesture_overlay_view);
bottom = (LinearLayout)findViewById(R.id.gesture_builder_layout_bottom);
btnDone = (Button)findViewById(R.id.gesture_builder_btn_done);
Button btnDiscard = (Button)findViewById(R.id.gesture_builder_btn_discard);
slidingDrawer = (SlidingDrawer)findViewById(R.id.gesture_builder_sliding_drawer);
imageView = (ImageView)findViewById(R.id.gesture_builder_handler);
listView = (ListView)findViewById(R.id.gesture_builder_content);
/**
* Done按钮默认不可用
*/
btnDone.setEnabled(false);
/**
* 当名称不为空时,Done按钮可用
*/
txtName.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (gesture != null && txtName.getText().toString().trim().length() > 0){
btnDone.setEnabled(true);
} else {
btnDone.setEnabled(false);
}
return false;
}
});
/**
* 监听gesture,实现gesture开始和结束的事件
*/
gestureOverlayView.addOnGestureListener(new GestureOverlayView.OnGestureListener() {
@Override
public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
gesture = null;
btnDone.setEnabled(false);
}
@Override
public void onGesture(GestureOverlayView overlay, MotionEvent event) {
}
@Override
public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
gesture = overlay.getGesture();
if (gesture != null && txtName.getText().toString().trim().length() > 0) {
btnDone.setEnabled(true);
}
}
@Override
public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) {
}
});
/**
* 添加手势
*/
btnDone.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = txtName.getText().toString().trim();
GestureLibrary library = GestureLibraries.fromFile(gesPath);
File file = new File(gesPath);
/**
* 若gestures文件存在,则判断是否有同名手势已经存在,若有,则移除后添加,若没有直接添加
*/
if (file.exists()){
if (library.load()){
Set<String> entries = library.getGestureEntries();
if (entries.contains(name)){
List<Gesture> list = library.getGestures(name);
for (int i=0;i<list.size();i++){
library.removeGesture(name, list.get(i));
}
}
}
}
/**
* 添加手势
*/
library.addGesture(name, gesture);
/**
* 保存手势
*/
if (library.save()){
txtName.setText("");
gestureOverlayView.clear(true);
btnDone.setEnabled(false);
Toast.makeText(GestureBuilderActivity.this, "保存成功,路径为:"+gesPath, Toast.LENGTH_SHORT).show();
}else
{
Toast.makeText(GestureBuilderActivity.this, "保存失败", Toast.LENGTH_SHORT).show();
}
/**
* 拉开抽屉,显示gesture列表
*/
slidingDrawer.toggle();
}
});
/**
* 取消手势
*/
btnDiscard.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
txtName.setText("");
gestureOverlayView.clear(true);
gesture = null;
btnDone.setEnabled(false);
}
});
/**
* 初始化ListView
*/
listView.setAdapter(adapter);
UpdateGestureList();
/**
* 抽屉打开,显示ListView
*/
slidingDrawer.setOnDrawerOpenListener(new SlidingDrawer.OnDrawerOpenListener() {
@Override
public void onDrawerOpened() {
imageView.setImageResource(R.drawable.thumb_normal);
top.setVisibility(View.GONE);
gestureOverlayView.setVisibility(View.GONE);
bottom.setVisibility(View.GONE);
GestureBuilderActivity.this.UpdateGestureList();
}
});
/**
* 抽屉关闭,隐藏ListView
*/
slidingDrawer.setOnDrawerCloseListener(new SlidingDrawer.OnDrawerCloseListener() {
@Override
public void onDrawerClosed() {
imageView.setImageResource(R.drawable.thumb_select);
top.setVisibility(View.VISIBLE);
gestureOverlayView.setVisibility(View.VISIBLE);
bottom.setVisibility(View.VISIBLE);
}
});
/**
* 长按删除
*/
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) {
AlertDialog.Builder builder = new AlertDialog.Builder(GestureBuilderActivity.this);
builder.setTitle("是否删除该项?");
builder.setIcon(R.drawable.ic_launcher);
builder.setMessage("点击确定,将删除该手势!");
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String name = gestureNames.get(position);
File file = new File(gesPath);
if (file.exists()){
GestureLibrary library = GestureLibraries.fromFile(file);
if (library != null && library.load()){
ArrayList<Gesture> gestures = library.getGestures(name);
if (gestures != null){
for (Gesture ges : gestures){
library.removeGesture(name, ges);
}
}
library.save();
GestureBuilderActivity.this.UpdateGestureList();
}
}
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
return;
}
});
AlertDialog dialog = builder.create();
dialog.show();
return false;
}
});
}
/**
* 重新读取gestures中的手势,更新ListView
*/
private void UpdateGestureList(){
/**
* 读取gestures中的手势
*/
File file = new File(gesPath);
if (!file.exists()){
return;
}
gestureNames.clear();
gestureBitmaps.clear();
GestureLibrary library = GestureLibraries.fromFile(file);
if (library == null || !library.load()){
return;
}
Object[] names = library.getGestureEntries().toArray();
if (names == null || names.length < 1){
return;
}
for (Object obj : names){
String name = obj.toString();
ArrayList<Gesture> gestures = library.getGestures(name);
if (gestures != null){
for (Gesture ges : gestures){
gestureNames.add(name);
gestureBitmaps.add(ges.toBitmap(80,80,12,Color.YELLOW));
}
}
}
adapter.notifyDataSetChanged();
}
}
在 AndoridManifest.xml 中需要定义访问外部存储的权限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
参考文章:http://blog.csdn.net/ta893115871/article/details/7822816