1
0
mirror of https://bitbucket.org/librepilot/librepilot.git synced 2025-01-18 03:52:11 +01:00

Check in a joystick gadget class

This commit is contained in:
James Cotton 2012-08-04 21:00:54 -05:00
parent 4e7d8bffc4
commit 8f98383fa5
18 changed files with 2125 additions and 0 deletions

View File

@ -0,0 +1,319 @@
package com.MobileAnarchy.Android.Widgets.DockPanel;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.TranslateAnimation;
import android.view.animation.Animation.AnimationListener;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
public class DockPanel extends LinearLayout {
// =========================================
// Private members
// =========================================
private static final String TAG = "DockPanel";
private DockPosition position;
private int contentLayoutId;
private int handleButtonDrawableId;
private Boolean isOpen;
private Boolean animationRunning;
private FrameLayout contentPlaceHolder;
private ImageButton toggleButton;
private int animationDuration;
// =========================================
// Constructors
// =========================================
public DockPanel(Context context, int contentLayoutId,
int handleButtonDrawableId, Boolean isOpen) {
super(context);
this.contentLayoutId = contentLayoutId;
this.handleButtonDrawableId = handleButtonDrawableId;
this.isOpen = isOpen;
Init(null);
}
public DockPanel(Context context, AttributeSet attrs) {
super(context, attrs);
// to prevent from crashing the designer
try {
Init(attrs);
} catch (Exception ex) {
}
}
// =========================================
// Initialization
// =========================================
private void Init(AttributeSet attrs) {
setDefaultValues(attrs);
createHandleToggleButton();
// create the handle container
FrameLayout handleContainer = new FrameLayout(getContext());
handleContainer.addView(toggleButton);
// create and populate the panel's container, and inflate it
contentPlaceHolder = new FrameLayout(getContext());
String infService = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li = (LayoutInflater) getContext().getSystemService(
infService);
li.inflate(contentLayoutId, contentPlaceHolder, true);
// setting the layout of the panel parameters according to the docking
// position
if (position == DockPosition.LEFT || position == DockPosition.RIGHT) {
handleContainer.setLayoutParams(new LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.FILL_PARENT, 1));
contentPlaceHolder.setLayoutParams(new LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.FILL_PARENT, 1));
} else {
handleContainer.setLayoutParams(new LayoutParams(
android.view.ViewGroup.LayoutParams.FILL_PARENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT, 1));
contentPlaceHolder.setLayoutParams(new LayoutParams(
android.view.ViewGroup.LayoutParams.FILL_PARENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT, 1));
}
// adding the view to the parent layout according to docking position
if (position == DockPosition.RIGHT || position == DockPosition.BOTTOM) {
this.addView(handleContainer);
this.addView(contentPlaceHolder);
} else {
this.addView(contentPlaceHolder);
this.addView(handleContainer);
}
if (!isOpen) {
contentPlaceHolder.setVisibility(GONE);
}
}
private void setDefaultValues(AttributeSet attrs) {
// set default values
isOpen = true;
animationRunning = false;
animationDuration = 500;
setPosition(DockPosition.RIGHT);
// Try to load values set by xml markup
if (attrs != null) {
String namespace = "http://com.MobileAnarchy.Android.Widgets";
animationDuration = attrs.getAttributeIntValue(namespace,
"animationDuration", 500);
contentLayoutId = attrs.getAttributeResourceValue(namespace,
"contentLayoutId", 0);
handleButtonDrawableId = attrs.getAttributeResourceValue(
namespace, "handleButtonDrawableResourceId", 0);
isOpen = attrs.getAttributeBooleanValue(namespace, "isOpen", true);
// Enums are a bit trickier (needs to be parsed)
try {
position = DockPosition.valueOf(attrs.getAttributeValue(
namespace, "dockPosition").toUpperCase());
setPosition(position);
} catch (Exception ex) {
// Docking to the left is the default behavior
setPosition(DockPosition.LEFT);
}
}
}
private void createHandleToggleButton() {
toggleButton = new ImageButton(getContext());
toggleButton.setPadding(0, 0, 0, 0);
toggleButton.setLayoutParams(new FrameLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.CENTER));
toggleButton.setBackgroundColor(Color.TRANSPARENT);
toggleButton.setImageResource(handleButtonDrawableId);
toggleButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
toggle();
}
});
}
private void setPosition(DockPosition position) {
this.position = position;
switch (position) {
case TOP:
setOrientation(LinearLayout.VERTICAL);
setGravity(Gravity.TOP);
break;
case RIGHT:
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.RIGHT);
break;
case BOTTOM:
setOrientation(LinearLayout.VERTICAL);
setGravity(Gravity.BOTTOM);
break;
case LEFT:
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.LEFT);
break;
}
}
// =========================================
// Public methods
// =========================================
public int getAnimationDuration() {
return animationDuration;
}
public void setAnimationDuration(int milliseconds) {
animationDuration = milliseconds;
}
public Boolean getIsRunning() {
return animationRunning;
}
public void open() {
if (!animationRunning) {
Log.d(TAG, "Opening...");
Animation animation = createShowAnimation();
this.setAnimation(animation);
animation.start();
isOpen = true;
}
}
public void close() {
if (!animationRunning) {
Log.d(TAG, "Closing...");
Animation animation = createHideAnimation();
this.setAnimation(animation);
animation.start();
isOpen = false;
}
}
public void toggle() {
if (isOpen) {
close();
} else {
open();
}
}
// =========================================
// Private methods
// =========================================
private Animation createHideAnimation() {
Animation animation = null;
switch (position) {
case TOP:
animation = new TranslateAnimation(0, 0, 0, -contentPlaceHolder
.getHeight());
break;
case RIGHT:
animation = new TranslateAnimation(0, contentPlaceHolder
.getWidth(), 0, 0);
break;
case BOTTOM:
animation = new TranslateAnimation(0, 0, 0, contentPlaceHolder
.getHeight());
break;
case LEFT:
animation = new TranslateAnimation(0, -contentPlaceHolder
.getWidth(), 0, 0);
break;
}
animation.setDuration(animationDuration);
animation.setInterpolator(new AccelerateInterpolator());
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
animationRunning = true;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
contentPlaceHolder.setVisibility(View.GONE);
animationRunning = false;
}
});
return animation;
}
private Animation createShowAnimation() {
Animation animation = null;
switch (position) {
case TOP:
animation = new TranslateAnimation(0, 0, -contentPlaceHolder
.getHeight(), 0);
break;
case RIGHT:
animation = new TranslateAnimation(contentPlaceHolder.getWidth(),
0, 0, 0);
break;
case BOTTOM:
animation = new TranslateAnimation(0, 0, contentPlaceHolder
.getHeight(), 0);
break;
case LEFT:
animation = new TranslateAnimation(-contentPlaceHolder.getWidth(),
0, 0, 0);
break;
}
Log.d(TAG, "Animation duration: " + animationDuration);
animation.setDuration(animationDuration);
animation.setInterpolator(new DecelerateInterpolator());
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
animationRunning = true;
contentPlaceHolder.setVisibility(View.VISIBLE);
Log.d(TAG, "\"Show\" Animation started");
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
animationRunning = false;
Log.d(TAG, "\"Show\" Animation ended");
}
});
return animation;
}
}

View File

@ -0,0 +1,5 @@
package com.MobileAnarchy.Android.Widgets.DockPanel;
public enum DockPosition {
TOP, BOTTOM, LEFT, RIGHT
}

View File

@ -0,0 +1,124 @@
package com.MobileAnarchy.Android.Widgets.DragAndDrop;
import java.util.ArrayList;
import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
public class DragAndDropManager {
// =========================================
// Private members
// =========================================
protected static final String TAG = "DragAndDropManager";
private static DragAndDropManager instance = null;
private ArrayList<DropZone> dropZones;
private OnTouchListener originalTouchListener;
private DragSurface dragSurface;
private DraggableItem draggedItem;
private DropZone activeDropZone;
// =========================================
// Protected Constructor
// =========================================
protected DragAndDropManager() {
// Exists only to defeat instantiation.
dropZones = new ArrayList<DropZone>();
}
// =========================================
// Public Properties
// =========================================
public static DragAndDropManager getInstance() {
if (instance == null) {
instance = new DragAndDropManager();
}
return instance;
}
public Context getContext() {
if (dragSurface == null)
return null;
return dragSurface.getContext();
}
// =========================================
// Public Methods
// =========================================
public void init(DragSurface surface) {
dragSurface = surface;
clearDropZones();
}
public void clearDropZones() {
dropZones.clear();
}
public void addDropZone(DropZone dropZone) {
dropZones.add(dropZone);
}
public void startDragging(OnTouchListener originalListener, DraggableItem draggedItem) {
originalTouchListener = originalListener;
this.draggedItem = draggedItem;
draggedItem.getSource().setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int[] location = new int[2];
v.getLocationOnScreen(location);
event.offsetLocation(location[0], location[1]);
invalidateDropZones((int)event.getX(), (int)event.getY());
return dragSurface.onTouchEvent(event);
}
});
dragSurface.startDragging(draggedItem);
}
// =========================================
// Protected Methods
// =========================================
protected void invalidateDropZones(int x, int y) {
if (activeDropZone != null) {
if (!activeDropZone.isOver(x, y)) {
activeDropZone.getListener().OnDragZoneLeft(activeDropZone, draggedItem);
activeDropZone = null;
}
else {
// we are still over the same drop zone, no need to check other drop zones
return;
}
}
for (DropZone dropZone : dropZones) {
if (dropZone.isOver(x, y)) {
activeDropZone = dropZone;
dropZone.getListener().OnDragZoneEntered(activeDropZone, draggedItem);
break;
}
}
}
protected void stoppedDragging() {
if (activeDropZone != null) {
activeDropZone.getListener().OnDropped(activeDropZone, draggedItem);
}
// Registering the "old" listener to the view that initiated this drag session
draggedItem.getSource().setOnTouchListener(originalTouchListener);
draggedItem = null;
activeDropZone = null;
}
}

View File

@ -0,0 +1,94 @@
package com.MobileAnarchy.Android.Widgets.DragAndDrop;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.widget.FrameLayout;
public class DragSurface extends FrameLayout {
private float draggedViewHalfHeight;
private float draggedViewHalfWidth;
private int framesCount;
private Boolean isDragging;
private DraggableItem draggedItem;
public DragSurface(Context context, AttributeSet attrs) {
super(context, attrs);
isDragging = false;
}
// =========================================
// Touch Events Listener
// =========================================
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isDragging && event.getAction() == MotionEvent.ACTION_UP) {
// Dragging ended
removeAllViews();
isDragging = false;
DragAndDropManager.getInstance().stoppedDragging();
}
if (isDragging && event.getAction() == MotionEvent.ACTION_MOVE) {
// Move the dragged view to it's new position
repositionView(event.getX(), event.getY());
// Mark this event as handled (so that other UI elements will not intercept it)
return true;
}
return false;
}
public void startDragging(DraggableItem draggableItem) {
this.draggedItem = draggableItem;
this.draggedItem.getDraggedView().setVisibility(INVISIBLE);
isDragging = true;
addView(this.draggedItem.getDraggedView());
//repositionView(x, y);
framesCount = 0;
}
private void repositionView(float x, float y) {
draggedViewHalfHeight = draggedItem.getDraggedView().getHeight() / 2f;
draggedViewHalfWidth = draggedItem.getDraggedView().getWidth() / 2f;
// If the dragged view was not drawn yet, skip this phase
if (draggedViewHalfHeight == 0 || draggedViewHalfWidth == 0)
return;
framesCount++;
//Log.d(TAG, "Original = (x=" + x + ", y=" + y + ")");
//Log.d(TAG, "Size (W=" + draggedViewHalfWidth + ", H=" + draggedViewHalfHeight + ")");
x = x - draggedViewHalfWidth;
y = y - draggedViewHalfHeight;
x = Math.max(x, 0);
x = Math.min(x, getWidth() - draggedViewHalfWidth * 2);
y = Math.max(y, 0);
y = Math.min(y, getHeight() - draggedViewHalfHeight * 2);
//Log.d(TAG, "Moving view to (x=" + x + ", y=" + y + ")");
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT, Gravity.TOP + Gravity.LEFT);
lp.setMargins((int)x, (int)y, 0, 0);
draggedItem.getDraggedView().setLayoutParams(lp);
// hte first couple of dragged frame's positions are not calculated correctly,
// so we have a threshold before making the dragged view visible again
if (framesCount < 2)
return;
draggedItem.getDraggedView().setVisibility(VISIBLE);
}
}

View File

@ -0,0 +1,44 @@
package com.MobileAnarchy.Android.Widgets.DragAndDrop;
import android.view.View;
public class DraggableItem {
// =========================================
// Private members
// =========================================
private View source;
private View draggedView;
private Object tag;
// =========================================
// Constructor
// =========================================
public DraggableItem(View source, View draggedItem) {
this.source = source;
this.draggedView = draggedItem;
}
// =========================================
// Public properties
// =========================================
public Object getTag() {
return tag;
}
public void setTag(Object tag) {
this.tag = tag;
}
public View getSource() {
return source;
}
public View getDraggedView() {
return draggedView;
}
}

View File

@ -0,0 +1,19 @@
package com.MobileAnarchy.Android.Widgets.DragAndDrop;
import android.content.Context;
import android.view.View;
import android.widget.TextView;
import android.widget.TableLayout.LayoutParams;
public class DraggableViewsFactory {
public static View getLabel(String text) {
Context context = DragAndDropManager.getInstance().getContext();
TextView textView = new TextView(context);
textView.setText(text);
textView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
//textView.setGravity(Gravity.TOP + Gravity.LEFT);
return textView;
}
}

View File

@ -0,0 +1,67 @@
package com.MobileAnarchy.Android.Widgets.DragAndDrop;
import android.view.View;
public class DropZone {
// =========================================
// Private members
// =========================================
private View view;
private DropZoneEventsListener listener;
private int left, top, width, height;
private Boolean dimansionsCalculated;
// =========================================
// Constructor
// =========================================
public DropZone(View view, DropZoneEventsListener listener) {
this.view = view;
this.listener = listener;
dimansionsCalculated = false;
}
// =========================================
// Public properties
// =========================================
public View getView() {
return view;
}
// =========================================
// Public methods
// =========================================
public Boolean isOver(int x, int y) {
if (!dimansionsCalculated)
calculateDimensions();
Boolean isOver = (x >= left && x <= (left + width)) &&
(y >= top && y <= (top + height));
//Log.d("DragZone", "x=" +x + ", left=" + left + ", y=" + y + ", top=" + top + " width=" + width + ", height=" + height + ", isover=" + isOver);
return isOver;
}
// =========================================
// Protected & Private methods
// =========================================
protected DropZoneEventsListener getListener() {
return listener;
}
private void calculateDimensions() {
int[] location = new int[2];
view.getLocationOnScreen(location);
left = location[0];
top = location[1];
width = view.getWidth();
height = view.getHeight();
dimansionsCalculated = true;
}
}

View File

@ -0,0 +1,9 @@
package com.MobileAnarchy.Android.Widgets.DragAndDrop;
public interface DropZoneEventsListener {
void OnDragZoneEntered(DropZone zone, DraggableItem item);
void OnDragZoneLeft(DropZone zone, DraggableItem item);
void OnDropped(DropZone zone, DraggableItem item);
}

View File

@ -0,0 +1,147 @@
package com.MobileAnarchy.Android.Widgets.Joystick;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
public class DualJoystickView extends LinearLayout {
@SuppressWarnings("unused")
private static final String TAG = DualJoystickView.class.getSimpleName();
private final boolean D = false;
private Paint dbgPaint1;
private JoystickView stickL;
private JoystickView stickR;
private View pad;
public DualJoystickView(Context context) {
super(context);
stickL = new JoystickView(context);
stickR = new JoystickView(context);
initDualJoystickView();
}
public DualJoystickView(Context context, AttributeSet attrs) {
super(context, attrs);
stickL = new JoystickView(context, attrs);
stickR = new JoystickView(context, attrs);
initDualJoystickView();
}
private void initDualJoystickView() {
setOrientation(LinearLayout.HORIZONTAL);
if ( D ) {
dbgPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
dbgPaint1.setColor(Color.CYAN);
dbgPaint1.setStrokeWidth(1);
dbgPaint1.setStyle(Paint.Style.STROKE);
}
pad = new View(getContext());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
removeView(stickL);
removeView(stickR);
float padW = getMeasuredWidth()-(getMeasuredHeight()*2);
int joyWidth = (int) ((getMeasuredWidth()-padW)/2);
LayoutParams joyLParams = new LayoutParams(joyWidth,getMeasuredHeight());
stickL.setLayoutParams(joyLParams);
stickR.setLayoutParams(joyLParams);
stickL.TAG = "L";
stickR.TAG = "R";
stickL.setPointerId(JoystickView.INVALID_POINTER_ID);
stickR.setPointerId(JoystickView.INVALID_POINTER_ID);
addView(stickL);
ViewGroup.LayoutParams padLParams = new ViewGroup.LayoutParams((int) padW,getMeasuredHeight());
removeView(pad);
pad.setLayoutParams(padLParams);
addView(pad);
addView(stickR);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
stickR.setTouchOffset(stickR.getLeft(), stickR.getTop());
}
public void setAutoReturnToCenter(boolean left, boolean right) {
stickL.setAutoReturnToCenter(left);
stickR.setAutoReturnToCenter(right);
}
public void setOnJostickMovedListener(JoystickMovedListener left, JoystickMovedListener right) {
stickL.setOnJostickMovedListener(left);
stickR.setOnJostickMovedListener(right);
}
public void setOnJostickClickedListener(JoystickClickedListener left, JoystickClickedListener right) {
stickL.setOnJostickClickedListener(left);
stickR.setOnJostickClickedListener(right);
}
public void setYAxisInverted(boolean leftYAxisInverted, boolean rightYAxisInverted) {
stickL.setYAxisInverted(leftYAxisInverted);
stickL.setYAxisInverted(rightYAxisInverted);
}
public void setMovementConstraint(int movementConstraint) {
stickL.setMovementConstraint(movementConstraint);
stickR.setMovementConstraint(movementConstraint);
}
public void setMovementRange(float movementRangeLeft, float movementRangeRight) {
stickL.setMovementRange(movementRangeLeft);
stickR.setMovementRange(movementRangeRight);
}
public void setMoveResolution(float leftMoveResolution, float rightMoveResolution) {
stickL.setMoveResolution(leftMoveResolution);
stickR.setMoveResolution(rightMoveResolution);
}
public void setUserCoordinateSystem(int leftCoordinateSystem, int rightCoordinateSystem) {
stickL.setUserCoordinateSystem(leftCoordinateSystem);
stickR.setUserCoordinateSystem(rightCoordinateSystem);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (D) {
canvas.drawRect(1, 1, getMeasuredWidth()-1, getMeasuredHeight()-1, dbgPaint1);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean l = stickL.dispatchTouchEvent(ev);
boolean r = stickR.dispatchTouchEvent(ev);
return l || r;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean l = stickL.onTouchEvent(ev);
boolean r = stickR.onTouchEvent(ev);
return l || r;
}
}

View File

@ -0,0 +1,6 @@
package com.MobileAnarchy.Android.Widgets.Joystick;
public interface JoystickClickedListener {
public void OnClicked();
public void OnReleased();
}

View File

@ -0,0 +1,7 @@
package com.MobileAnarchy.Android.Widgets.Joystick;
public interface JoystickMovedListener {
public void OnMoved(int pan, int tilt);
public void OnReleased();
public void OnReturnedToCenter();
}

View File

@ -0,0 +1,521 @@
package com.MobileAnarchy.Android.Widgets.Joystick;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
public class JoystickView extends View {
public static final int INVALID_POINTER_ID = -1;
// =========================================
// Private Members
// =========================================
private final boolean D = false;
String TAG = "JoystickView";
private Paint dbgPaint1;
private Paint dbgPaint2;
private Paint bgPaint;
private Paint handlePaint;
private int innerPadding;
private int bgRadius;
private int handleRadius;
private int movementRadius;
private int handleInnerBoundaries;
private JoystickMovedListener moveListener;
private JoystickClickedListener clickListener;
//# of pixels movement required between reporting to the listener
private float moveResolution;
private boolean yAxisInverted;
private boolean autoReturnToCenter;
//Max range of movement in user coordinate system
public final static int CONSTRAIN_BOX = 0;
public final static int CONSTRAIN_CIRCLE = 1;
private int movementConstraint;
private float movementRange;
public final static int COORDINATE_CARTESIAN = 0; //Regular cartesian coordinates
public final static int COORDINATE_DIFFERENTIAL = 1; //Uses polar rotation of 45 degrees to calc differential drive paramaters
private int userCoordinateSystem;
//Records touch pressure for click handling
private float touchPressure;
private boolean clicked;
private float clickThreshold;
//Last touch point in view coordinates
private int pointerId = INVALID_POINTER_ID;
private float touchX, touchY;
//Last reported position in view coordinates (allows different reporting sensitivities)
private float reportX, reportY;
//Handle center in view coordinates
private float handleX, handleY;
//Center of the view in view coordinates
private int cX, cY;
//Size of the view in view coordinates
private int dimX, dimY;
//Cartesian coordinates of last touch point - joystick center is (0,0)
private int cartX, cartY;
//Polar coordinates of the touch point from joystick center
private double radial;
private double angle;
//User coordinates of last touch point
private int userX, userY;
//Offset co-ordinates (used when touch events are received from parent's coordinate origin)
private int offsetX;
private int offsetY;
// =========================================
// Constructors
// =========================================
public JoystickView(Context context) {
super(context);
initJoystickView();
}
public JoystickView(Context context, AttributeSet attrs) {
super(context, attrs);
initJoystickView();
}
public JoystickView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initJoystickView();
}
// =========================================
// Initialization
// =========================================
private void initJoystickView() {
setFocusable(true);
dbgPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
dbgPaint1.setColor(Color.RED);
dbgPaint1.setStrokeWidth(1);
dbgPaint1.setStyle(Paint.Style.STROKE);
dbgPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
dbgPaint2.setColor(Color.GREEN);
dbgPaint2.setStrokeWidth(1);
dbgPaint2.setStyle(Paint.Style.STROKE);
bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bgPaint.setColor(Color.GRAY);
bgPaint.setStrokeWidth(1);
bgPaint.setStyle(Paint.Style.FILL_AND_STROKE);
handlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
handlePaint.setColor(Color.DKGRAY);
handlePaint.setStrokeWidth(1);
handlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
innerPadding = 10;
setMovementRange(10);
setMoveResolution(1.0f);
setClickThreshold(0.4f);
setYAxisInverted(true);
setUserCoordinateSystem(COORDINATE_CARTESIAN);
setAutoReturnToCenter(true);
}
public void setAutoReturnToCenter(boolean autoReturnToCenter) {
this.autoReturnToCenter = autoReturnToCenter;
}
public boolean isAutoReturnToCenter() {
return autoReturnToCenter;
}
public void setUserCoordinateSystem(int userCoordinateSystem) {
if (userCoordinateSystem < COORDINATE_CARTESIAN || movementConstraint > COORDINATE_DIFFERENTIAL)
Log.e(TAG, "invalid value for userCoordinateSystem");
else
this.userCoordinateSystem = userCoordinateSystem;
}
public int getUserCoordinateSystem() {
return userCoordinateSystem;
}
public void setMovementConstraint(int movementConstraint) {
if (movementConstraint < CONSTRAIN_BOX || movementConstraint > CONSTRAIN_CIRCLE)
Log.e(TAG, "invalid value for movementConstraint");
else
this.movementConstraint = movementConstraint;
}
public int getMovementConstraint() {
return movementConstraint;
}
public boolean isYAxisInverted() {
return yAxisInverted;
}
public void setYAxisInverted(boolean yAxisInverted) {
this.yAxisInverted = yAxisInverted;
}
/**
* Set the pressure sensitivity for registering a click
* @param clickThreshold threshold 0...1.0f inclusive. 0 will cause clicks to never be reported, 1.0 is a very hard click
*/
public void setClickThreshold(float clickThreshold) {
if (clickThreshold < 0 || clickThreshold > 1.0f)
Log.e(TAG, "clickThreshold must range from 0...1.0f inclusive");
else
this.clickThreshold = clickThreshold;
}
public float getClickThreshold() {
return clickThreshold;
}
public void setMovementRange(float movementRange) {
this.movementRange = movementRange;
}
public float getMovementRange() {
return movementRange;
}
public void setMoveResolution(float moveResolution) {
this.moveResolution = moveResolution;
}
public float getMoveResolution() {
return moveResolution;
}
// =========================================
// Public Methods
// =========================================
public void setOnJostickMovedListener(JoystickMovedListener listener) {
this.moveListener = listener;
}
public void setOnJostickClickedListener(JoystickClickedListener listener) {
this.clickListener = listener;
}
// =========================================
// Drawing Functionality
// =========================================
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Here we make sure that we have a perfect circle
int measuredWidth = measure(widthMeasureSpec);
int measuredHeight = measure(heightMeasureSpec);
setMeasuredDimension(measuredWidth, measuredHeight);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int d = Math.min(getMeasuredWidth(), getMeasuredHeight());
dimX = d;
dimY = d;
cX = d / 2;
cY = d / 2;
bgRadius = dimX/2 - innerPadding;
handleRadius = (int)(d * 0.25);
handleInnerBoundaries = handleRadius;
movementRadius = Math.min(cX, cY) - handleInnerBoundaries;
}
private int measure(int measureSpec) {
int result = 0;
// Decode the measurement specifications.
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.UNSPECIFIED) {
// Return a default size of 200 if no bounds are specified.
result = 200;
} else {
// As you want to fill the available space
// always return the full available bounds.
result = specSize;
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
// Draw the background
canvas.drawCircle(cX, cY, bgRadius, bgPaint);
// Draw the handle
handleX = touchX + cX;
handleY = touchY + cY;
canvas.drawCircle(handleX, handleY, handleRadius, handlePaint);
if (D) {
canvas.drawRect(1, 1, getMeasuredWidth()-1, getMeasuredHeight()-1, dbgPaint1);
canvas.drawCircle(handleX, handleY, 3, dbgPaint1);
if ( movementConstraint == CONSTRAIN_CIRCLE ) {
canvas.drawCircle(cX, cY, this.movementRadius, dbgPaint1);
}
else {
canvas.drawRect(cX-movementRadius, cY-movementRadius, cX+movementRadius, cY+movementRadius, dbgPaint1);
}
//Origin to touch point
canvas.drawLine(cX, cY, handleX, handleY, dbgPaint2);
int baseY = (int) (touchY < 0 ? cY + handleRadius : cY - handleRadius);
canvas.drawText(String.format("%s (%.0f,%.0f)", TAG, touchX, touchY), handleX-20, baseY-7, dbgPaint2);
canvas.drawText("("+ String.format("%.0f, %.1f", radial, angle * 57.2957795) + (char) 0x00B0 + ")", handleX-20, baseY+15, dbgPaint2);
}
// Log.d(TAG, String.format("touch(%f,%f)", touchX, touchY));
// Log.d(TAG, String.format("onDraw(%.1f,%.1f)\n\n", handleX, handleY));
canvas.restore();
}
// Constrain touch within a box
private void constrainBox() {
touchX = Math.max(Math.min(touchX, movementRadius), -movementRadius);
touchY = Math.max(Math.min(touchY, movementRadius), -movementRadius);
}
// Constrain touch within a circle
private void constrainCircle() {
float diffX = touchX;
float diffY = touchY;
double radial = Math.sqrt((diffX*diffX) + (diffY*diffY));
if ( radial > movementRadius ) {
touchX = (int)((diffX / radial) * movementRadius);
touchY = (int)((diffY / radial) * movementRadius);
}
}
public void setPointerId(int id) {
this.pointerId = id;
}
public int getPointerId() {
return pointerId;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
return processMoveEvent(ev);
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
if ( pointerId != INVALID_POINTER_ID ) {
// Log.d(TAG, "ACTION_UP");
returnHandleToCenter();
setPointerId(INVALID_POINTER_ID);
}
break;
}
case MotionEvent.ACTION_POINTER_UP: {
if ( pointerId != INVALID_POINTER_ID ) {
final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if ( pointerId == this.pointerId ) {
// Log.d(TAG, "ACTION_POINTER_UP: " + pointerId);
returnHandleToCenter();
setPointerId(INVALID_POINTER_ID);
return true;
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
if ( pointerId == INVALID_POINTER_ID ) {
int x = (int) ev.getX();
if ( x >= offsetX && x < offsetX + dimX ) {
setPointerId(ev.getPointerId(0));
// Log.d(TAG, "ACTION_DOWN: " + getPointerId());
return true;
}
}
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
if ( pointerId == INVALID_POINTER_ID ) {
final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
int x = (int) ev.getX(pointerId);
if ( x >= offsetX && x < offsetX + dimX ) {
// Log.d(TAG, "ACTION_POINTER_DOWN: " + pointerId);
setPointerId(pointerId);
return true;
}
}
break;
}
}
return false;
}
private boolean processMoveEvent(MotionEvent ev) {
if ( pointerId != INVALID_POINTER_ID ) {
final int pointerIndex = ev.findPointerIndex(pointerId);
// Translate touch position to center of view
float x = ev.getX(pointerIndex);
touchX = x - cX - offsetX;
float y = ev.getY(pointerIndex);
touchY = y - cY - offsetY;
// Log.d(TAG, String.format("ACTION_MOVE: (%03.0f, %03.0f) => (%03.0f, %03.0f)", x, y, touchX, touchY));
reportOnMoved();
invalidate();
touchPressure = ev.getPressure(pointerIndex);
reportOnPressure();
return true;
}
return false;
}
private void reportOnMoved() {
if ( movementConstraint == CONSTRAIN_CIRCLE )
constrainCircle();
else
constrainBox();
calcUserCoordinates();
if (moveListener != null) {
boolean rx = Math.abs(touchX - reportX) >= moveResolution;
boolean ry = Math.abs(touchY - reportY) >= moveResolution;
if (rx || ry) {
this.reportX = touchX;
this.reportY = touchY;
// Log.d(TAG, String.format("moveListener.OnMoved(%d,%d)", (int)userX, (int)userY));
moveListener.OnMoved(userX, userY);
}
}
}
private void calcUserCoordinates() {
//First convert to cartesian coordinates
cartX = (int)(touchX / movementRadius * movementRange);
cartY = (int)(touchY / movementRadius * movementRange);
radial = Math.sqrt((cartX*cartX) + (cartY*cartY));
angle = Math.atan2(cartY, cartX);
//Invert Y axis if requested
if ( !yAxisInverted )
cartY *= -1;
if ( userCoordinateSystem == COORDINATE_CARTESIAN ) {
userX = cartX;
userY = cartY;
}
else if ( userCoordinateSystem == COORDINATE_DIFFERENTIAL ) {
userX = cartY + cartX / 4;
userY = cartY - cartX / 4;
if ( userX < -movementRange )
userX = (int)-movementRange;
if ( userX > movementRange )
userX = (int)movementRange;
if ( userY < -movementRange )
userY = (int)-movementRange;
if ( userY > movementRange )
userY = (int)movementRange;
}
}
//Simple pressure click
private void reportOnPressure() {
// Log.d(TAG, String.format("touchPressure=%.2f", this.touchPressure));
if ( clickListener != null ) {
if ( clicked && touchPressure < clickThreshold ) {
clickListener.OnReleased();
this.clicked = false;
// Log.d(TAG, "reset click");
invalidate();
}
else if ( !clicked && touchPressure >= clickThreshold ) {
clicked = true;
clickListener.OnClicked();
// Log.d(TAG, "click");
invalidate();
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
}
}
private void returnHandleToCenter() {
if ( autoReturnToCenter ) {
final int numberOfFrames = 5;
final double intervalsX = (0 - touchX) / numberOfFrames;
final double intervalsY = (0 - touchY) / numberOfFrames;
for (int i = 0; i < numberOfFrames; i++) {
final int j = i;
postDelayed(new Runnable() {
@Override
public void run() {
touchX += intervalsX;
touchY += intervalsY;
reportOnMoved();
invalidate();
if (moveListener != null && j == numberOfFrames - 1) {
moveListener.OnReturnedToCenter();
}
}
}, i * 40);
}
if (moveListener != null) {
moveListener.OnReleased();
}
}
}
public void setTouchOffset(int x, int y) {
offsetX = x;
offsetY = y;
}
}

View File

@ -0,0 +1,169 @@
package com.MobileAnarchy.Android.Widgets.ThresholdEditText;
import android.content.Context;
import android.os.Handler;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.widget.EditText;
public class ThresholdEditText extends EditText {
// =========================================
// Private members
// =========================================
private int threshold;
private ThresholdTextChanged thresholdTextChanged;
private Handler handler;
private Runnable invoker;
private boolean thresholdDisabledOnEmptyInput;
// =========================================
// Constructors
// =========================================
public ThresholdEditText(Context context) {
super(context);
initAttributes(null);
init();
}
public ThresholdEditText(Context context, AttributeSet attrs) {
super(context, attrs);
initAttributes(attrs);
init();
}
// =========================================
// Public properties
// =========================================
/**
* Get the current threshold value
*/
public int getThreshold() {
return threshold;
}
/**
* Set the threshold value (in milliseconds)
*
* @param threshold
* Threshold value
*/
public void setThreshold(int threshold) {
this.threshold = threshold;
}
/**
* @return True = the callback will fire immediately when the content of the
* EditText is emptied False = The threshold will be used even on
* empty input
*/
public boolean getThresholdDisabledOnEmptyInput() {
return thresholdDisabledOnEmptyInput;
}
/**
* @param thresholdDisabledOnEmptyInput
* Set to true if you want the callback to fire immediately when
* the content of the EditText is emptied
*/
public void setThresholdDisabledOnEmptyInput(
boolean thresholdDisabledOnEmptyInput) {
this.thresholdDisabledOnEmptyInput = thresholdDisabledOnEmptyInput;
}
/**
* Set the callback to the OnThresholdTextChanged event
*
* @param listener
*/
public void setOnThresholdTextChanged(ThresholdTextChanged listener) {
this.thresholdTextChanged = listener;
}
// =========================================
// Private / Protected methods
// =========================================
/**
* Load properties values from xml layout
*/
private void initAttributes(AttributeSet attrs) {
if (attrs != null) {
String namespace = "http://com.MobileAnarchy.Android.Widgets";
// Load values to local members
this.threshold = attrs.getAttributeIntValue(namespace, "threshold",
500);
this.thresholdDisabledOnEmptyInput = attrs.getAttributeBooleanValue(
namespace, "disableThresholdOnEmptyInput", true);
} else {
// Default threshold value is 0.5 seconds
threshold = 500;
// Default behaviour on emptied text - no threshold
thresholdDisabledOnEmptyInput = true;
}
}
/**
* Initialize the private members with default values
*/
private void init() {
handler = new Handler();
invoker = new Runnable() {
@Override
public void run() {
invokeCallback();
}
};
this.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// Remove any existing pending callbacks
handler.removeCallbacks(invoker);
if (s.length() == 0 && thresholdDisabledOnEmptyInput) {
// The text is empty, so invoke the callback immediately
invoker.run();
} else {
// Post a new delayed callback
handler.postDelayed(invoker, threshold);
}
}
});
}
/**
* Invoking the callback on the listener provided (if provided)
*/
private void invokeCallback() {
if (thresholdTextChanged != null) {
thresholdTextChanged.onThersholdTextChanged(this.getText());
}
}
}

View File

@ -0,0 +1,7 @@
package com.MobileAnarchy.Android.Widgets.ThresholdEditText;
import android.text.Editable;
public interface ThresholdTextChanged {
void onThersholdTextChanged(Editable text);
}

View File

@ -0,0 +1,68 @@
package com.MobileAnarchy.Android.Widgets.TilesLayout;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
public class SingleTileLayout extends FrameLayout {
// =========================================
// Private members
// =========================================
private TilePosition position;
private long timestamp;
// =========================================
// Constructors
// =========================================
public SingleTileLayout(Context context) {
super(context);
}
public SingleTileLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
// =========================================
// Public Methods
// =========================================
public TilePosition getPosition() {
return position;
}
public void setPosition(TilePosition position) {
this.position = position;
}
public long getTimestamp() {
return this.timestamp;
}
// =========================================
// Overrides
// =========================================
@Override
public void addView(View child) {
super.addView(child);
timestamp = java.lang.System.currentTimeMillis();
}
@Override
public void removeAllViews() {
super.removeAllViews();
timestamp = 0;
}
@Override
public void removeView(View view) {
super.removeView(view);
timestamp = 0;
}
}

View File

@ -0,0 +1,63 @@
package com.MobileAnarchy.Android.Widgets.TilesLayout;
public class TilePosition {
// =========================================
// Private Members
// =========================================
private float x, y, height, width;
// =========================================
// Constructors
// =========================================
public TilePosition(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
}
// =========================================
// Public Properties
// =========================================
public float getX() {
return x;
}
public float getY() {
return y;
}
public float getHeight() {
return height;
}
public float getWidth() {
return width;
}
// =========================================
// Public Methods
// =========================================
public Boolean equals(TilePosition position) {
if (position == null)
return false;
return this.x == position.x &&
this.y == position.y &&
this.height == position.height &&
this.width == position.width;
}
@Override
public String toString() {
return "TilePosition = [X: " + x + ", Y: " + y + ", Height: " + height + ", Width: " + width + "]";
}
}

View File

@ -0,0 +1,299 @@
package com.MobileAnarchy.Android.Widgets.TilesLayout;
import java.util.ArrayList;
import java.util.List;
import android.R;
import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.view.animation.Animation.AnimationListener;
import android.widget.FrameLayout;
public class TilesLayout extends FrameLayout {
// =========================================
// Private members
// =========================================
private static final String TAG = "TilesLayout";
private int animatedTransitionDuration;
private List<SingleTileLayout> tiles;
private TilesLayoutPreset preset;
private int tileBackgroundResourceId;
// =========================================
// Constructors
// =========================================
public TilesLayout(Context context) {
super(context);
Init(null);
}
public TilesLayout(Context context, AttributeSet attrs) {
super(context, attrs);
Init(attrs);
}
// =========================================
// Public Properties
// =========================================
public void setPreset(TilesLayoutPreset preset) {
try {
rebuildLayout(preset);
this.preset = preset;
}
catch (Exception ex) {
Log.e(TAG, "Failed to set layout preset", ex);
}
}
public TilesLayoutPreset getPreset() {
return this.preset;
}
public int getAnimatedTransitionDuration() {
return animatedTransitionDuration;
}
public void setAnimatedTransitionDuration(int animatedTransitionDuration) {
this.animatedTransitionDuration = animatedTransitionDuration;
}
public int getTileBackgroundResourceId() {
return tileBackgroundResourceId;
}
public void setTileBackgroundResourceId(int tileBackgroundResourceId) {
this.tileBackgroundResourceId = tileBackgroundResourceId;
}
// =========================================
// Public Methods
// =========================================
public void addContent(View view) {
for (int i = 0; i < tiles.size(); i++) {
if (tiles.get(i).getChildCount() == 0) {
tiles.get(i).addView(view);
return;
}
}
// No available space for the new view...
// TODO: Take the tile with the smallest time stamp and place the new view in it
}
public void clearView(int tileId) {
if (tiles.size() < tileId) {
tiles.get(tileId).removeAllViews();
}
}
// =========================================
// Private Methods
// =========================================
private void Init(AttributeSet attrs) {
animatedTransitionDuration = 750;
tileBackgroundResourceId = R.drawable.edit_text;
tiles = new ArrayList<SingleTileLayout>();
}
private void rebuildLayout(TilesLayoutPreset preset) {
ArrayList<TilePosition> positions = buildViewsPositions(preset);
// We need to transform the current layout, to the new layout
int extraViews = tiles.size() - positions.size();
if (extraViews > 0) {
// Remove the extra views
while(tiles.size() - positions.size() > 0) {
int lastViewPosition = tiles.size() - 1;
removeView(tiles.get(lastViewPosition));
tiles.remove(lastViewPosition);
}
} else {
// Add the extra views
for (int i = tiles.size(); i< positions.size(); i++) {
TilePosition newTilePosition = positions.get(i);
SingleTileLayout tile = new SingleTileLayout(getContext());
tile.setBackgroundResource(tileBackgroundResourceId);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
(int)newTilePosition.getWidth(),
(int)newTilePosition.getHeight(),
Gravity.TOP + Gravity.LEFT);
lp.setMargins((int)newTilePosition.getX(),
(int)newTilePosition.getY(), 0, 0);
tile.setLayoutParams(lp);
tiles.add(tile);
addView(tile);
}
}
// There is a bug in the animation-set, so we'll not animate
animateChange(positions);
// Regular repositioning (no animation)
//processChange(positions);
}
private ArrayList<TilePosition> buildViewsPositions(TilesLayoutPreset preset) {
int width = getWidth();
int height = getHeight();
Log.d(TAG, "Container's Dimensions = Width: " + width + ", Height: " + height);
ArrayList<TilePosition> actualPositions = new ArrayList<TilePosition>();
for (TilePosition position : preset.getTilePositions()) {
int tileX = (int) Math.round(width * ((float)position.getX() / 100.0));
int tileY = (int) Math.round(height * ((float)position.getY() / 100.0));
int tileWidth = (int) Math.round(width * ((float)position.getWidth() / 100.0));
int tileHeight = (int) Math.round(height * ((float)position.getHeight() / 100.0));
TilePosition actualPosition = new TilePosition(tileX, tileY, tileWidth, tileHeight);
actualPositions.add(actualPosition);
Log.d(TAG, "New tile created - X: " + tileX + ", Y: " + tileY + ", Width: " + tileWidth + ", Height: " + tileHeight);
}
return actualPositions;
}
@SuppressWarnings("unused")
private void processChange(ArrayList<TilePosition> positions) {
for (int i = 0; i < tiles.size(); i++) {
final SingleTileLayout currentTile = tiles.get(i);
final TilePosition targetPosition = positions.get(i);
currentTile.setPosition(targetPosition);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
(int)targetPosition.getWidth(),
(int)targetPosition.getHeight(),
Gravity.TOP + Gravity.LEFT);
lp.setMargins((int)targetPosition.getX(),
(int)targetPosition.getY(), 0, 0);
currentTile.setLayoutParams(lp);
}
}
private void animateChange(ArrayList<TilePosition> positions) {
AnimationSet animationSet = new AnimationSet(true);
DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator();
for (int i = 0; i < tiles.size(); i++) {
AnimationSet scaleAndMove = new AnimationSet(true);
scaleAndMove.setFillAfter(true);
final SingleTileLayout currentTile = tiles.get(i);
TilePosition currentPosition = currentTile.getPosition();
final TilePosition targetPosition = positions.get(i);
if (currentPosition == null) {
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(animatedTransitionDuration);
alphaAnimation.setStartOffset(0);
currentTile.setAnimation(alphaAnimation);
scaleAndMove.addAnimation(alphaAnimation);
}
currentTile.setPosition(targetPosition);
if (!targetPosition.equals(currentPosition)) {
float toXDelta = 0, toYDelta = 0;
if (currentPosition != null) {
// Calculate new position
toXDelta = targetPosition.getX() - currentPosition.getX();
toYDelta = targetPosition.getY() - currentPosition.getY();
// Factor in the scaling animation
toXDelta = toXDelta / (targetPosition.getWidth() / currentPosition.getWidth());
toYDelta = toYDelta / (targetPosition.getHeight() / currentPosition.getHeight());
}
// Move
TranslateAnimation moveAnimation = new TranslateAnimation(0, toXDelta, 0, toYDelta);
moveAnimation.setDuration(animatedTransitionDuration);
moveAnimation.setStartOffset(0);
moveAnimation.setFillAfter(true);
moveAnimation.setInterpolator(decelerateInterpolator);
scaleAndMove.addAnimation(moveAnimation);
// Physically move the tile when the animation ends
scaleAndMove.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) { }
@Override
public void onAnimationRepeat(Animation animation) { }
@Override
public void onAnimationEnd(Animation animation) {
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
(int)targetPosition.getWidth(),
(int)targetPosition.getHeight(),
Gravity.TOP + Gravity.LEFT);
lp.setMargins((int)targetPosition.getX(),
(int)targetPosition.getY(), 0, 0);
currentTile.setLayoutParams(lp);
// The following null animation just gets rid of screen flicker
animation = new TranslateAnimation(0.0f, 0.0f, 0.0f, 0.0f);
animation.setDuration(1);
currentTile.startAnimation(animation);
}
});
// Scale
if (currentPosition != null) {
ScaleAnimation scaleAnimation =
new ScaleAnimation(1,
targetPosition.getWidth() / currentPosition.getWidth(),
1,
targetPosition.getHeight() / currentPosition.getHeight(),
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0);
scaleAnimation.setDuration(animatedTransitionDuration);
scaleAnimation.setStartOffset(0);
scaleAnimation.setFillAfter(true);
scaleAndMove.addAnimation(scaleAnimation);
}
// Set animation to the tile
currentTile.setAnimation(scaleAndMove);
}
// Add to the total animation set
animationSet.addAnimation(scaleAndMove);
}
if (animationSet.getAnimations().size() > 0) {
Log.d(TAG, "Starting animation");
animationSet.setFillAfter(true);
animationSet.start();
}
}
}

View File

@ -0,0 +1,157 @@
package com.MobileAnarchy.Android.Widgets.TilesLayout;
import java.util.LinkedList;
import java.util.List;
/**
* Describes the positioning of a tiles in a 100x100 environment
*/
public class TilesLayoutPreset {
// =========================================
// Private Members
// =========================================
private List<TilePosition> _positions;
private String _presetName;
// =========================================
// Constructors
// =========================================
public TilesLayoutPreset(String name) {
_presetName = name;
_positions = new LinkedList<TilePosition>();
}
// =========================================
// Public Methods
// =========================================
public void add(float x, float y, float width, float height) {
TilePosition tilePosition = new TilePosition(x, y, height, width);
add(tilePosition);
}
public void add(TilePosition tilePosition) {
_positions.add(tilePosition);
}
public Iterable<TilePosition> getTilePositions() {
return _positions;
}
public int getCount() {
return _positions.size();
}
// =========================================
// Static Presets Factories
// =========================================
public static TilesLayoutPreset get1x1() {
TilesLayoutPreset preset = new TilesLayoutPreset("Default 1X1");
preset.add(0, 0, 100, 100);
return preset;
}
public static TilesLayoutPreset get1x2() {
TilesLayoutPreset preset = new TilesLayoutPreset("Default 1X2");
preset.add(0, 0, 50, 100);
preset.add(0, 50, 50, 100);
return preset;
}
public static TilesLayoutPreset get2x1() {
TilesLayoutPreset preset = new TilesLayoutPreset("Default 2X1");
preset.add(0, 0, 100, 50);
preset.add(50, 0, 100, 50);
return preset;
}
public static TilesLayoutPreset get2x2() {
TilesLayoutPreset preset = new TilesLayoutPreset("Default 2X2");
preset.add(0, 0, 50, 50);
preset.add(50, 0, 50, 50);
preset.add(0, 50, 50, 50);
preset.add(50, 50, 50, 50);
return preset;
}
public static TilesLayoutPreset get3x3() {
TilesLayoutPreset preset = new TilesLayoutPreset("Default 3X3");
preset.add(0, 0, 100/3f, 100/3f);
preset.add(100/3f, 0, 100/3f, 100/3f);
preset.add(200/3f, 0, 100/3f, 100/3f);
preset.add(0, 100/3f, 100/3f, 100/3f);
preset.add(100/3f, 100/3f, 100/3f, 100/3f);
preset.add(200/3f, 100/3f, 100/3f, 100/3f);
preset.add(0, 200/3f, 100/3f, 100/3f);
preset.add(100/3f, 200/3f, 100/3f, 100/3f);
preset.add(200/3f, 200/3f, 100/3f, 100/3f);
return preset;
}
public static TilesLayoutPreset get3x2() {
TilesLayoutPreset preset = new TilesLayoutPreset("Default 3X2");
preset.add(0, 0, 50, 100/3f);
preset.add(100/3f, 0, 50, 100/3f);
preset.add(200/3f, 0, 50, 100/3f);
preset.add(0, 50, 50, 100/3f);
preset.add(100/3f, 50, 50, 100/3f);
preset.add(200/3f, 50, 50, 100/3f);
return preset;
}
public static TilesLayoutPreset get2x3() {
TilesLayoutPreset preset = new TilesLayoutPreset("Default 2X3");
preset.add(0, 0, 100/3f, 50);
preset.add(50, 0, 100/3f, 50);
preset.add(0, 100/3f, 100/3f, 50);
preset.add(50, 100/3f, 100/3f, 50);
preset.add(0, 200/3f, 100/3f, 50);
preset.add(50, 200/3f, 100/3f, 50);
return preset;
}
public static TilesLayoutPreset get4x4() {
TilesLayoutPreset preset = new TilesLayoutPreset("Default 2X2");
preset.add(0, 0, 25, 25);
preset.add(25, 0, 25, 25);
preset.add(50, 0, 25, 25);
preset.add(75, 0, 25, 25);
preset.add(0, 25, 25, 25);
preset.add(25, 25, 25, 25);
preset.add(50, 25, 25, 25);
preset.add(75, 25, 25, 25);
preset.add(0, 50, 25, 25);
preset.add(25, 50, 25, 25);
preset.add(50, 50, 25, 25);
preset.add(75, 50, 25, 25);
preset.add(0, 75, 25, 25);
preset.add(25, 75, 25, 25);
preset.add(50, 75, 25, 25);
preset.add(75, 75, 25, 25);
return preset;
}
public static TilesLayoutPreset get2x3x3() {
TilesLayoutPreset preset = new TilesLayoutPreset("Custom 2X4X4");
preset.add(0, 0, 50, 50);
preset.add(50, 0, 50, 50);
preset.add(0, 50, 50, 50);
preset.add(50, 50, 25, 25);
preset.add(75, 50, 25, 25);
preset.add(50, 75, 25, 25);
preset.add(75, 75, 25, 25);
return preset;
}
}