Draggable Symbols I
For many possible applications in design, modeling, games, and so on, it is important for the user to be able to drag symbols from one part of the display screen to another. For example, in touchscreen Android phones it is common to move application icons to different positions or different folders by long-pressing (holding down longer than a tap) on the icon with a finger, dragging it to the new location, and then raising your finger. In this project we develop some techniques for dragging arbitrary user-defined symbols around the screen.
Rather than putting this example together piece by piece, we shall give the complete listing for the two classes that we shall use and then dissect the functionality of each. In this example, we do the complete layout using Java (there will be no XML files needed at all in the res/layout directory), so all the action will involve just two Java classes and some Drawable image resource files that they interact with. To begin, create a new project in Eclipse with
as its parameters.
Let's go ahead and copy some resources that we will use. Open the course images directory and copy the files green_square.png, red_square.png, and yellow_square.png to the res/drawable-hdpi directory of the DraggableSymbols project.
The primary purpose of the DraggableSymbols class will be to set up some resource arrays and then to pass them to an instance of the class SymbolDragger, which will do all the work. So let's create SymbolDragger. Right-click on src/<namespace>/draggablesymbols and select New > Class to create the new class file SymbolDragger.java, with superclass android.view.View. Edit this file so that it has the content
package com.lightcone.draggablesymbols;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.View;
/*
Demonstration of one way to put a set of draggable symbols on screen.
Adapted loosely from material discussed in
http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
See also
http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html
This example requires only API 3 (Android 1.5).
*/
public class SymbolDragger extends View {
// Colors for background and text
static final int BACKGROUND_COLOR = Color.argb(255, 0, 0, 0);
static final int HEADER_COLOR = Color.argb(255, 30, 30, 30);
static final int TEXT_COLOR = Color.argb(255,255,255,0);
private int numberSymbols; // Total number of symbols to use
private Drawable [] symbol; // Array of symbols (dimension numberSymbols)
private int [] symbolIndex; // Index of drawable resource (R.drawable.symbol)
private float [] X; // Current x coordinate, upper left corner of symbol i
private float [] Y; // Current y coordinate, upper left corner of symbol i
private int [] symbolWidth; // Width of symbol i
private int [] symbolHeight; // Height of symbol i
private float [] lastTouchX; // x coordinate of symbol i at last touch
private float [] lastTouchY; // y coordinate of symbol i at last touch
private int symbolSelected; // Index of symbol last touched (-1 if none selected)
private Paint paint;
// Following define upper left and lower right corners of display stage rectangle
private int stageX1 = 0;
private int stageY1 = 45;
private int stageX2 = 500;
private int stageY2 = 500;
private boolean isDragging = false; // True if some symbol is currently being dragged
public SymbolDragger(Context context, float[]X, float[]Y, int[]symbolIndex) {
// Call through to simplest constructor of View superclass
super(context);
// Set up local arrays defining symbols and their positions
this.X = X;
this.Y = Y;
this.symbolIndex = symbolIndex;
numberSymbols = X.length;
symbol = new Drawable[numberSymbols];
symbolWidth = new int[numberSymbols];
symbolHeight = new int[numberSymbols];
lastTouchX = new float[numberSymbols];
lastTouchY = new float[numberSymbols];
// Fill the symbol arrays with data
for(int i=0; i<numberSymbols; i++){
symbol[i] = context.getResources().getDrawable(symbolIndex[i]);
symbolWidth[i] = symbol[i].getIntrinsicWidth();
symbolHeight[i] = symbol[i].getIntrinsicHeight();
symbol[i].setBounds(0,0,symbolWidth[i],symbolHeight[i]);
}
// Set up the Paint object that will control format of screen draws
paint = new Paint();
paint.setAntiAlias(true);
paint.setTextSize(14);
paint.setStrokeWidth(0);
}
/* Process MotionEvents corresponding to screen touches and drags. MotionEvent
reports movement (mouse, pen, finger, trackball) events. The MotionEvent method
getAction() returns the kind of action being performed as an integer constant of
the MotionEvent class, with possible values ACTION_DOWN, ACTION_MOVE, ACTION_UP,
and ACTION_CANCEL. Thus we can switch on the returned integer to determine the
kind of event and the appropriate action.
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
// MotionEvent class constant signifying a finger-down event
case MotionEvent.ACTION_DOWN: {
isDragging = false;
// Get coordinates of touch event
final float x = ev.getX();
final float y = ev.getY();
// Initialize
symbolSelected = -1; // -1 if touch not within current bounds of some symbol
// Determine if touch within bounds of one of the symbols
for(int i=0; i<numberSymbols; i++){
if((x>X[i] && x<(X[i]+symbolWidth[i])) && (y>Y[i]
&& y<(Y[i]+symbolHeight[i]))) {
symbolSelected = i;
break;
}
}
// If touch within bounds of a symbol, remember start position for this symbol
if(symbolSelected > -1){
lastTouchX[symbolSelected] = x;
lastTouchY[symbolSelected] = y;
}
break;
}
// MotionEvent class constant signifying a finger-drag event
case MotionEvent.ACTION_MOVE: {
// Only process if touch selected a symbol
if(symbolSelected > -1 ){
isDragging = true;
final float x = ev.getX();
final float y = ev.getY();
// Calculate the distance moved
final float dx = x - lastTouchX[symbolSelected];
final float dy = y - lastTouchY[symbolSelected];
// Move the object selected
X[symbolSelected] += dx;
Y[symbolSelected] += dy;
// Remember this touch position for the next move event of this object
lastTouchX[symbolSelected] = x;
lastTouchY[symbolSelected] = y;
// Request a redraw
invalidate();
}
break;
}
// MotionEvent class constant signifying a finger-up event
case MotionEvent.ACTION_UP:
isDragging = false;
invalidate(); // Request redraw
break;
}
return true;
}
// This method will be called each time the screen is redrawn. The draw is
// on the Canvas object, with formatting controlled by the Paint object.
// When to redraw is under Android control, but we can request a redraw
// using the method invalidate() inherited from the View superclass.
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw backgrounds
drawBackground(paint,canvas);
// Draw all draggable symbols at their current locations
for(int i=0; i<numberSymbols; i++){
canvas.save();
canvas.translate(X[i],Y[i]);
symbol[i].draw(canvas);
canvas.restore();
}
isDragging = false;
}
// Method to draw the background for the screen. Invoked from onDraw each time
// the screen is redrawn.
private void drawBackground(Paint paint, Canvas canvas){
// Draw header bar background
paint.setColor(HEADER_COLOR);
canvas.drawRect(0,0,stageX2,stageY2,paint);
// Draw main stage background
paint.setColor(BACKGROUND_COLOR);
canvas.drawRect(stageX1,stageY1,stageX2,stageY2,paint);
// If dragging a symbol, display its x and y coordinates in a readout
if(isDragging){
paint.setColor(TEXT_COLOR);
canvas.drawText("X = "+X[symbolSelected], 200, 18, paint);
canvas.drawText("Y = "+Y[symbolSelected], 200, 33, paint);
}
}
}
This class should compile without error.
Now edit the file DraggableSymbols.java in src/<namespace>/draggablesymbols so that it reads
package com.lightcone.draggablesymbols;
import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup;
/*
Demonstration of one way to put a set of draggable symbols on screen. Define symbols as image
files in res/drawable-hdpi/filename.png (or .jpg or .gif files). Refer to them then by
R.drawable.filename in the array symbolIndex, and give the initial x and y coordinates for
each symbol in the arrays X and Y.
*/
public class DraggableSymbols extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Define the symbols and their initial coordinates in arrays. No limit in principle
// to how many. Coordinates are measured from the upper left corner of the screen,
// with x increasing to the right and y increasing downward
float [] X = {2, 48, 94}; // Initial x coord in pixels of upper left corner of symbol
float [] Y = {2, 2, 2}; // Initial y coord in pixels of upper left corner of symbol
// The Drawable corresponding to the symbol. R.drawable.file refers to file.png, .jpg,
// or .gif stored in res/drawable-hdpi (referenced from code without the extension).
int[]symbolIndex = {R.drawable.red_square, R.drawable.green_square, R.drawable.yellow_square};
// Instantiate a SymbolDragger instance (which subclasses View), passing to it in the
// constructor the context (this) and the above arrays. Then set the content view to
// this instance of SymbolDragger (so the layout is being specified entirely by SymbolDragger,
// with no XML layout file). The resulting view should then place draggable symbols with
// initial content and position defined by the above arrays on the screen.
SymbolDragger view = new SymbolDragger(this, X, Y, symbolIndex);
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT));
setContentView(view);
}
}
This class also should compile without error.
| It isn't essential, but to emphasize that our layout has been created entirely in Java, you can go to the res/layout directory of the project and delete the main.xml file, since we are not using it. This file was produced automatically by Eclipse when the project was created, but we changed the view in DraggableSymbols set by the argument of setContentView() from the default R.layout.main to our own Java-created view object. |
If you now execute on an emulator or device by right-clicking on the project and selecting Run As > Android Application (or selecting the Run dropdown menu at the top), you should get screens like the following figures,
![]() |
![]() |
The left figure corresponds to the initial state, before anything has been dragged. In the right figure the green and red symbols already have been dragged to new positions by touching with the finger on a touchscreen (in this case the screen of a Motorola Backflip phone) and dragging to the new position. The yellow symbol is in the process of being dragged to a new position, so a dynamically updated readout of its position in pixels is being displayed as it is being dragged. Also, you should find that nothing happens if you press or drag on the screen background instead of one of the symbols.
The two classes defined in this project are heavily commented, so you should be able to understand what the code is doing by reading through it with systematic attention to the documentation. But some of the comments are a little terse, so let's give a somewhat less abridged version here.
The class DraggableSymbols is the entry point for the app (notice in AndroidManifest.xml that it is the only Activity registered), and extends Activity, as is normal. However, we have replaced the usual specification of the initial screen layout through an XML resource file by the SymbolDragger instance view, which is subclassed from View, in the last three lines of the file. Thus our layout is going to be defined by the Java class SymbolDragger described below.
We also have set up some arrays in DraggableSymbols that will hold the initial positions of the draggable symbols that we wish to place on the screen (the arrays X and Y, which hold pixel coordinates as floats). Finally, we have set up an array symbolIndex that holds the integer references to the Drawable image resources that we placed in the res/drawable-hdpi folder. For example, R.drawable.red_square is a reference to the file res/drawable-hdpi/red_square.png that the class SymbolDragger will use to access this image resource. These arrays of data are passed as arguments to the constructor of SymbolDragger that we instantiate as the screen view.
The Java class SymbolDragger, through the view instance created by DraggableSymbols, does most of the work. It
Let's now describe briefly the main blocks of code that accomplish these tasks in SymbolDragger.
When to redraw the screen is under the control of Android, but the invalidate() method of View requests a redraw (through a call to onDraw(Canvas), if the screen is visible) as soon as possible.
| The View method invalidate() marks the entire screen as needing a redraw. The overloaded form invalidate(Rect dirtyRectangle) marks only the portion of the screen defined by the Rect dirtyRectangle as "dirty" and in need of a redraw. Use of this latter form can be important in achieving rapid and smooth response if only a fixed portion of a complex display is changing because of some action. In our example, we have only a few symbols that can be dragged anywhere on the screen, so we request a redraw of the entire screen each time the status of a symbol changes. |
This completes the basic demonstration of how to drag some symbols around the screen. We shall now go on to add some additional features.
| The project for the application as described to this point is archived at the link DraggableSymbols. |