Mega Code Archive

 
Categories / Android / Hardware
 

Joystick View

//package com.MobileAnarchy.Android.Widgets.Joystick; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Shader; import android.util.AttributeSet; import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; interface JoystickClickedListener {   public void OnClicked();   public void OnReleased(); } public class JoystickView extends View {   public static final int INVALID_POINTER_ID = -1;      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;   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();   }   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.setStyle(Paint.Style.FILL);     handlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);     handlePaint.setColor(Color.rgb(8, 8, 8));     handlePaint.setStrokeWidth(1);     handlePaint.setStyle(Paint.Style.FILL_AND_STROKE);     innerPadding = 10;          setMovementRange(150);     setMoveResolution(0.1f);     setClickThreshold(0.4f);     setYAxisInverted(false);     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 void setOnJostickMovedListener(JoystickMovedListener listener) {     this.moveListener = listener;   }      public void setOnJostickClickedListener(JoystickClickedListener listener) {     this.clickListener = listener;   }      @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;          // Texturize the back of the joysticks     Bitmap backTexture = null;//BitmapFactory.decodeResource(getResources(), R.drawable.joy_back);     backTexture = Bitmap.createScaledBitmap(backTexture, dimX, dimY, true);     BitmapShader backShader = new BitmapShader(backTexture, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);     bgPaint.setShader(backShader);          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;   } } interface JoystickMovedListener {   public void OnMoved(int pan, int tilt);   public void OnReleased();   public void OnReturnedToCenter(); }