/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.gesture.builder
import _root_.android.app.{Dialog, AlertDialog, ListActivity}
import _root_.android.app.Activity._
import _root_.android.os.{AsyncTask, Bundle, Environment}
import _root_.android.view.{ContextMenu, View, MenuItem, LayoutInflater, ViewGroup}
import _root_.android.gesture.{Gesture, GestureLibrary, GestureLibraries}
import _root_.android.widget.{AdapterView, ArrayAdapter, EditText, TextView, Toast}
import _root_.android.content.{Context, DialogInterface, Intent}
import _root_.android.content.res.Resources
import _root_.android.text.TextUtils
import _root_.android.graphics.Bitmap
import _root_.android.graphics.drawable.{BitmapDrawable, Drawable}
import java.util.Comparator
import java.io.File
import scala.collection.JavaConversions.{JListWrapper, JSetWrapper}
import scala.collection.mutable.{HashMap, SynchronizedMap}
object GestureBuilderActivity {
private final val STATUS_SUCCESS = 0
private final val STATUS_CANCELLED = 1
private final val STATUS_NO_STORAGE = 2
private final val STATUS_NOT_LOADED = 3
private final val MENU_ID_RENAME = 1
private final val MENU_ID_REMOVE = 2
private final val DIALOG_RENAME_GESTURE = 1
private final val REQUEST_NEW_GESTURE = 1
// Type: long (id)
private final val GESTURES_INFO_ID = "gestures.info_id"
private final val mStoreFile =
new File(Environment.getExternalStorageDirectory, "gestures")
private final val sStore = GestureLibraries.fromFile(mStoreFile)
def getStore: GestureLibrary = sStore
class NamedGesture(var name: String, var gesture: Gesture)
}
class GestureBuilderActivity extends ListActivity {
import GestureBuilderActivity._ // companion object
private final val mSorter = new Comparator[NamedGesture]() {
def compare(object1: NamedGesture, object2: NamedGesture): Int = {
object1.name compareTo object2.name
}
}
private var mAdapter: GesturesAdapter = _
private var mTask: GesturesLoadTask = _
private var mEmpty: TextView = _
private var mRenameDialog: Dialog = _
private var mInput: EditText = _
private var mCurrentRenameGesture: NamedGesture = _
override protected def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.gestures_list)
mAdapter = new GesturesAdapter(this)
setListAdapter(mAdapter)
mEmpty = findViewById(android.R.id.empty).asInstanceOf[TextView]
loadGestures()
registerForContextMenu(getListView)
}
//@SuppressWarnings({"UnusedDeclaration"})
def reloadGestures(v: View) {
loadGestures()
}
//@SuppressWarnings({"UnusedDeclaration"})
def addGesture(v: View) {
val intent = new Intent(this, classOf[CreateGestureActivity])
startActivityForResult(intent, REQUEST_NEW_GESTURE)
}
override protected def onActivityResult(requestCode: Int,
resultCode: Int, data: Intent) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
requestCode match {
case REQUEST_NEW_GESTURE =>
loadGestures()
}
}
}
private def loadGestures() {
if (mTask != null && mTask.getStatus != AsyncTask.Status.FINISHED) {
mTask cancel true
}
mTask = new GesturesLoadTask().execute().asInstanceOf[GesturesLoadTask]
}
override protected def onDestroy() {
super.onDestroy()
if (mTask != null && mTask.getStatus != AsyncTask.Status.FINISHED) {
mTask.cancel(true)
mTask = null
}
cleanupRenameDialog()
}
private def checkForEmpty() {
if (mAdapter.getCount == 0) {
mEmpty setText R.string.gestures_empty
}
}
override protected def onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
if (mCurrentRenameGesture != null) {
outState.putLong(GESTURES_INFO_ID, mCurrentRenameGesture.gesture.getID)
}
}
override protected def onRestoreInstanceState(state: Bundle) {
super.onRestoreInstanceState(state)
val id = state.getLong(GESTURES_INFO_ID, -1)
if (id != -1) {
val entries = new JSetWrapper(sStore.getGestureEntries)
var found = false
for (name <- entries if !found) {
val gestures = new JListWrapper(sStore.getGestures(name))
for (gesture <- gestures if !found) {
if (gesture.getID == id) {
mCurrentRenameGesture = new NamedGesture(name, gesture)
found = true
}
}
}
}
}
override def onCreateContextMenu(menu: ContextMenu, v: View,
menuInfo: ContextMenu.ContextMenuInfo) {
super.onCreateContextMenu(menu, v, menuInfo)
val info = menuInfo.asInstanceOf[AdapterView.AdapterContextMenuInfo]
menu setHeaderTitle info.targetView.asInstanceOf[TextView].getText
menu.add(0, MENU_ID_RENAME, 0, R.string.gestures_rename)
menu.add(0, MENU_ID_REMOVE, 0, R.string.gestures_delete)
}
override def onContextItemSelected(item: MenuItem): Boolean = {
val menuInfo =
item.getMenuInfo.asInstanceOf[AdapterView.AdapterContextMenuInfo]
val gesture = menuInfo.targetView.getTag.asInstanceOf[NamedGesture]
item.getItemId match {
case MENU_ID_RENAME =>
renameGesture(gesture)
true
case MENU_ID_REMOVE =>
deleteGesture(gesture)
true
case _ =>
super.onContextItemSelected(item)
}
}
private def renameGesture(gesture: NamedGesture) {
mCurrentRenameGesture = gesture
showDialog(DIALOG_RENAME_GESTURE)
}
override protected def onCreateDialog(id: Int): Dialog = {
if (id == DIALOG_RENAME_GESTURE)
createRenameDialog()
else
super.onCreateDialog(id)
}
override protected def onPrepareDialog(id: Int, dialog: Dialog) {
super.onPrepareDialog(id, dialog)
if (id == DIALOG_RENAME_GESTURE) {
mInput setText mCurrentRenameGesture.name
}
}
private def createRenameDialog(): Dialog = {
val layout = View.inflate(this, R.layout.dialog_rename, null)
mInput = layout.findViewById(R.id.name).asInstanceOf[EditText]
val tv = layout.findViewById(R.id.label).asInstanceOf[TextView]
tv setText R.string.gestures_rename_label
val builder = new AlertDialog.Builder(this)
builder setIcon 0
builder setTitle getString(R.string.gestures_rename_title)
builder setCancelable true
builder setOnCancelListener new /*Dialog*/DialogInterface.OnCancelListener {
def onCancel(dialog: DialogInterface) {
cleanupRenameDialog()
}
}
builder.setNegativeButton(getString(R.string.cancel_action),
new /*Dialog*/DialogInterface.OnClickListener {
def onClick(dialog: DialogInterface, which: Int) {
cleanupRenameDialog()
}
}
)
builder.setPositiveButton(getString(R.string.rename_action),
new /*Dialog*/DialogInterface.OnClickListener() {
def onClick(dialog: DialogInterface, which: Int) {
changeGestureName()
}
}
)
builder setView layout
builder.create()
}
private def changeGestureName() {
val name = mInput.getText.toString
if (!TextUtils.isEmpty(name)) {
val renameGesture = mCurrentRenameGesture
val adapter = mAdapter
val count = adapter.getCount
// Simple linear search, there should not be enough items to warrant
// a more sophisticated search
var found = false
for (i <- 0 until count if !found) {
val gesture = adapter.getItem(i)
if (gesture.gesture.getID == renameGesture.gesture.getID) {
sStore.removeGesture(gesture.name, gesture.gesture)
gesture.name = mInput.getText.toString
sStore.addGesture(gesture.name, gesture.gesture)
found = true
}
}
adapter.notifyDataSetChanged()
}
mCurrentRenameGesture = null
}
private def cleanupRenameDialog() {
if (mRenameDialog != null) {
mRenameDialog.dismiss()
mRenameDialog = null
}
mCurrentRenameGesture = null
}
private def deleteGesture(gesture: NamedGesture) {
sStore.removeGesture(gesture.name, gesture.gesture)
sStore.save()
val adapter = mAdapter
adapter setNotifyOnChange false
adapter remove gesture
adapter sort mSorter
checkForEmpty()
adapter.notifyDataSetChanged()
Toast.makeText(this, R.string.gestures_delete_success,
Toast.LENGTH_SHORT).show()
}
private class GesturesLoadTask extends AsyncTask[AnyRef, NamedGesture, Int] {
private var mThumbnailSize: Int = _
private var mThumbnailInset: Int = _
private var mPathColor: Int = _
override protected def onPreExecute() {
super.onPreExecute()
val resources = getResources
mPathColor = resources getColor R.color.gesture_color
mThumbnailInset =
resources.getDimension(R.dimen.gesture_thumbnail_inset).toInt
mThumbnailSize =
resources.getDimension(R.dimen.gesture_thumbnail_size).toInt
findViewById(R.id.addButton) setEnabled false
findViewById(R.id.reloadButton) setEnabled false
mAdapter setNotifyOnChange false
mAdapter.clear()
}
// temporary workaround (compiler bug !?)
private def publishProgress1(values: NamedGesture*) {
super.publishProgress(values: _*)
}
override protected def doInBackground(params: AnyRef*): Int = {
if (isCancelled())
STATUS_CANCELLED
else if (! (Environment.MEDIA_MOUNTED equals
Environment.getExternalStorageState))
STATUS_NO_STORAGE
else if (sStore.load()) {
val entries = new JSetWrapper(sStore.getGestureEntries)
for (name <- entries if !isCancelled()) {
val gestures = new JListWrapper(sStore.getGestures(name))
for (gesture <- gestures) {
val bitmap = gesture.toBitmap(mThumbnailSize, mThumbnailSize,
mThumbnailInset, mPathColor)
val namedGesture = new NamedGesture(name, gesture)
mAdapter.addBitmap(namedGesture.gesture.getID, bitmap)
publishProgress1(namedGesture)
}
}
STATUS_SUCCESS
} else
STATUS_NOT_LOADED
}
override protected def onProgressUpdate(values: NamedGesture*) {
super.onProgressUpdate(values: _*)
val adapter = mAdapter
adapter setNotifyOnChange false
for (gesture <- values) {
adapter add gesture
}
adapter sort mSorter
adapter.notifyDataSetChanged()
}
override protected def onPostExecute(result: Int) {
super.onPostExecute(result)
if (result == STATUS_NO_STORAGE) {
getListView setVisibility View.GONE
mEmpty setVisibility View.VISIBLE
mEmpty setText getString(R.string.gestures_error_loading,
mStoreFile.getAbsolutePath)
} else {
findViewById(R.id.addButton) setEnabled true
findViewById(R.id.reloadButton) setEnabled true
checkForEmpty()
}
}
}
private class GesturesAdapter(context: Context)
extends ArrayAdapter[NamedGesture](context, 0) {
private val mInflater: LayoutInflater =
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE).asInstanceOf[LayoutInflater]
private val mThumbnails =
new HashMap[Long, Drawable] with SynchronizedMap[Long, Drawable]
def addBitmap(id: Long, bitmap: Bitmap) {
mThumbnails(id) = new BitmapDrawable(bitmap)
}
override def getView(position: Int, convertView: View,
parent: ViewGroup): View = {
val convertView1 = if (convertView == null)
mInflater.inflate(R.layout.gestures_item, parent, false)
else
conve
안드로이드 제스쳐
reating a gestures library
Loading the gestures library
Recognizing gestures
Gestures overlay
==================== |
|
Like it or not touch screens are becoming part of both developers and users life, i mean dont think i would buy one of Those uncool phones without touchscreen unless i really have to, would you? but the important point is that what is the Touchscreen use if applications doesn't support touchscreen interaction, in other words who is really gonna pay for a ,say, Picture management application if it does not support some gestures for switching between pictures or zooming? [ Gesture Detecti
on in Android ].
In Android there are three levels of touch screen event handling mechanism which can be used by developers. the most low level technique is to receive all touch events and take care of all things, you can attach an 'OnTouchListener' to a view and get notified whenever there is a touch event or you can override onTouchEvent() or dispatchTouchEvent() method of your activity or view, in all these cases you would be dealing with an instance of MotionEvent every single time and you would have to detect what user is doing all on your own which will suit requirments for developing games and stuff like that. But it is just too much hassle if you only need a few simple gestures for your application.
Next approach is to use GestureDetector class along with OnGestureListener and/or OnDoubleTapListener, in this technique whenever there is a new MotionEvent you have to pass it to Gesture Detector's onTouchEvent() method, it then will analyse this event and previous events and tell you what is happening on the screen by calling some of the callback methods.
here is a simple activity which uses GestureDetector :
public class SimpleActivity extends Activity
implements OnDoubleTapListener,
android.view.GestureDetector.OnGestureListener {
private GestureDetector detector;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.simple);
detector = new GestureDetector(this,this);
}
@Override
public boolean onTouchEvent(MotionEvent me)
{
this.detector.onTouchEvent(me);
return super.onTouchEvent(me);
}
//@Override
public boolean onDown(MotionEvent e)
{
Log.d("---onDown----",e.toString());
return false;
}
//@Override
public boolean onFling(MotionEvent e1,
MotionEvent e2, float velocityX,
float velocityY)
{
Log.d("---onFling---",e1.toString()+e2.toString());
return false;
}
//@Override
public void onLongPress(MotionEvent e)
{
Log.d("---onLongPress---",e.toString());
}
//@Override
public boolean onScroll(MotionEvent e1,
MotionEvent e2, float distanceX, float distanceY)
{
Log.d("---onScroll---",e1.toString()+e2.toString());
return false;
}
//@Override
public void onShowPress(MotionEvent e)
{
Log.d("---onShowPress---",e.toString());
}
//@Override
public boolean onSingleTapUp(MotionEvent e)
{
Log.d("---onSingleTapUp---",e.toString());
return false;
}
//@Override
public boolean onDoubleTap(MotionEvent e)
{
Log.d("---onDoubleTap---",e.toString());
return false;
}
//@Override
public boolean onDoubleTapEvent(MotionEvent e)
{
Log.d("---onDoubleTapEvent---",e.toString());
return false;
}
//@Override
public boolean onSingleTapConfirmed(MotionEvent e)
{
Log.d("---onSingleTapConfirmed---",e.toString());
return false;
}
} |
if we want to understand all gesture types and how they work, first of all we need to know three basic MotionEvents which can combine with each other and create some gestures, these three Events are Action_Down , Action_Move and Action_Up , each time you touch the screen an Action_Down occurs and when you start moving it will create Action_Move event and finally when you take your finger off the screen an Action_Up Event will be created.
onDown() is called simply when there is an Action_Down event.
onShowPress() is called after onDown() and before any other callback method, I found out it sometimes might not get called for example when you tap on the screen so fast, but it's actually what this method all about, to make a distinction between a possible unintentional touch and an intentional one.
onSingleTapUp() is called when there is a Tap Gesture. Tap Gesture happens when an Action_Down event followed by an Action_Up event(like a Single-Click).
onDoubleTap() is called when there is two consecutive Tap gesture(like a Double-Click).
onSingleTapConfirmed() is so similar to onSingleTapUp() but it is only get called when the detected tap gesture is definitely a single Tap and not part of a double tap gesture.
Here is the sequence in which these callback methods are called when we tap on the screen:
onDown() – onShowPress() - onSingleTapUp() – onSingleTapConfirmed()
and here is when we do a double tap:
onDown() – onShowPress() - onSingleTapUp() – onDoubleTap() – onDoubleTapEvent()
onDown() – onShowPress() – onDoubleTapEvent()
onFling() is called when a Fling Gesture is detected. fling Gesture occurs when there is an Action_Down then some Action_Move events and finally an Action_Up, but they must take place with a specified movement and velocity pattern to be considered as a fling gesture. for example if you put your finger on the screen and start moving it slowly and then remove your finger gently it won’t be count as a fling gesture.
onScroll() is usually called when there is a Action_Move event so if you put your finger on the screen and move it for a few seconds there will be a method call chain like this :
onDown() – onShowPress() – onScroll() - onScroll() - onScroll() - ....... - onScroll()
or If the movement was a Fling Gesture, then there would be a call chain like this :
onDown() – onShowPress() – onScroll() - onScroll() - onScroll() - ....... – onFling()
If there is an Action_Move event between first tap and second tap of a doubleTap gesture it will be handled by calling onDoubleTapEvent() instead of onScroll() method. onDoubleTapEvent() receives all Action_Up events of a doubleTap gesture as well.
Remember that if we touch the screen and don’t remove our finger for a specified amount of time onLongPress() method is called and in most cases there will be no further event callback regardless of whatever we do after that, moving or removing our finger. we can easily change this behavior of detector by calling setIsLongpressEnabled() method of GestureDetector class.
although GestureDetector makes our life much easier, it still could be a real pain in the ass if we would need to handle some complicated gestures, imagine you need an application which should do task1 when there is a circle gesture, task2 for rectangle gesture and task3 for triangle gesture. obviously it would not be so pleasant to deal with such a situation with those mechanism we have seen so far, it's actually when Gesture API and Gesture Builder Application come into play.
Gesture Builder Application comes with Android emulator as a pre-installed app, It helps us to simply make new gestures and save them on the device, then we can retrieve and detect those gestures later using Gesture API.
I'm not gonna talk about Gesture Builder app here, 제스처 빌더를 여기서 말하려 하는거 아니다.
since there is a pretty good Article about it on Android Developers blog.
// 제스처 오버레이 뷰 클래스
the only thing I'd like to mention here is GestureOverlayView class,
It is actually just a transparent FrameLayout which can detect gestures but the thing is it can be used in two completely different ways, you can either put other views inside it and use it as a parent view or put it is the last child view of a FrameLayout (or any other way which causes it to be placed on top of another view).
//첫번째 시나리오 : 모든 차일드 뷰가 터치 이벤트를 받는다..
In the first Scenario all child views will receive Touch events,
therefore we will be able to press buttons or interact with other widgets as well as doing some gestures, on the other hand if GestureOverlayView has been placed on top, it will swallow all Touch events and no underlay view will be notified for any touch Event.
Although Gesture API brings many useful features for us,
I personally prefer to use GestureDetector for some simple,
basic gestures and honestly I feel like something is missing here,
I mean , apart from games, I would say more than 70% of all gestures
that might be needed in our applications are just a simple sliding in different directions or a double tap.
and that's why I have decided to come up with something easy
which can enable us to handle those 70% as simple as possible. how simple? you might be asking...
here is how our previous activity will look like if we use SimpleGestureFilter class :
public class SimpleActivity extends Activity implements SimpleGestureListener{
private SimpleGestureFilter detector;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
detector = new SimpleGestureFilter(this,this);
}
@Override
public boolean dispatchTouchEvent(MotionEvent me){
this.detector.onTouchEvent(me);
return super.dispatchTouchEvent(me);
}
@Override
public void onSwipe(int direction) {
String str = "";
switch (direction) {
case SimpleGestureFilter.SWIPE_RIGHT : str = "Swipe Right";
break;
case SimpleGestureFilter.SWIPE_LEFT : str = "Swipe Left";
break;
case SimpleGestureFilter.SWIPE_DOWN : str = "Swipe Down";
break;
case SimpleGestureFilter.SWIPE_UP : str = "Swipe Up";
break;
}
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
@Override
public void onDoubleTap() {
Toast.makeText(this, "Double Tap", Toast.LENGTH_SHORT).show();
}
}
|
and here is SimpleGestureFilter source code :
public class SimpleGestureFilter extends SimpleOnGestureListener{
public final static int SWIPE_UP = 1;
public final static int SWIPE_DOWN = 2;
public final static int SWIPE_LEFT = 3;
public final static int SWIPE_RIGHT = 4;
public final static int MODE_TRANSPARENT = 0;
public final static int MODE_SOLID = 1;
public final static int MODE_DYNAMIC = 2;
private final static int ACTION_FAKE = -13; //just an unlikely number
private int swipe_Min_Distance = 100;
private int swipe_Max_Distance = 350;
private int swipe_Min_Velocity = 100;
private int mode = MODE_DYNAMIC;
private boolean running = true;
private boolean tapIndicator = false;
private Activity context;
private GestureDetector detector;
private SimpleGestureListener listener;
public SimpleGestureFilter(Activity context,SimpleGestureListener sgl) {
this.context = context;
this.detector = new GestureDetector(context, this);
this.listener = sgl;
}
public void onTouchEvent(MotionEvent event){
if(!this.running)
return;
boolean result = this.detector.onTouchEvent(event);
if(this.mode == MODE_SOLID)
event.setAction(MotionEvent.ACTION_CANCEL);
else if (this.mode == MODE_DYNAMIC) {
if(event.getAction() == ACTION_FAKE)
event.setAction(MotionEvent.ACTION_UP);
else if (result)
event.setAction(MotionEvent.ACTION_CANCEL);
else if(this.tapIndicator){
event.setAction(MotionEvent.ACTION_DOWN);
this.tapIndicator = false;
}
}
//else just do nothing, it's Transparent
}
public void setMode(int m){
this.mode = m;
}
public int getMode(){
return this.mode;
}
public void setEnabled(boolean status){
this.running = status;
}
public void setSwipeMaxDistance(int distance){
this.swipe_Max_Distance = distance;
}
public void setSwipeMinDistance(int distance){
this.swipe_Min_Distance = distance;
}
public void setSwipeMinVelocity(int distance){
this.swipe_Min_Velocity = distance;
}
public int getSwipeMaxDistance(){
return this.swipe_Max_Distance;
}
public int getSwipeMinDistance(){
return this.swipe_Min_Distance;
}
public int getSwipeMinVelocity(){
return this.swipe_Min_Velocity;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
final float xDistance = Math.abs(e1.getX() - e2.getX());
final float yDistance = Math.abs(e1.getY() - e2.getY());
if(xDistance > this.swipe_Max_Distance || yDistance > this.swipe_Max_Distance)
return false;
velocityX = Math.abs(velocityX);
velocityY = Math.abs(velocityY);
boolean result = false;
if(velocityX > this.swipe_Min_Velocity && xDistance > this.swipe_Min_Distance){
if(e1.getX() > e2.getX()) // right to left
this.listener.onSwipe(SWIPE_LEFT);
else
this.listener.onSwipe(SWIPE_RIGHT);
result = true;
}
else if(velocityY > this.swipe_Min_Velocity && yDistance > this.swipe_Min_Distance){
if(e1.getY() > e2.getY()) // bottom to up
this.listener.onSwipe(SWIPE_UP);
else
this.listener.onSwipe(SWIPE_DOWN);
result = true;
}
return result;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
this.tapIndicator = true;
return false;
}
@Override
public boolean onDoubleTap(MotionEvent arg0) {
this.listener.onDoubleTap();;
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent arg0) {
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent arg0) {
if(this.mode == MODE_DYNAMIC){ // we owe an ACTION_UP, so we fake an
arg0.setAction(ACTION_FAKE); //action which will be converted to an ACTION_UP later.
this.context.dispatchTouchEvent(arg0);
}
return false;
}
static interface SimpleGestureListener{
void onSwipe(int direction);
void onDoubleTap();
}
}
|
as you can see clients of these class can determine the minimum and maximum distance and also minimum velocity which is required for a movement on screen to be considered as a Swipe Gesture,
I also thought it would be great if our filter can behave differently like what GestureOverlayView
can do and even more than that!
this Filter can run in three different mode:
Transparent, Solid and Dynamic.
in Transparent mode
it will work just like when we have a GestureOverlayView as parent: all views will receive Touch events;
Solid mode works
like when we put a GestureOverlayView as a child view:
no one will receive TouchEvent, it is not as efficient as GestureOverlayView is,
since we actually let all events get passed but what we do is we literally kill them
when they are passing through our filter ;).
the last mode is Dynamic mode,
the primary purpose of this mode is to have a bit smarter gesture detection, i mean there has been sometimes that i wanted to slide from one page to another, but a button get pressed and something else happens.
it does not happen so much but it is really annoying. what i tried to do in Dynamic mode is to distinguish between a swipe/double tap gesture and a movement which is neither of them.
so if you have a view full of buttons and other interactive stuff and user does a swipe or double tap gesture, it is guaranteed (although i believe there is no such thing as guarantee in life ;) )
that no other event callback will be called but only onSwipe() or onDoubleTap().
Anyway that's what i came up with to take the pain away in those circumstances
when we just need to handle some simple Gestures.
hope it will be helpful for you and can make your life a bit easier.
http://android-journey.blogspot.com/2010/01/android-gestures.html
Gesture Builder APK
http://code.google.com/p/quickdroid/downloads/detail?name=com.android.gesture.builder.apk&can=2&q=
Source
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.gesture.builder
import _root_.android.app.{Dialog, AlertDialog, ListActivity}
import _root_.android.app.Activity._
import _root_.android.os.{AsyncTask, Bundle, Environment}
import _root_.android.view.{ContextMenu, View, MenuItem, LayoutInflater, ViewGroup}
import _root_.android.gesture.{Gesture, GestureLibrary, GestureLibraries}
import _root_.android.widget.{AdapterView, ArrayAdapter, EditText, TextView, Toast}
import _root_.android.content.{Context, DialogInterface, Intent}
import _root_.android.content.res.Resources
import _root_.android.text.TextUtils
import _root_.android.graphics.Bitmap
import _root_.android.graphics.drawable.{BitmapDrawable, Drawable}
import java.util.Comparator
import java.io.File
import scala.collection.JavaConversions.{JListWrapper, JSetWrapper}
import scala.collection.mutable.{HashMap, SynchronizedMap}
object GestureBuilderActivity {
private final val STATUS_SUCCESS = 0
private final val STATUS_CANCELLED = 1
private final val STATUS_NO_STORAGE = 2
private final val STATUS_NOT_LOADED = 3
private final val MENU_ID_RENAME = 1
private final val MENU_ID_REMOVE = 2
private final val DIALOG_RENAME_GESTURE = 1
private final val REQUEST_NEW_GESTURE = 1
// Type: long (id)
private final val GESTURES_INFO_ID = "gestures.info_id"
private final val mStoreFile =
new File(Environment.getExternalStorageDirectory, "gestures")
private final val sStore = GestureLibraries.fromFile(mStoreFile)
def getStore: GestureLibrary = sStore
class NamedGesture(var name: String, var gesture: Gesture)
}
class GestureBuilderActivity extends ListActivity {
import GestureBuilderActivity._ // companion object
private final val mSorter = new Comparator[NamedGesture]() {
def compare(object1: NamedGesture, object2: NamedGesture): Int = {
object1.name compareTo object2.name
}
}
private var mAdapter: GesturesAdapter = _
private var mTask: GesturesLoadTask = _
private var mEmpty: TextView = _
private var mRenameDialog: Dialog = _
private var mInput: EditText = _
private var mCurrentRenameGesture: NamedGesture = _
override protected def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.gestures_list)
mAdapter = new GesturesAdapter(this)
setListAdapter(mAdapter)
mEmpty = findViewById(android.R.id.empty).asInstanceOf[TextView]
loadGestures()
registerForContextMenu(getListView)
}
//@SuppressWarnings({"UnusedDeclaration"})
def reloadGestures(v: View) {
loadGestures()
}
//@SuppressWarnings({"UnusedDeclaration"})
def addGesture(v: View) {
val intent = new Intent(this, classOf[CreateGestureActivity])
startActivityForResult(intent, REQUEST_NEW_GESTURE)
}
override protected def onActivityResult(requestCode: Int,
resultCode: Int, data: Intent) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
requestCode match {
case REQUEST_NEW_GESTURE =>
loadGestures()
}
}
}
private def loadGestures() {
if (mTask != null && mTask.getStatus != AsyncTask.Status.FINISHED) {
mTask cancel true
}
mTask = new GesturesLoadTask().execute().asInstanceOf[GesturesLoadTask]
}
override protected def onDestroy() {
super.onDestroy()
if (mTask != null && mTask.getStatus != AsyncTask.Status.FINISHED) {
mTask.cancel(true)
mTask = null
}
cleanupRenameDialog()
}
private def checkForEmpty() {
if (mAdapter.getCount == 0) {
mEmpty setText R.string.gestures_empty
}
}
override protected def onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
if (mCurrentRenameGesture != null) {
outState.putLong(GESTURES_INFO_ID, mCurrentRenameGesture.gesture.getID)
}
}
override protected def onRestoreInstanceState(state: Bundle) {
super.onRestoreInstanceState(state)
val id = state.getLong(GESTURES_INFO_ID, -1)
if (id != -1) {
val entries = new JSetWrapper(sStore.getGestureEntries)
var found = false
for (name <- entries if !found) {
val gestures = new JListWrapper(sStore.getGestures(name))
for (gesture <- gestures if !found) {
if (gesture.getID == id) {
mCurrentRenameGesture = new NamedGesture(name, gesture)
found = true
}
}
}
}
}
override def onCreateContextMenu(menu: ContextMenu, v: View,
menuInfo: ContextMenu.ContextMenuInfo) {
super.onCreateContextMenu(menu, v, menuInfo)
val info = menuInfo.asInstanceOf[AdapterView.AdapterContextMenuInfo]
menu setHeaderTitle info.targetView.asInstanceOf[TextView].getText
menu.add(0, MENU_ID_RENAME, 0, R.string.gestures_rename)
menu.add(0, MENU_ID_REMOVE, 0, R.string.gestures_delete)
}
override def onContextItemSelected(item: MenuItem): Boolean = {
val menuInfo =
item.getMenuInfo.asInstanceOf[AdapterView.AdapterContextMenuInfo]
val gesture = menuInfo.targetView.getTag.asInstanceOf[NamedGesture]
item.getItemId match {
case MENU_ID_RENAME =>
renameGesture(gesture)
true
case MENU_ID_REMOVE =>
deleteGesture(gesture)
true
case _ =>
super.onContextItemSelected(item)
}
}
private def renameGesture(gesture: NamedGesture) {
mCurrentRenameGesture = gesture
showDialog(DIALOG_RENAME_GESTURE)
}
override protected def onCreateDialog(id: Int): Dialog = {
if (id == DIALOG_RENAME_GESTURE)
createRenameDialog()
else
super.onCreateDialog(id)
}
override protected def onPrepareDialog(id: Int, dialog: Dialog) {
super.onPrepareDialog(id, dialog)
if (id == DIALOG_RENAME_GESTURE) {
mInput setText mCurrentRenameGesture.name
}
}
private def createRenameDialog(): Dialog = {
val layout = View.inflate(this, R.layout.dialog_rename, null)
mInput = layout.findViewById(R.id.name).asInstanceOf[EditText]
val tv = layout.findViewById(R.id.label).asInstanceOf[TextView]
tv setText R.string.gestures_rename_label
val builder = new AlertDialog.Builder(this)
builder setIcon 0
builder setTitle getString(R.string.gestures_rename_title)
builder setCancelable true
builder setOnCancelListener new /*Dialog*/DialogInterface.OnCancelListener {
def onCancel(dialog: DialogInterface) {
cleanupRenameDialog()
}
}
builder.setNegativeButton(getString(R.string.cancel_action),
new /*Dialog*/DialogInterface.OnClickListener {
def onClick(dialog: DialogInterface, which: Int) {
cleanupRenameDialog()
}
}
)
builder.setPositiveButton(getString(R.string.rename_action),
new /*Dialog*/DialogInterface.OnClickListener() {
def onClick(dialog: DialogInterface, which: Int) {
changeGestureName()
}
}
)
builder setView layout
builder.create()
}
private def changeGestureName() {
val name = mInput.getText.toString
if (!TextUtils.isEmpty(name)) {
val renameGesture = mCurrentRenameGesture
val adapter = mAdapter
val count = adapter.getCount
// Simple linear search, there should not be enough items to warrant
// a more sophisticated search
var found = false
for (i <- 0 until count if !found) {
val gesture = adapter.getItem(i)
if (gesture.gesture.getID == renameGesture.gesture.getID) {
sStore.removeGesture(gesture.name, gesture.gesture)
gesture.name = mInput.getText.toString
sStore.addGesture(gesture.name, gesture.gesture)
found = true
}
}
adapter.notifyDataSetChanged()
}
mCurrentRenameGesture = null
}
private def cleanupRenameDialog() {
if (mRenameDialog != null) {
mRenameDialog.dismiss()
mRenameDialog = null
}
mCurrentRenameGesture = null
}
private def deleteGesture(gesture: NamedGesture) {
sStore.removeGesture(gesture.name, gesture.gesture)
sStore.save()
val adapter = mAdapter
adapter setNotifyOnChange false
adapter remove gesture
adapter sort mSorter
checkForEmpty()
adapter.notifyDataSetChanged()
Toast.makeText(this, R.string.gestures_delete_success,
Toast.LENGTH_SHORT).show()
}
private class GesturesLoadTask extends AsyncTask[AnyRef, NamedGesture, Int] {
private var mThumbnailSize: Int = _
private var mThumbnailInset: Int = _
private var mPathColor: Int = _
override protected def onPreExecute() {
super.onPreExecute()
val resources = getResources
mPathColor = resources getColor R.color.gesture_color
mThumbnailInset =
resources.getDimension(R.dimen.gesture_thumbnail_inset).toInt
mThumbnailSize =
resources.getDimension(R.dimen.gesture_thumbnail_size).toInt
findViewById(R.id.addButton) setEnabled false
findViewById(R.id.reloadButton) setEnabled false
mAdapter setNotifyOnChange false
mAdapter.clear()
}
// temporary workaround (compiler bug !?)
private def publishProgress1(values: NamedGesture*) {
super.publishProgress(values: _*)
}
override protected def doInBackground(params: AnyRef*): Int = {
if (isCancelled())
STATUS_CANCELLED
else if (! (Environment.MEDIA_MOUNTED equals
Environment.getExternalStorageState))
STATUS_NO_STORAGE
else if (sStore.load()) {
val entries = new JSetWrapper(sStore.getGestureEntries)
for (name <- entries if !isCancelled()) {
val gestures = new JListWrapper(sStore.getGestures(name))
for (gesture <- gestures) {
val bitmap = gesture.toBitmap(mThumbnailSize, mThumbnailSize,
mThumbnailInset, mPathColor)
val namedGesture = new NamedGesture(name, gesture)
mAdapter.addBitmap(namedGesture.gesture.getID, bitmap)
publishProgress1(namedGesture)
}
}
STATUS_SUCCESS
} else
STATUS_NOT_LOADED
}
override protected def onProgressUpdate(values: NamedGesture*) {
super.onProgressUpdate(values: _*)
val adapter = mAdapter
adapter setNotifyOnChange false
for (gesture <- values) {
adapter add gesture
}
adapter sort mSorter
adapter.notifyDataSetChanged()
}
override protected def onPostExecute(result: Int) {
super.onPostExecute(result)
if (result == STATUS_NO_STORAGE) {
getListView setVisibility View.GONE
mEmpty setVisibility View.VISIBLE
mEmpty setText getString(R.string.gestures_error_loading,
mStoreFile.getAbsolutePath)
} else {
findViewById(R.id.addButton) setEnabled true
findViewById(R.id.reloadButton) setEnabled true
checkForEmpty()
}
}
}
private class GesturesAdapter(context: Context)
extends ArrayAdapter[NamedGesture](context, 0) {
private val mInflater: LayoutInflater =
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE).asInstanceOf[LayoutInflater]
private val mThumbnails =
new HashMap[Long, Drawable] with SynchronizedMap[Long, Drawable]
def addBitmap(id: Long, bitmap: Bitmap) {
mThumbnails(id) = new BitmapDrawable(bitmap)
}
override def getView(position: Int, convertView: View,
parent: ViewGroup): View = {
val convertView1 = if (convertView == null)
mInflater.inflate(R.layout.gestures_item, parent, false)
else
convertView
val gesture = getItem(position)
val label = convertView1.asInstanceOf[TextView]
label setTag gesture
label setText gesture.name
label.setCompoundDrawablesWithIntrinsicBounds(
mThumbnails(gesture.gesture.getID), null, null, null)
convertView1
}
}
} | http://www.assembla.com/code/scala-eclipse-toolchain/git/nodes/docs/android-examples/GestureBuilder/src/com/android/gesture/builder/GestureBuilderActivity.scala?rev=334c117e3292c12e728639de6c19056b3513d877 rtView
val gesture = getItem(position)
val label = convertView1.asInstanceOf[TextView]
label setTag gesture
label setText gesture.name
label.setCompoundDrawablesWithIntrinsicBounds(
mThumbnails(gesture.gesture.getID), null, null, null)
convertView1
}
}
} |