转载自【黑米GameDev街区】 原文链接: http://www.himigame.com/android-game/340.html
———————————————————————
『很多童鞋说我的代码运行后,点击home或者back后会程序异常,如果你也这样遇到过,那么你肯定没有仔细读完Himi的博文,第十九篇Himi专门写了关于这些错误的原因和解决方法,这里我在博客都补充说明下,省的童鞋们总疑惑这一块;请点击下面联系进入阅读:
【Android游戏开发十九】(必看篇)SurfaceView运行机制详解—剖析Back与Home按键及切入后台等异常处理! 』
——————————————————————-
有童鞋问我为什么不用SDK2.1 ,2.2来进行游戏开发,那我这里稍微说两句:
1.Android SDK 属于向下兼容!那么低版本可以运行的,高版本基本上更是没问题!(当然每次SDK的更新也会带来新功能,或者修改了一些原来的BUG等等,那么其实对于游戏开发来说,如果你的游戏中不需要更高的SDK版本的支持情况下,完全不必去追求最新的SDK!)
2.使用低版本进行游戏开发这样能兼顾更多的机型,获取更多的用户!
3.大家都知道Android SDK 每次版本的更新,底层代码也会更健壮和优化了!比如我们公司的网游Android版在G2(SDK1.5)上跑起来稍微有些卡,而在我的手机上(SDK2.2)运行起来流畅的没说的~各种舒坦~~但是这样也会带来一些弊端,比如我们自己游戏如果上来就用高版本SDK进行开发,那么对于性能、内存上到底如何,我们都不会很容易的看出其效果,如果我们用低版本的SDK则会让我们明显的感受到性能到底如何~你想想如果你的游戏在1.5 ,1.6上跑起来很流畅,那放在更高版本的SDK机器上更是没说的啦~
总结:游戏开发中,如果你游戏不需要更高的API的支持,那么推荐基于SDK 1.5和1.6来开发!
在上一篇中我给大家介绍了触摸屏手势操作,但是这种触屏手势的操作比较有局限性;比如我们都知道Android可以利用手势来解锁,比如九宫格形式的,通过自定义的一个单笔画手势可以解开屏幕锁,还可以自定义笔画手势来启动一个应用等,那么这种所谓的笔画手势其实就是今天我要给大家讲解的输入法手势识别技术!这种手势是我们可以自己来自定义,而不像之前的触屏手势操作只是利用Android 对一些触屏动作的封装罢了。下面上几张手机自定义笔划手势解锁的的截图:
OK,那么既然利用手势既然能进行解锁等操作,那么我们游戏开发中,更是可以加入这一亮点了,比如在游戏中我画个圆形执行换背景操作,画个X表示退出游戏等等,等等、哈哈 是不是感觉很有意思了?好的,下面就开始进入讲解!
首先本篇主要学习两点:
1. 如何创建输入法手势、删除输入法手势、从SD卡中读取出手势文件!
2.当输入法手势创建后,如何来匹配出我们的自定义手势!
下面我们来熟习两个类和几个概念:
1. 什么是 GestureOverlayView ? 简单点说其实就是一个手写绘图区;
2. 什么是 GestureLibrary ? 这个类是对手势进行保存、删除等操作的,一个存放手势的小仓库!
3. 笔划是什么,字体笔画? 是的,其实就是跟我们写字的笔划一个概念!
4.什么是笔划类型? 输入法手势操作中,笔划类型有两种;一种是:单一笔划,另外一种是:多笔划
所谓单一笔划笔划就是一笔划画出一个手势,从你手指接触屏幕开始到你离开屏幕笔画就会立刻形成一个手势!一气呵成!
而多笔划则是可以在一定紧凑时间内随意几笔划都可!然后超过这个紧凑时间后便会形成一个手势!
先出项目截图,简单说下其功能和操作:
图1界面中分为3块,从上到下依次是:TextView ,EditText,SurfaceView;然后在SurfaceView后面还有一个覆盖全屏的GestureOverlayView!
图2界面是在创建好的手势中匹配手势的界面,这里很清晰看出来,找的很对 ~嘿嘿~
先看下main.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
&
lt
;
?
xml
version
=
"1.0"
encoding
=
"utf-8"
?
&
gt
;
&
lt
;
LinearLayout
xmlns
:
android
=
"http://schemas.android.com/apk/res/android"
android
:
orientation
=
"vertical"
android
:
layout_width
=
"fill_parent"
android
:
layout_height
=
"fill_parent"
&
gt
;
&
lt
;
TextView
android
:
id
=
"@+id/himi_tv"
android
:
layout_width
=
"fill_parent"
android
:
layout_height
=
"wrap_content"
android
:
text
=
"@string/hello"
android
:
textSize
=
"15sp"
android
:
textColor
=
"#FFFFFF00"
/
&
gt
;
&
lt
;
EditText
android
:
id
=
"@+id/himi_edit"
android
:
layout_width
=
"fill_parent"
android
:
layout_height
=
"wrap_content"
/
&
gt
;
&
lt
;
RelativeLayout
android
:
layout_width
=
"fill_parent"
android
:
layout_height
=
"wrap_content"
android
:
layout_weight
=
"1"
&
gt
;
&
lt
;
com
.
himi
.
MySurfaceView
android
:
id
=
"@+id/view3d"
android
:
layout_width
=
"fill_parent"
android
:
layout_height
=
"fill_parent"
/
&
gt
;
&
lt
;
android
.
gesture
.
GestureOverlayView
android
:
id
=
"@+id/himi_gesture"
android
:
layout_width
=
"fill_parent"
android
:
layout_height
=
"fill_parent"
android
:
layout_weight
=
"1.0"
/
&
gt
;
&
lt
;
/
RelativeLayout
&
gt
;
&
lt
;
/
LinearLayout
&
gt
;
|
xml中注册的有我们自定义的surfaceview,对此不太熟悉可以去看下【Android2D开发之六】,不多解释了。关于GestureOverlayView这里也只是简单的定义了宽高,还有一些重要的属性设置在代码中设置了,当然xml也可以设置的;
下面看MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
*
*
*
@
author
Himi
*
@输入法手势识别
*
@注意
:
android
.
gesture这个类在
api
-
4
(
SDK1
.
6
)才开始支持的!
*
@提醒:默认存到
SD卡中,所以别忘记在
AndroidMainfest
.
xml加上
SD卡读写权限!
*
/
public
class
MainActivity
extends
Activity
{
private
GestureOverlayView
gov
;
// 创建一个手写绘图区
private
Gesture
gesture
;
// 手写实例
private
GestureLibrary
gestureLib
;
//创建一个手势仓库
private
TextView
tv
;
private
EditText
et
;
private
String
path
;
//手势文件路径
private
File
file
;
//
@
Override
public
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
)
;
this
.
getWindow
(
)
.
setFlags
(
WindowManager
.
LayoutParams
.
FLAG_FULLSCREEN
,
WindowManager
.
LayoutParams
.
FLAG_FULLSCREEN
)
;
this
.
requestWindowFeature
(
Window
.
FEATURE_NO_TITLE
)
;
setContentView
(
R
.
layout
.
main
)
;
tv
=
(
TextView
)
findViewById
(
R
.
id
.
himi_tv
)
;
et
=
(
EditText
)
findViewById
(
R
.
id
.
himi_edit
)
;
gov
=
(
GestureOverlayView
)
findViewById
(
R
.
id
.
himi_gesture
)
;
gov
.
setGestureStrokeType
(
GestureOverlayView
.
GESTURE_STROKE_TYPE_MULTIPLE
)
;
//设置笔划类型
// GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE 设置支持多笔划
// GestureOverlayView.GESTURE_STROKE_TYPE_SINGLE 仅支持单一笔划
path
=
new
File
(
Environment
.
getExternalStorageDirectory
(
)
,
"gestures"
)
.
getAbsolutePath
(
)
;
//得到默认路径和文件名/sdcard/gestures
file
=
new
File
(
path
)
;
//实例gestures的文件对象
gestureLib
=
GestureLibraries
.
fromFile
(
path
)
;
//实例手势仓库
gov
.
addOnGestureListener
(
new
OnGestureListener
(
)
{
// 这里是绑定手写绘图区
@
Override
// 以下方法是你刚开始画手势的时候触发
public
void
onGestureStarted
(
GestureOverlayView
overlay
,
MotionEvent
event
)
{
tv
.
setText
(
"请您在紧凑的时间内用两笔划来完成一个手势!西西~"
)
;
}
@
Override
// 以下方法是当手势完整形成的时候触发
public
void
onGestureEnded
(
GestureOverlayView
overlay
,
MotionEvent
event
)
{
gesture
=
overlay
.
getGesture
(
)
;
// 从绘图区取出形成的手势
if
(
gesture
.
getStrokesCount
(
)
==
2
)
{
//我判定当用户用了两笔划
//(强调:如果一开始设置手势笔画类型是单一笔画,那你这里始终得到的只是1!)
if
(
event
.
getAction
(
)
==
MotionEvent
.
ACTION_UP
)
{
//判定第两笔划离开屏幕
//if(gesture.getLength()==100){}//这里是判定长度达到100像素
if
(
et
.
getText
(
)
.
toString
(
)
.
equals
(
""
)
)
{
tv
.
setText
(
"由于您没有输入手势名称,so~保存失败啦~"
)
;
}
else
{
tv
.
setText
(
"正在保存手势..."
)
;
addGesture
(
et
.
getText
(
)
.
toString
(
)
,
gesture
)
;
//我自己写的添加手势函数
}
}
}
else
{
tv
.
setText
(
"请您在紧凑的时间内用两笔划来完成一个手势!西西~"
)
;
}
}
@
Override
public
void
onGestureCancelled
(
GestureOverlayView
overlay
,
MotionEvent
event
)
{
}
@
Override
public
void
onGesture
(
GestureOverlayView
overlay
,
MotionEvent
event
)
{
}
}
)
;
//----这里是在程序启动的时候进行遍历所有手势!------
if
(
!
gestureLib
.
load
(
)
)
{
tv
.
setText
(
"Himi提示:手势超过9个我做了删除所有手势的操作,为了界面整洁一些!"
+
" 输入法手势练习~(*^__^*)~ 嘻嘻!/n操作介绍:(画手势我设置必须画两笔划才行哦~)/n1."
+
"添加手势:先EditText中输入名称,然后在屏幕上画出手势!/n2.匹配手势:"
+
"在EditText输入/"
himi
/
",然后输入手势即可! "
)
;
}
else
{
Set
&
lt
;
String
&
gt
;
set
=
gestureLib
.
getGestureEntries
(
)
;
//取出所有手势
Object
ob
[
]
=
set
.
toArray
(
)
;
loadAllGesture
(
set
,
ob
)
;
}
}
}
|
这个就是MainActivity主要代码了,其中添加手势、匹配手势、遍历手势、将手势转成图片这些我都单独写成了函数,这样让各位童鞋更清晰思路一些。
从以上代码中我们看出在创建手势之前,手写绘图区(GestureOverlayView)肯定先被创建出来,然后我们就可以在其区域中进行笔划绘画手势了,当然绘画手势前,我们也需要设置了笔划类型,也就是我一开始给大家介绍的~其后最重要的就是手写绘图区的手势监听器绑定,增加OnGestureListener这个监听器重写了四个函数,这里最重要的就两个函数:
onGestureStarted 和 onGestureEnded ; 手势开始和手势结束的监听函数!
尤其是手势结束监听这个函数尤为重要,在其中我设置好几个条件语句,这么几个条件一方面是让大家了解Gesture中一些比较重要常用的方法,另一方面我要提醒各位童鞋:
如果你设置笔划类型是多笔划类型的,那么理想状态下,应该是在一段紧凑时间内,不管你使用了几笔划来绘制手势,系统都应该在判定你在一定短暂时间内没有再进行笔划的时候才应该创建手势,并且系统响应此函数;
其实错了,一开始我也这么想,但是发现,不管你设置的笔划类型是单一的还是多笔划当你手指离开屏幕,不管你当前是第几笔,Android都会去响应这个完成函数,so~ 我在这里调用手势Gesture类中的getStrokesCount()函数,这个函数会记录在紧凑时间内你绘制手势的笔划数,那么根据这个函数我们就可以解决手指离开屏幕总被响应的问题了,因为单一笔划类型永远这个值不会大于1!
而 if (event.getAction() == MotionEvent.ACTION_UP) {}写这个只是给大家演示第二个参数按键动作该怎么用;
那么我们下面就来看如何创建一个手势:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
public
void
addMyGesture
(
String
name
,
Gesture
gesture
)
{
try
{
if
(
name
.
equals
(
"himi"
)
)
{
findGesture
(
gesture
)
;
}
else
{
// 关于两种方式创建模拟器的SDcard在【Android2D游戏开发之十】有详解
if
(
Environment
.
getExternalStorageState
(
)
!=
null
)
{
// 这个方法在试探终端是否有sdcard!
if
(
!
file
.
exists
(
)
)
{
// 判定是否已经存在手势文件
// 不存在文件的时候我们去直接把我们的手势文件存入
gestureLib
.
addGesture
(
name
,
gesture
)
;
if
(
gestureLib
.
save
(
)
)
{
////保存到文件中
gov
.
clear
(
true
)
;
//清除笔画
// 注意保存的路径默认是/sdcard/gesture ,so~别忘记AndroidMainfest.xml加上读写权限!
// 这里抱怨一下,咳咳、其实昨天就应该出这篇博文的,就是因为这里总是异常,今天仔细看了
// 才发现不是没写权限,而是我虽然在AndroidMainfest.xml中写了权限,但是写错了位置..哭死!
tv
.
setText
(
"保存手势成功!因为不存在手势文件,"
+
"所以第一次保存手势成功会默认先创"
+
"建了一个手势文件!然后将手势保存到文件中."
)
;
et
.
setText
(
""
)
;
gestureToImage
(
gesture
)
;
}
else
{
tv
.
setText
(
"保存手势失败!"
)
;
}
}
else
{
//当存在此文件的时候我们需要先删除此手势然后把新的手势放上
//读取已经存在的文件,得到文件中的所有手势
if
(
!
gestureLib
.
load
(
)
)
{
//如果读取失败
tv
.
setText
(
"手势文件读取失败!"
)
;
}
else
{
//读取成功
Set
&
lt
;
String
&
gt
;
set
=
gestureLib
.
getGestureEntries
(
)
;
//取出所有手势
Object
ob
[
]
=
set
.
toArray
(
)
;
boolean
isHavedGesture
=
false
;
for
(
int
i
=
0
;
i
&
lt
;
ob
.
length
;
i
++
)
{
//这里是遍历所有手势的name
if
(
(
(
String
)
ob
[
i
]
)
.
equals
(
name
)
)
{
//和我们新添的手势name做对比
isHavedGesture
=
true
;
}
}
if
(
isHavedGesture
)
{
//如果此变量为true说明有相同name的手势
//----备注1-------------------//gestureLib.removeGesture(name, gesture);//删除与当前名字相同的手势
/*----备注2-----------------*/
gestureLib
.
removeEntry
(
name
)
;
gestureLib
.
addGesture
(
name
,
gesture
)
;
}
else
{
gestureLib
.
addGesture
(
name
,
gesture
)
;
}
if
(
gestureLib
.
save
(
)
)
{
gov
.
clear
(
true
)
;
//清除笔画
gestureToImage
(
gesture
)
;
tv
.
setText
(
"保存手势成功!当前所有手势一共有:"
+
ob
.
length
+
"个"
)
;
et
.
setText
(
""
)
;
}
else
{
tv
.
setText
(
"保存手势失败!"
)
;
}
////------- --以下代码是当手势超过9个就全部清空 操作--------
if
(
ob
.
length
&
gt
;
9
)
{
for
(
int
i
=
0
;
i
&
lt
;
ob
.
length
;
i
++
)
{
//这里是遍历删除手势
gestureLib
.
removeEntry
(
(
String
)
ob
[
i
]
)
;
}
gestureLib
.
save
(
)
;
if
(
MySurfaceView
.
vec_bmp
!=
null
)
{
MySurfaceView
.
vec_bmp
.
removeAllElements
(
)
;
//删除放置手势图的容器
}
tv
.
setText
(
"手势超过9个,已全部清空!"
)
;
et
.
setText
(
""
)
;
}
ob
=
null
;
set
=
null
;
}
}
}
else
{
tv
.
setText
(
"当前模拟器没有SD卡 - -。"
)
;
}
}
}
catch
(
Exception
e
)
{
tv
.
setText
(
"操作异常!"
)
;
}
}
|
这里也都很好理解,套路类似之前File文件存储的套路,先判断SD是否存在,然后是文件是否存在:
如果文件不存在就先直接添加到手势到手势仓库中,然后手势仓调用gestureLib.save()才算把手势存到SD卡的手势文件中。
文件存在的话还要去判定是否文件中包含了相同名字的手势;当然这里可以不判定是否有相同手势名存在,然后进行删除操作!其实也可不删除,直接添加进去当前新建的手势;原因看了下面的备注解释就明白了;
备注 1:因为gestureLib保存的手势是个HashMap, key=手势的名字,value=手势,所以gestureLib.removeGesture(name, gesture);这种删除方式只是删除了手势,该手势名字依旧保存在hashmap中,下次还有相同的name手势存入的时候Hashmap就直接覆盖本条目了。所以根据Hashmap的特征,我们可以不进行删除操作,可以直接gestureLib.addGesture(name, gesture);因为如果出现相同的手势名字的手势,Hashmap就会根据key(手势的名字)直接覆盖其条目的value(手势)滴~
备注2 :这里也是一种删除手势的方式,但是这种方式跟备注1的不同,这里是将Hashmap中的条目删除,也就是说key和value都被删去!
下面看下如何把手势转成bitmap!
1
2
3
4
5
6
|
public
void
gestureToImage
(
Gesture
ges
)
{
//将手势转换成Bitmap
//把手势转成图片,存到我们SurfaceView中定义的Image容器中,然后都画出来~
if
(
MySurfaceView
.
vec_bmp
!=
null
)
{
MySurfaceView
.
vec_bmp
.
addElement
(
ges
.
toBitmap
(
100
,
100
,
12
,
Color
.
GREEN
)
)
;
}
}
|
下面是如何遍历手势!
1
2
3
4
5
6
7
8
9
10
11
12
|
public
void
loadAllGesture
(
Set
&
lt
;
String
&
gt
;
set
,
Object
ob
[
]
)
{
//遍历所有的手势
if
(
gestureLib
.
load
(
)
)
{
//读取最新的手势文件
set
=
gestureLib
.
getGestureEntries
(
)
;
//取出所有手势
ob
=
set
.
toArray
(
)
;
for
(
int
i
=
0
;
i
&
lt
;
ob
.
length
;
i
++
)
{
//把手势转成Bitmap
gestureToImage
(
gestureLib
.
getGestures
(
(
String
)
ob
[
i
]
)
.
get
(
0
)
)
;
//这里是把我们每个手势的名字也保存下来
MySurfaceView
.
vec_string
.
addElement
(
(
String
)
ob
[
i
]
)
;
}
}
}
|
下面最后来看看手势的匹配!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public
void
findGesture
(
Gesture
gesture
)
{
try
{
// 关于两种方式创建模拟器的SDcard在【Android2D游戏开发之十】有详解
if
(
Environment
.
getExternalStorageState
(
)
!=
null
)
{
// 这个方法在试探终端是否有sdcard!
if
(
!
file
.
exists
(
)
)
{
// 判定是否已经存在手势文件
tv
.
setText
(
"匹配手势失败,因为手势文件不存在!!"
)
;
}
else
{
//当存在此文件的时候我们需要先删除此手势然后把新的手势放上
//读取已经存在的文件,得到文件中的所有手势
if
(
!
gestureLib
.
load
(
)
)
{
//如果读取失败
tv
.
setText
(
"匹配手势失败,手势文件读取失败!"
)
;
}
else
{
//读取成功
List
&
lt
;
Prediction
&
gt
;
predictions
=
gestureLib
.
recognize
(
gesture
)
;
//recognize()的返回结果是一个prediction集合,
//包含了所有与gesture相匹配的结果。
//从手势库中查询匹配的内容,匹配的结果可能包括多个相似的结果,
if
(
!
predictions
.
isEmpty
(
)
)
{
Prediction
prediction
=
predictions
.
get
(
0
)
;
//prediction的score属性代表了与手势的相似程度
//prediction的name代表手势对应的名称
//prediction的score属性代表了与gesture得相似程度(通常情况下不考虑score小于1的结果)。
if
(
prediction
.
score
&
gt
;
=
1
)
{
tv
.
setText
(
"当前你的手势在手势库中找到最相似的手势:name ="
+
prediction
.
name
)
;
}
}
}
}
}
else
{
tv
.
setText
(
"匹配手势失败,,当前模拟器没有SD卡 - -。"
)
;
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
(
)
;
tv
.
setText
(
"由于出现异常,匹配手势失败啦~"
)
;
}
}
|
那么最后给各位童鞋说一下,其实输入法手势操作很是适合游戏中使用,不管是触摸屏手势操作还是今天讲的输入法手势操作如果加到游戏中那都是相当赞的!但是我们公司网游引擎和框架不适合插入手势 – -、唉~
其实前两天应该发这篇的,但是因为工作忙了几天,让大家久等了,挺不好意思的,所以今天熬夜给大家写了出来,现在都凌晨 7:00 了~
源码下载: “输入法手势识别.rar” 下载地址: http://vdisk.weibo.com/s/hq3f2