Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package com.example.rcclient;
- 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;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement