Click here to Skip to main content
15,892,797 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 46.8K   467   31  
A DSL for creating touch gestures in Android.
package com.hfk.android.gestures;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import android.graphics.Point;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;

public class TouchHandler {
	
	public static String TouchHandlerId = "TOUCH_HANDLER";
	
	public static String LastActionPos = "LAST_ACTION_POSITION";
	
	public static String ActionDownPos = "ACTION_DOWN_POSITION";
	public static String ActionDownTime = "ACTION_DOWN_TIME";
	
	public static String ActionMovePos = "ACTION_MOVE_POSITION";
	public static String ActionMoveTime = "ACTION_MOVE_TIME";
	
	public static String ActionUpPos = "ACTION_UP_POSITION";
	public static String ActionUpTime = "ACTION_UP_TIME";
	
	public TouchHandler()
	{
		handler = new Handler();
	}
	
	public void addGesture(TouchGesture gesture)
	{
		gesture.addContext(TouchHandlerId, this);
		gestureList.add(gesture);
	}
	
	public static String getEventId(String dataKey, int index)
	{
		return dataKey + "_" + ((Integer)index).toString();
	}
	
	public void invalidateTimer() {
		if(timerAction != null)
		{
			handler.removeCallbacks(timerAction);
			timerAction = null;
		}
	}
	
	public void tryReset()
	{
		boolean canReset = true;

		for(TouchGesture eventOrder : gestureList)
		{
			// This can not be done if any gesture is valid but not yet completed
			if(eventOrder.isValid() && !eventOrder.isCompleted())
				canReset = false;
		}
		
		if(canReset)
		{
			for(TouchGesture eventOrder : gestureList)
			{
				eventOrder.reset();
				eventOrder.addContext(TouchHandlerId, this);
				
				touchDownCounter = 0;
				touchUpCounter = 0;
			}
		}
	}
	
	public void installTimer(IGestureAction action, int timeOut, final TouchGesture gesture) {
		timerGestureAction = action;
		timerAction = new Runnable()
		{
			public void run() {
				timerGestureAction.executeAction(lastMotionEvent, gesture);
			}
		};
		handler.postDelayed(timerAction, timeOut);
	}

	public void onTouchEvent(MotionEvent androidMotion)   {
		lastMotionEvent = new GestureEvent(androidMotion);
		
    	int action = androidMotion.getActionMasked();
    	
    	GestureEvent motion = new GestureEvent(androidMotion);
      	
		switch (action) {
    	case MotionEvent.ACTION_DOWN:
    	case MotionEvent.ACTION_POINTER_DOWN:
			// We have a touchdown event
			touchDownCounter++;
    		for(TouchGesture eventOrder : gestureList)
    		{
				// Store some data we can query in our actions and conditions
    			if(eventOrder.contextExists(LastActionPos))
				{
					eventOrder.setContext(LastActionPos, motion.getPosition());
				}
				else
				{
					eventOrder.addContext(LastActionPos, motion.getPosition());
				}
    			eventOrder.addContext(TouchHandler.getEventId(ActionDownPos, touchDownCounter), motion.getPosition());
    			eventOrder.addContext(TouchHandler.getEventId(ActionDownTime, touchDownCounter), motion.getTime());
				// If the sequence is still valid and we expect a touchdown event, then process it
	    		if(eventOrder.isValid() && eventOrder.current().event == TouchEvent.TOUCH_DOWN)
	    		{
	    			for(IfThenClause condition: eventOrder.current().conditionList)
	    			{
						// Execute the condition
	    				condition.Execute(motion, eventOrder);
						// Check if our gesture is still valid.
						//	It is possible that the condition invalidated the gesture.
						//	If this happened, there is no need to check any further conditions
	    				if(!eventOrder.isValid())
	    				{
	    					break;
	    				}
	    			}
	    			
					// Signal this part of the sequence as executed
					eventOrder.currentIsExecuted();
					// and move the sequence pointer forward
	    			eventOrder.moveNext();
	    		}
    		}

    		break;
    	case MotionEvent.ACTION_MOVE:
			// We have a move event
    		for(TouchGesture eventOrder : gestureList)
    		{
				// Store some data we can query in our actions and conditions
    			if(eventOrder.contextExists(LastActionPos))
				{
					eventOrder.setContext(LastActionPos, motion.getPosition());
				}
				else
				{
					eventOrder.addContext(LastActionPos, motion.getPosition());
				}
				
    			boolean isValid = false;
				// If the sequence is still valid and we expect a move event, then process it
	    		if(eventOrder.isValid() && eventOrder.current().event == TouchEvent.TOUCH_MOVE)
	    		{
	    			isValid = true;
	    			for(IfThenClause condition: eventOrder.current().conditionList)
	    			{
	    				condition.Execute(motion, eventOrder);
	    				if(!eventOrder.isValid())
	    				{
	    					break;
	    				}
	    			}

					eventOrder.currentIsExecuted();
	    			// Do not move to the next event because there will most likely be a series
	    			//	of these move-events and otherwise only one would be accepted
	    			//eventOrder.moveNext();
	    		}

	    		if(!isValid)
	    			eventOrder.invalidate();
    		}

    		break;
    	case MotionEvent.ACTION_UP:
    	case MotionEvent.ACTION_POINTER_UP:
			touchUpCounter++;
    		for(TouchGesture eventOrder : gestureList)
    		{
				// Store some data we can query in our actions and conditions
    			if(eventOrder.contextExists(LastActionPos))
				{
					eventOrder.setContext(LastActionPos, motion.getPosition());
				}
				else
				{
					eventOrder.addContext(LastActionPos, motion.getPosition());
				}
    			eventOrder.addContext(TouchHandler.getEventId(ActionUpPos, touchUpCounter), motion.getPosition());
    			eventOrder.addContext(TouchHandler.getEventId(ActionUpTime, touchUpCounter), motion.getTime());
				
				// If the sequence is still valid and we expect a move event
				//	which is not optional and is not executed yet
				// Then our sequence is no longer valid
				if(eventOrder.isValid() && (eventOrder.current().event == TouchEvent.TOUCH_MOVE) 
						&& !eventOrder.current().isOptional && !eventOrder.isCurrentExecuted())
				{
					eventOrder.invalidate();
				}
				// If the sequence is still valid and we expect a move event
				//	which is optional and is executed yet
				// Then move forward in the sequence
				if(eventOrder.isValid() && (eventOrder.current().event == TouchEvent.TOUCH_MOVE) 
						&& (eventOrder.current().isOptional || eventOrder.isCurrentExecuted()))
				{
					eventOrder.moveNext();
				}
				
				// If the sequence is still valid and we expect a touchup event, then process it
	    		if(eventOrder.isValid() && eventOrder.current().event == TouchEvent.TOUCH_UP)
	    		{
	    			for(IfThenClause condition: eventOrder.current().conditionList)
	    			{
	    				condition.Execute(motion, eventOrder);
	    				if(!eventOrder.isValid())
	    				{
	    					break;
	    				}
	    			}
	    			
					eventOrder.currentIsExecuted();
	    			eventOrder.moveNext();
	    		}
    		}

    		break;
    	}
		
		// Try resetting all the gestures
		tryReset();

	}
	
	private List<TouchGesture> gestureList = new ArrayList<TouchGesture>();
	private Handler handler;
	private Runnable timerAction;
	private IGestureAction timerGestureAction = null;
	//private IGestureCondition timerGestureCondition = null;
	private GestureEvent lastMotionEvent;
	
	private int touchDownCounter = 0;
	private int touchUpCounter = 0;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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