Click here to Skip to main content
15,868,016 members
Articles / Mobile Apps / Android

A Domain Specific Language for Android touch events: Part 1: Description and usage of the DSL

Rate me:
Please Sign up or sign in to vote.
4.98/5 (14 votes)
6 Dec 2013CPOL9 min read 46K   467   31   15
A DSL for creating touch gestures in Android.

Contents

Introduction

The following article describes a DSL allowing to construct sentences describing actions and conditions to be fulfilled when touching the screen of an Android device.

It is the first part of a two part series:

  1. A Domain Specific Language for Android touch events: Part 1: Description and usage of the DSL
  2. A Domain Specific Language for Android touch events: Part 2: Construction and inner workings

The basic idea

In writing a custom control (on which a later article will follow) I came to the conclusion that most of the time, when handling touch events you have a sequence of events and certain conditions on which you want to respond. If the sequence is not correct or a condition is not fulfilled, then you don't want anything to happen.

A small example will make this clear: 

Let's say you have a view like this

Image 1

And you want to perform the following gestures:

  • When you touch the screen and if you touch on any of the rectangles, then next, while moving, you want the touched rectangle to move
  • When you touch the screen and you touch none of the rectangles, then next, while moving, you want to perform a panning like movement
  • When you click the screen on any of the rectangles, then show an action menu
  • When you long touch the screen on any of the rectangles, then show some data about the rectangle
Would it not be great if you could write sentences like:
  • ontouchdown if(on rectangle) andnext move do(move selected rectangle)
  • ontouchdown if(not on rectangle) andnext move do(panning)
A "click" is a composite of a touchdown and a touchup in which the touchup must be within a certain timeframe of the touchdown, so you get something like:
  • ontouchdown if(on rectangle) andnext touchup if (within 1 second of touchdown) then do(show action menu)
A "long touch" requires that nothing happens for a certain timeframe:

  • ontouchdown if(on rectangle) and if(nothinghappened during 4 seconds) then do(show data about rectangle)

Supported sentences

Main sentences

The standard sentence structure is based on the normal event sequence of touch events:

Java
ontouchdown().andnext().move().andnext().touchup()

It is of course also possible to have multiple clicks, so what you actually get is a chaining of the above:

Java
ontouchdown().andnext().move().andnext().touchup().
    andnext().touchdown.andnext().touchup() 
    // and so on ...

The move event can be optional. For example during a click event you would think you'd have a touchdown followed by a touchup but in reality however you will almost always have a small movement in between. For this following sentences are supported:

Java
ontouchdown().andnext().canmove().andnext().touchup()

And we actually would like to do something when any of the touch events happen, so after each type of event we can give an action to perform. For example, performing an action after a touchdown event:

Java
ontouchdown().do(youractionhere())

And also, you may not want to perform the action always, but only if a certain condition is met. This then results in sentences of the form:

Java
// an if then else structure
touchdown().if(<cond>).do(<action>).else().do(<action>)

// only cotinue to the move if the condition on the touchdown is true
touchdown().if(<cond>).andnext().move().do(<action>)

Subsentences for conditions

Mostly conditions will be custom conditions implemented by yourself. However a few conditions can be identified which have to do with the touch events themselves. Following are some examples:

Java
touchup.if(within.seconds(x).from.touchdown).do2(act)

move.if(within.millimeters(x).from.touchdown).do2(act)

move.if(exceed.millimeters(x).from.point(posx,posy)).do2(act) // not yet implemented

touchdown.if(nothinghappened.during.seconds(x).except.move.within.millimeters(x))

Constructing gestures

Constructing gestures is done by overriding the class GestureBuilder. The basic structure of the code is thus:

Java
public class SampleGesture extends GestureBuilder<YourViewClass> {
    
    public SampleGesture(YourViewClass view)
    {
        super(view);
    }
    
    public TouchGesture create()
    {
        TouchGesture gesture = new TouchGesture("SampleGestureName");
        
        this.Create(gesture).TouchDown()
                .If(SomeCondition())
            .AndNext().CanMove()
            .AndNext().TouchUp()
                    .Do2(YourAction())
        ;
        
        return gesture;
    }
    
    // more code here
}

By inheriting your class from GestureBuilder you get access to the methods implementing the standard conditions and actions, like for example, the within() construction in the above example.

Supported start-words for conditions are:

  • exceed(): Specify a distance or time range condition
  • within(): Specify a distance or time range condition
  • not(): Negate a condition

Supported start-words for actions are:

  • nothing(): Do nothing
  • after(): Start a timer to perform some action
  • endCurrentTimer(): End a running timer. The action for the timer will not be performed.
  • invalidateGesture(): Invalidate the gesture
  • gestureIsCompleted(): Set the gesture as completed
At the same time you can provide your own methods implementing custom conditions and actions. Methods providing conditions must return an object of a type implementing the IGestureCondition interface, those providing actions must return an object of a type implementing the IGestureAction interface.

Java
public class SampleGesture extends GestureBuilder<YourViewClass> {
    
    // see above for the constructor and create method
    
    IGestureCondition SomeCondition()
    {
        // return an object of a type implementing the interface
    }
 
    IGestureAction YourAction()
    {
        // return an object of a type implementing the interface
    }
}

The interface IGestureConditionis defined like:

Java
public interface IGestureCondition {
    boolean checkCondition(GestureEvent motion, TouchGesture gesture);
}

The interface IGestureAction is defined like:

Java
public interface IGestureAction {
    void executeAction(GestureEvent motion, TouchGesture gesture);
}

Often you will want to check things using properties of the touch events, like for example if you did touch inside a certain area of the view. Or you will want to take actions based on properties of the touch events. For this, the main methods of the interfaces have the GestureEvent motion and the TouchGesture gesture parameters. The motion parameter provides you the event on which your condition is checked or your action is executed. The GestureEvent class has methods to get the position and time properties of the event:

Java
public class GestureEvent {
    public GestureEvent(MotionEvent event)
    {
        androidEvent = event;
        position = new ScreenVector((int)androidEvent.getX(), (int)androidEvent.getY());
    }
    
    public ScreenVector getPosition()
    {
        return position;
    }
    
    public long getTime()
    {
        return androidEvent.getEventTime();
    }
    
    ScreenVector position;
    MotionEvent androidEvent;
}

The gesture parameter represents the gesture for which the event is evaluated. The TouchGesture class provides methods for storing data:

Java
public class TouchGesture implements IResetable  {
 
    // more code ...
    
    public boolean contextExists(String key)
    {
        return context.containsKey(key);
    }
    
    public void addContext(String key, Object data)
    {
        context.put(key, data);
    }
    
    public void removeContext(String key)
    {
        context.remove(key);
    }
    
    public Object getContext(String key)
    {
        return context.get(key);
    }
}

There are a number of default keys available in the TouchHandler class to get at data automatically stored for each gesture:

  • TouchHandler.ActionDownPos: the position where the screen was touched
  • TouchHandler.ActionDownTime: the time at which the screen was touched
  • TouchHandler.ActionMovePos: the last position of a continuous series of move events
  • TouchHandler.ActionMoveTime: the last time at which a continuous series of move events happened
  • TouchHandler.ActionUpPos: the position at which a touchup event happened
  • TouchHandler.ActionUpTime: the time at which the touchup event happened

It is of course possible to have multiple touch events in a single gesture, like for example during a double click gesture. In this case you have two touchdown and two touchup events. For this the TouchHandler class has the static method getEventId:

Java
public static String getEventId(String dataKey, int index)
{
    return dataKey + "_" + ((Integer)index).toString();
}

Mind that the index for the first event is 1 and not zero!

All that often you will want to have access to the view on which your gestures are executed. After all, mostly your actions will change the state of the view. For this, I created the GestureConditionBase<View> and the GestureActionBase<View> classes from which you can derive your own condition and action classes. These give you the opportunity to retrieve the view using the getTouchedView() method.

Java
public abstract class GestureConditionBase<T> implements IGestureCondition {
 
    public GestureConditionBase(T view) {
        touchView = view;
    }
 
    public T getTouchedView()
    {
        return touchView;
    }
    
    private T touchView;
}
 
public abstract class GestureActionBase<T> implements IGestureAction {
 
    public GestureActionBase(T view) {
        touchView = view;
    }
 
    public T getTouchedView()
    {
        return touchView;
    }
    
    private T touchView;
}

Some sample gestures

We have a rather simple view class AndroidGestureDSLView showing a green rectangle as in the image at the beginning of the article. This class has several methods allowing us to manipulate the rectangle, etc... which can be used by the gestures we define.

A Click gesture

Java
public class ClickOnRectangleGesture extends GestureBuilder<AndroidGestureDSLView> {
    
    public ClickOnRectangleGesture(AndroidGestureDSLView view)
    {
        super(view);
    }
    
    public TouchGesture create()
    {
        TouchGesture gesture = new TouchGesture("ClickOnRectangleGesture");
        
        this.Create(gesture).TouchDown()
                .If(OnRectangle())
            .AndNext().CanMove()
                .If(within().milliMeters(2).fromTouchDown(1))
            .AndNext().TouchUp()
                .If(within().seconds(1).fromTouchDown(1))
                    .Do2(ShowMessage("You clicked on the rectangle"))
        ;
        
        return gesture;
    }
    
    IGestureCondition OnRectangle()
    {
        return new OnRectangleCondition(getView());
    }
 
    IGestureAction ShowMessage(String message)
    {
        return new ShowMessageAction(getView(), message);
    }
}

Okay, let's dissect this code:

The touchdown and touchup events will be clear I think, and also the OnRectangle condition and ShowMessage action connected to them. This condition and action are the only custom code in this gesture. All other code is part of the DSL. The within() condition on the touchup event makes sure the touchup happens within a certain timeframe from the touchdown event. This way we can make sure it is a click and not a long click.

The move event might be a little bit more strange: after all, all we want is a click which doesn't really involve a move. However, although this is true in the android emulator, on a real phone the user can make unintentional small movements. All this results in the following code:

Java
.AndNext().CanMove()    // a move CAN happen but is not necesary
    .If(within().milliMeters(2).fromTouchDown(1))    // but when a move happens, it 
                                                    // must be within a small distance 
                                                    // of the touchdown event

There is little "under the hood semantics" in this piece of code. If you write a condition without performing an action, you always say that the condition must be fulfilled. If the condition is not fulfilled, then the gesture is invalidated.

A Double Click gesture

Java
public class DoubleClickOnRectangleGesture extends GestureBuilder<AndroidGestureDSLView> {
    
    public DoubleClickOnRectangleGesture(AndroidGestureDSLView view)
    {
        super(view);
    }
    
    public TouchGesture create()
    {
        TouchGesture gesture = new TouchGesture("DoubleClickOnRectangleGesture");
        
        this.Create(gesture).TouchDown()
                .If(OnRectangle())
            .AndNext().CanMove()
                .If(within().milliMeters(2).fromTouchDown(1))
            .AndNext().TouchUp()
                .Do1(nothing())
            .AndNext().TouchDown()
                .Do1(nothing())
            .AndNext().CanMove()
                .If(within().milliMeters(2).fromTouchDown(2))
            .AndNext().TouchUp()
                .If(within().seconds(1).fromTouchDown(1))
                    .Do2(ShowMessage("You doubleclicked on the rectangle"))
        ;
        
        return gesture;
    }
    
    IGestureCondition OnRectangle()
    {
        return new OnRectangleCondition(getView());
    }
 
    IGestureAction ShowMessage(String message)
    {
        return new ShowMessageAction(getView(), message);
    }
}

I'll keep this one short, because I think it's pretty obvious: it's basically two clicks after each other, but the action and timing constraint are all on the last click.

I just wanted to show it because you should think what would happen if you combined it with the previous click gesture. The click event would of course also trigger which is probably not what you want. To solve this we must handle the click and double click in a single gesture. and with this we arrive at the next gesture.

A combined Click and Double Click gesture

Java
public class ClickAndDoubleClickOnRectangleGesture extends GestureBuilder<AndroidGestureDSLView> {
    
    public ClickAndDoubleClickOnRectangleGesture(AndroidGestureDSLView view)
    {
        super(view);
    }
    
    public TouchGesture create()
    {
        TouchGesture gesture = new TouchGesture("ClickAndDoubleClickOnRectangleGesture");
        
        this.Create(gesture).TouchDown()
                .If(OnRectangle())
            .AndNext().CanMove()
                .If(within().milliMeters(2).fromTouchDown(1))
            .AndNext().TouchUp()
                .Do1(after().seconds(1).Do(
                        ShowMessage("You clicked on the rectangle"),
                        gestureIsCompleted()))
            .AndNext().TouchDown()
                .Do1(endCurrentTimer())
            .AndNext().CanMove()
                .If(within().milliMeters(2).fromTouchDown(1))
            .AndNext().TouchUp()
                .If(within().seconds(2).fromTouchDown(1))
                    .Do2(ShowMessage("You doubleclicked on the rectangle"))
        ;
        
        return gesture;
    }
    
    IGestureCondition OnRectangle()
    {
        return new OnRectangleCondition(getView());
    }
 
    IGestureAction ShowMessage(String message)
    {
        return new ShowMessageAction(getView(), message);
    }
}

Again, most of this will be obvious so I will restrict myself to what's important here: how to differentiate between the click and the double click.

From watching the code you will probably get the basic idea, after all, this is a DSL which should make things more obvious. How do we know we have a Click event and not a Double Click? If the last touchup event of the Click is not immediately followed by a touchdown event. We know this by starting a timer on the touchup event of the Click and destroying it when the second touchup event of the Double Click happens. Now, lets say you have a single click. The second touchup event will never happen, thus the timer will never be cancelled and will fire. The action you want to perform on the Click gesture is connected to this timer and will thus get executed. Lastly, once this action is executed, the gesture is finished so we call the gestureIsCompleted() method.

A Drag gesture

Java
public class DragRectangleGesture extends GestureBuilder<AndroidGestureDSLView> {
    
    public DragRectangleGesture(AndroidGestureDSLView view)
    {
        super(view);
    }
    
    public TouchGesture create()
    {
        TouchGesture gesture = new TouchGesture("DragRectangleGesture");
        
        this.Create(gesture).TouchDown()
                .If(OnRectangle())
                    .Do2(RegisterRectangleHitPoint())
            .AndNext()
                .Move()
                .If(not(within().milliMeters(2).fromTouchDown(1)))
                    .Do2(DragRectangle())
                .Else()
                    .Do3(nothing())
            .AndNext()
                .TouchUp()
                .Do1(nothing())
        ;
 
        
        return gesture;
    }
    
    IGestureCondition OnRectangle()
    {
        return new OnRectangleCondition(getView());
    }
    
    IGestureAction RegisterRectangleHitPoint()
    {
        return new RegisterRectangleHitPointAction(getView());
    }
    
    IGestureAction DragRectangle()
    {
        return new DragRectangleAction(getView());
    }
    
    IGestureAction NoDragging()
    {
        return new NoDraggingAction(getView());
    }
 
    IGestureAction ShowMessage(String message)
    {
        return new ShowMessageAction(getView(), message);
    }
}

The interesting things here are the action on the touchdown event and the condition and actions on the move event:

The action executed on the touchdown event stores the hit point on the rectangle in the event storage using the addContext method of the gesture:

Java
public class RegisterRectangleHitPointAction extends GestureActionBase<AndroidGestureDSLView> {
    
    public static String RECTANGLE_CENTER_HITOFFSET = "RECTANGLE_CENTER_HITOFFSET";
 
    public RegisterRectangleHitPointAction(AndroidGestureDSLView view) {
        super(view);
    }
    
    @Override
    public void executeAction(GestureEvent motion, TouchGesture gesture) {
        Point rectangleCenter = getTouchedView().getRectangleCenter();
        ScreenVector touchDownPoint = motion.getPosition();
        //(ScreenVector)gesture.getContext(TouchHandler.ActionDownPos);
        
        Point hitOffset = new Point(rectangleCenter.x - touchDownPoint.x, 
                                    rectangleCenter.y - touchDownPoint.y);
        
        gesture.addContext(RECTANGLE_CENTER_HITOFFSET, hitOffset);
    }
    
    String message;
}

The condition on the move event checks that the movement is bigger than two millimeters from the touchdown event. This is done in order to be able to combine this drag gesture with a click gesture: you wouldn't want to start dragging anything when all the user wants to do is click it. Of course, if you do not support the clicking, then you do not need the condition. Remember I stated above the implicit functionality that a condition which is not fulfilled invalidates the gesture? That is not what you want in this case, because otherwise your gesture would always immediately be invalidated: the condition will always start with failure because the distance will always start with a value smaller than two millimeters. And that is why the Else() part of the condition has an action nothing().

Where to go from here?

Of course, this is not yet feature complete. A few things come to mind:

  1. Support for multitouch gestures.
  2. Support for shape recognition in complex move gestures.
  3. Performance enhancements might be useful.

What's following?

In a following article I will provide a more detailed discussion on the internal workings of the DSL.

Version history

  • Version 1.0: Initial version
  • Version 1.1: Following changes:
    • Split initial code in library and application
    • GestureBuilder now supports not only within() but also exceed()
    • In GestureBuilder renamed getView() to getBase()
    • TouchHandler now supports context key LastActionPos
    • Removed AndIf() from AfterConditionalContinuation<NextGesture>: this allowed making sentences which are doubtful, like:
      • Java
        .AndNext().TouchUp()
        	.If(not(within().seconds(3).fromTouchDown(1)))
        		.Do2(ShowMessage("You longclicked outside the rectangle"))
        	.AndIf(/* some condition */)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Belgium Belgium
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralAwesome article Pin
den2k882-Sep-15 3:54
professionalden2k882-Sep-15 3:54 
GeneralRe: Awesome article Pin
Serge Desmedt2-Sep-15 4:06
professionalSerge Desmedt2-Sep-15 4:06 
QuestionWhile I like what you've done, I'm not convinced by the title. Pin
Pete O'Hanlon6-Dec-13 0:34
subeditorPete O'Hanlon6-Dec-13 0:34 
AnswerRe: While I like what you've done, I'm not convinced by the title. Pin
Serge Desmedt6-Dec-13 0:59
professionalSerge Desmedt6-Dec-13 0:59 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA16-Feb-13 19:23
professionalȘtefan-Mihai MOGA16-Feb-13 19:23 
QuestionI think this is pretty cool Pin
Sacha Barber4-Feb-13 5:41
Sacha Barber4-Feb-13 5:41 
AnswerRe: I think this is pretty cool Pin
Serge Desmedt8-Feb-13 20:05
professionalSerge Desmedt8-Feb-13 20:05 
GeneralMy vote of 5 Pin
asunbb21-Jan-13 15:04
asunbb21-Jan-13 15:04 
GeneralRe: My vote of 5 Pin
Serge Desmedt22-Jan-13 0:37
professionalSerge Desmedt22-Jan-13 0:37 
thanks
GeneralMy vote of 5 Pin
brucewayne81815-Jan-13 4:54
brucewayne81815-Jan-13 4:54 
GeneralRe: My vote of 5 Pin
Serge Desmedt15-Jan-13 6:55
professionalSerge Desmedt15-Jan-13 6:55 
GeneralVery curious Pin
Clark Kent12315-Jan-13 3:18
professionalClark Kent12315-Jan-13 3:18 
QuestionA more explanatory title would be nice. Pin
W. Kleinschmit14-Jan-13 23:11
W. Kleinschmit14-Jan-13 23:11 
AnswerRe: A more explanatory title would be nice. Pin
Serge Desmedt15-Jan-13 0:25
professionalSerge Desmedt15-Jan-13 0:25 
GeneralRe: A more explanatory title would be nice. Pin
W. Kleinschmit15-Jan-13 1:07
W. Kleinschmit15-Jan-13 1:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.