Advertisement
Guest User

Untitled

a guest
Oct 31st, 2014
157
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 21.45 KB | None | 0 0
  1. package com.example.rcclient;
  2.  
  3.  
  4. import android.content.Context;
  5. import android.graphics.Canvas;
  6. import android.graphics.Color;
  7. import android.graphics.Paint;
  8. //import android.os.Handler;
  9. import android.util.AttributeSet;
  10. import android.util.Log;
  11. import android.view.HapticFeedbackConstants;
  12. import android.view.MotionEvent;
  13. import android.view.View;
  14.  
  15. public class JoystickView extends View {
  16. public static final int INVALID_POINTER_ID = -1;
  17.  
  18. // =========================================
  19. // Private Members
  20. // =========================================
  21. private final boolean D = false;
  22. String TAG = "JoystickView";
  23.  
  24. private Paint dbgPaint1;
  25. private Paint dbgPaint2;
  26.  
  27. private Paint bgPaint;
  28. private Paint handlePaint;
  29.  
  30. private int innerPadding;
  31. private int bgRadius;
  32. private int handleRadius;
  33. private int movementRadius;
  34. private int handleInnerBoundaries;
  35.  
  36. private JoystickMovedListener moveListener;
  37. private JoystickClickedListener clickListener;
  38.  
  39. //# of pixels movement required between reporting to the listener
  40. private float moveResolution;
  41.  
  42. private boolean yAxisInverted;
  43. private boolean autoReturnToCenter;
  44.  
  45. //Max range of movement in user coordinate system
  46. public final static int CONSTRAIN_BOX = 0;
  47. public final static int CONSTRAIN_CIRCLE = 1;
  48. private int movementConstraint;
  49. private float movementRange;
  50.  
  51. public final static int COORDINATE_CARTESIAN = 0; //Regular cartesian coordinates
  52. public final static int COORDINATE_DIFFERENTIAL = 1; //Uses polar rotation of 45 degrees to calc differential drive paramaters
  53. private int userCoordinateSystem;
  54.  
  55. //Records touch pressure for click handling
  56. private float touchPressure;
  57. private boolean clicked;
  58. private float clickThreshold;
  59.  
  60. //Last touch point in view coordinates
  61. private int pointerId = INVALID_POINTER_ID;
  62. private float touchX, touchY;
  63.  
  64. //Last reported position in view coordinates (allows different reporting sensitivities)
  65. private float reportX, reportY;
  66.  
  67. //Handle center in view coordinates
  68. private float handleX, handleY;
  69.  
  70. //Center of the view in view coordinates
  71. private int cX, cY;
  72.  
  73. //Size of the view in view coordinates
  74. private int dimX, dimY;
  75.  
  76. //Cartesian coordinates of last touch point - joystick center is (0,0)
  77. private int cartX, cartY;
  78.  
  79. //Polar coordinates of the touch point from joystick center
  80. private double radial;
  81. private double angle;
  82.  
  83. //User coordinates of last touch point
  84. private int userX, userY;
  85.  
  86. //Offset co-ordinates (used when touch events are received from parent's coordinate origin)
  87. private int offsetX;
  88. private int offsetY;
  89.  
  90. // =========================================
  91. // Constructors
  92. // =========================================
  93.  
  94. public JoystickView(Context context) {
  95. super(context);
  96. initJoystickView();
  97. }
  98.  
  99. public JoystickView(Context context, AttributeSet attrs) {
  100. super(context, attrs);
  101. initJoystickView();
  102. }
  103.  
  104. public JoystickView(Context context, AttributeSet attrs, int defStyle) {
  105. super(context, attrs, defStyle);
  106. initJoystickView();
  107. }
  108.  
  109. // =========================================
  110. // Initialization
  111. // =========================================
  112.  
  113. private void initJoystickView() {
  114. setFocusable(true);
  115.  
  116. dbgPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
  117. dbgPaint1.setColor(Color.RED);
  118. dbgPaint1.setStrokeWidth(1);
  119. dbgPaint1.setStyle(Paint.Style.STROKE);
  120.  
  121. dbgPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
  122. dbgPaint2.setColor(Color.GREEN);
  123. dbgPaint2.setStrokeWidth(1);
  124. dbgPaint2.setStyle(Paint.Style.STROKE);
  125.  
  126. bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  127. bgPaint.setColor(Color.GRAY);
  128. bgPaint.setStrokeWidth(1);
  129. bgPaint.setStyle(Paint.Style.FILL_AND_STROKE);
  130.  
  131. handlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  132. handlePaint.setColor(Color.DKGRAY);
  133. handlePaint.setStrokeWidth(1);
  134. handlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
  135.  
  136. innerPadding = 10;
  137.  
  138. setMovementRange(10);
  139. setMoveResolution(1.0f);
  140. setClickThreshold(0.4f);
  141. setYAxisInverted(true);
  142. setUserCoordinateSystem(COORDINATE_CARTESIAN);
  143. setAutoReturnToCenter(true);
  144. }
  145.  
  146. public void setAutoReturnToCenter(boolean autoReturnToCenter) {
  147. this.autoReturnToCenter = autoReturnToCenter;
  148. }
  149.  
  150. public boolean isAutoReturnToCenter() {
  151. return autoReturnToCenter;
  152. }
  153.  
  154. public void setUserCoordinateSystem(int userCoordinateSystem) {
  155. if (userCoordinateSystem < COORDINATE_CARTESIAN || movementConstraint > COORDINATE_DIFFERENTIAL)
  156. Log.e(TAG, "invalid value for userCoordinateSystem");
  157. else
  158. this.userCoordinateSystem = userCoordinateSystem;
  159. }
  160.  
  161. public int getUserCoordinateSystem() {
  162. return userCoordinateSystem;
  163. }
  164.  
  165. public void setMovementConstraint(int movementConstraint) {
  166. if (movementConstraint < CONSTRAIN_BOX || movementConstraint > CONSTRAIN_CIRCLE)
  167. Log.e(TAG, "invalid value for movementConstraint");
  168. else
  169. this.movementConstraint = movementConstraint;
  170. }
  171.  
  172. public int getMovementConstraint() {
  173. return movementConstraint;
  174. }
  175.  
  176. public boolean isYAxisInverted() {
  177. return yAxisInverted;
  178. }
  179.  
  180. public void setYAxisInverted(boolean yAxisInverted) {
  181. this.yAxisInverted = yAxisInverted;
  182. }
  183.  
  184. /**
  185. * Set the pressure sensitivity for registering a click
  186. * @param clickThreshold threshold 0...1.0f inclusive. 0 will cause clicks to never be reported, 1.0 is a very hard click
  187. */
  188. public void setClickThreshold(float clickThreshold) {
  189. if (clickThreshold < 0 || clickThreshold > 1.0f)
  190. Log.e(TAG, "clickThreshold must range from 0...1.0f inclusive");
  191. else
  192. this.clickThreshold = clickThreshold;
  193. }
  194.  
  195. public float getClickThreshold() {
  196. return clickThreshold;
  197. }
  198.  
  199. public void setMovementRange(float movementRange) {
  200. this.movementRange = movementRange;
  201. }
  202.  
  203. public float getMovementRange() {
  204. return movementRange;
  205. }
  206.  
  207. public void setMoveResolution(float moveResolution) {
  208. this.moveResolution = moveResolution;
  209. }
  210.  
  211. public float getMoveResolution() {
  212. return moveResolution;
  213. }
  214.  
  215. // =========================================
  216. // Public Methods
  217. // =========================================
  218.  
  219. public void setOnJostickMovedListener(JoystickMovedListener listener) {
  220. this.moveListener = listener;
  221. }
  222.  
  223. public void setOnJostickClickedListener(JoystickClickedListener listener) {
  224. this.clickListener = listener;
  225. }
  226.  
  227. // =========================================
  228. // Drawing Functionality
  229. // =========================================
  230.  
  231. @Override
  232. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  233. // Here we make sure that we have a perfect circle
  234. int measuredWidth = measure(widthMeasureSpec);
  235. int measuredHeight = measure(heightMeasureSpec);
  236. setMeasuredDimension(measuredWidth, measuredHeight);
  237. }
  238.  
  239. @Override
  240. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  241. super.onLayout(changed, left, top, right, bottom);
  242.  
  243. int d = Math.min(getMeasuredWidth(), getMeasuredHeight());
  244.  
  245. dimX = d;
  246. dimY = d;
  247.  
  248. cX = d / 2;
  249. cY = d / 2;
  250.  
  251. bgRadius = dimX/2 - innerPadding;
  252. handleRadius = (int)(d * 0.25);
  253. handleInnerBoundaries = handleRadius;
  254. movementRadius = Math.min(cX, cY) - handleInnerBoundaries;
  255. }
  256.  
  257. private int measure(int measureSpec) {
  258. int result = 0;
  259. // Decode the measurement specifications.
  260. int specMode = MeasureSpec.getMode(measureSpec);
  261. int specSize = MeasureSpec.getSize(measureSpec);
  262. if (specMode == MeasureSpec.UNSPECIFIED) {
  263. // Return a default size of 200 if no bounds are specified.
  264. result = 200;
  265. } else {
  266. // As you want to fill the available space
  267. // always return the full available bounds.
  268. result = specSize;
  269. }
  270. return result;
  271. }
  272.  
  273. @Override
  274. protected void onDraw(Canvas canvas) {
  275. canvas.save();
  276. // Draw the background
  277. canvas.drawCircle(cX, cY, bgRadius, bgPaint);
  278.  
  279. // Draw the handle
  280. handleX = touchX + cX;
  281. handleY = touchY + cY;
  282. canvas.drawCircle(handleX, handleY, handleRadius, handlePaint);
  283.  
  284. if (D) {
  285. canvas.drawRect(1, 1, getMeasuredWidth()-1, getMeasuredHeight()-1, dbgPaint1);
  286.  
  287. canvas.drawCircle(handleX, handleY, 3, dbgPaint1);
  288.  
  289. if ( movementConstraint == CONSTRAIN_CIRCLE ) {
  290. canvas.drawCircle(cX, cY, this.movementRadius, dbgPaint1);
  291. }
  292. else {
  293. canvas.drawRect(cX-movementRadius, cY-movementRadius, cX+movementRadius, cY+movementRadius, dbgPaint1);
  294. }
  295.  
  296. //Origin to touch point
  297. canvas.drawLine(cX, cY, handleX, handleY, dbgPaint2);
  298.  
  299. int baseY = (int) (touchY < 0 ? cY + handleRadius : cY - handleRadius);
  300. canvas.drawText(String.format("%s (%.0f,%.0f)", TAG, touchX, touchY), handleX-20, baseY-7, dbgPaint2);
  301. canvas.drawText("("+ String.format("%.0f, %.1f", radial, angle * 57.2957795) + (char) 0x00B0 + ")", handleX-20, baseY+15, dbgPaint2);
  302. }
  303.  
  304. // Log.d(TAG, String.format("touch(%f,%f)", touchX, touchY));
  305. // Log.d(TAG, String.format("onDraw(%.1f,%.1f)\n\n", handleX, handleY));
  306. canvas.restore();
  307. }
  308.  
  309. // Constrain touch within a box
  310. private void constrainBox() {
  311. touchX = Math.max(Math.min(touchX, movementRadius), -movementRadius);
  312. touchY = Math.max(Math.min(touchY, movementRadius), -movementRadius);
  313. }
  314.  
  315. // Constrain touch within a circle
  316. private void constrainCircle() {
  317. float diffX = touchX;
  318. float diffY = touchY;
  319. double radial = Math.sqrt((diffX*diffX) + (diffY*diffY));
  320. if ( radial > movementRadius ) {
  321. touchX = (int)((diffX / radial) * movementRadius);
  322. touchY = (int)((diffY / radial) * movementRadius);
  323. }
  324. }
  325.  
  326. public void setPointerId(int id) {
  327. this.pointerId = id;
  328. }
  329.  
  330. public int getPointerId() {
  331. return pointerId;
  332. }
  333.  
  334. @Override
  335. public boolean onTouchEvent(MotionEvent ev) {
  336. final int action = ev.getAction();
  337. switch (action & MotionEvent.ACTION_MASK) {
  338. case MotionEvent.ACTION_MOVE: {
  339. return processMoveEvent(ev);
  340. }
  341. case MotionEvent.ACTION_CANCEL:
  342. case MotionEvent.ACTION_UP: {
  343. if ( pointerId != INVALID_POINTER_ID ) {
  344. // Log.d(TAG, "ACTION_UP");
  345. returnHandleToCenter();
  346. setPointerId(INVALID_POINTER_ID);
  347. }
  348. break;
  349. }
  350. case MotionEvent.ACTION_POINTER_UP: {
  351. if ( pointerId != INVALID_POINTER_ID ) {
  352. final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  353. final int pointerId = ev.getPointerId(pointerIndex);
  354. if ( pointerId == this.pointerId ) {
  355. // Log.d(TAG, "ACTION_POINTER_UP: " + pointerId);
  356. returnHandleToCenter();
  357. setPointerId(INVALID_POINTER_ID);
  358. return true;
  359. }
  360. }
  361. break;
  362. }
  363. case MotionEvent.ACTION_DOWN: {
  364. if ( pointerId == INVALID_POINTER_ID ) {
  365. int x = (int) ev.getX();
  366. if ( x >= offsetX && x < offsetX + dimX ) {
  367. setPointerId(ev.getPointerId(0));
  368. // Log.d(TAG, "ACTION_DOWN: " + getPointerId());
  369. return true;
  370. }
  371. }
  372. break;
  373. }
  374. case MotionEvent.ACTION_POINTER_DOWN: {
  375. if ( pointerId == INVALID_POINTER_ID ) {
  376. final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  377. final int pointerId = ev.getPointerId(pointerIndex);
  378. int x = (int) ev.getX(pointerId);
  379. if ( x >= offsetX && x < offsetX + dimX ) {
  380. // Log.d(TAG, "ACTION_POINTER_DOWN: " + pointerId);
  381. setPointerId(pointerId);
  382. return true;
  383. }
  384. }
  385. break;
  386. }
  387. }
  388. return false;
  389. }
  390.  
  391. private boolean processMoveEvent(MotionEvent ev) {
  392. if ( pointerId != INVALID_POINTER_ID ) {
  393. final int pointerIndex = ev.findPointerIndex(pointerId);
  394.  
  395. // Translate touch position to center of view
  396. float x = ev.getX(pointerIndex);
  397. touchX = x - cX - offsetX;
  398. float y = ev.getY(pointerIndex);
  399. touchY = y - cY - offsetY;
  400.  
  401. // Log.d(TAG, String.format("ACTION_MOVE: (%03.0f, %03.0f) => (%03.0f, %03.0f)", x, y, touchX, touchY));
  402.  
  403. reportOnMoved();
  404. invalidate();
  405.  
  406. touchPressure = ev.getPressure(pointerIndex);
  407. reportOnPressure();
  408.  
  409. return true;
  410. }
  411. return false;
  412. }
  413.  
  414. private void reportOnMoved() {
  415. if ( movementConstraint == CONSTRAIN_CIRCLE )
  416. constrainCircle();
  417. else
  418. constrainBox();
  419.  
  420. calcUserCoordinates();
  421.  
  422. if (moveListener != null) {
  423. boolean rx = Math.abs(touchX - reportX) >= moveResolution;
  424. boolean ry = Math.abs(touchY - reportY) >= moveResolution;
  425. if (rx || ry) {
  426. this.reportX = touchX;
  427. this.reportY = touchY;
  428.  
  429. // Log.d(TAG, String.format("moveListener.OnMoved(%d,%d)", (int)userX, (int)userY));
  430. moveListener.OnMoved(userX, userY);
  431. }
  432. }
  433. }
  434.  
  435. private void calcUserCoordinates() {
  436. //First convert to cartesian coordinates
  437. cartX = (int)(touchX / movementRadius * movementRange);
  438. cartY = (int)(touchY / movementRadius * movementRange);
  439.  
  440. radial = Math.sqrt((cartX*cartX) + (cartY*cartY));
  441. angle = Math.atan2(cartY, cartX);
  442.  
  443. //Invert Y axis if requested
  444. if ( !yAxisInverted )
  445. cartY *= -1;
  446.  
  447. if ( userCoordinateSystem == COORDINATE_CARTESIAN ) {
  448. userX = cartX;
  449. userY = cartY;
  450. }
  451. else if ( userCoordinateSystem == COORDINATE_DIFFERENTIAL ) {
  452. userX = cartY + cartX / 4;
  453. userY = cartY - cartX / 4;
  454.  
  455. if ( userX < -movementRange )
  456. userX = (int)-movementRange;
  457. if ( userX > movementRange )
  458. userX = (int)movementRange;
  459.  
  460. if ( userY < -movementRange )
  461. userY = (int)-movementRange;
  462. if ( userY > movementRange )
  463. userY = (int)movementRange;
  464. }
  465.  
  466. }
  467.  
  468. //Simple pressure click
  469. private void reportOnPressure() {
  470. // Log.d(TAG, String.format("touchPressure=%.2f", this.touchPressure));
  471. if ( clickListener != null ) {
  472. if ( clicked && touchPressure < clickThreshold ) {
  473. clickListener.OnReleased();
  474. this.clicked = false;
  475. // Log.d(TAG, "reset click");
  476. invalidate();
  477. }
  478. else if ( !clicked && touchPressure >= clickThreshold ) {
  479. clicked = true;
  480. clickListener.OnClicked();
  481. // Log.d(TAG, "click");
  482. invalidate();
  483. performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
  484. }
  485. }
  486. }
  487.  
  488. private void returnHandleToCenter() {
  489. if ( autoReturnToCenter ) {
  490. final int numberOfFrames = 5;
  491. final double intervalsX = (0 - touchX) / numberOfFrames;
  492. final double intervalsY = (0 - touchY) / numberOfFrames;
  493.  
  494. for (int i = 0; i < numberOfFrames; i++) {
  495. final int j = i;
  496. postDelayed(new Runnable() {
  497. @Override
  498. public void run() {
  499. touchX += intervalsX;
  500. touchY += intervalsY;
  501.  
  502. reportOnMoved();
  503. invalidate();
  504.  
  505. if (moveListener != null && j == numberOfFrames - 1) {
  506. moveListener.OnReturnedToCenter();
  507. }
  508. }
  509. }, i * 40);
  510. }
  511.  
  512. if (moveListener != null) {
  513. moveListener.OnReleased();
  514. }
  515. }
  516. }
  517.  
  518. public void setTouchOffset(int x, int y) {
  519. offsetX = x;
  520. offsetY = y;
  521. }
  522. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement