Gauge.java 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. // Copyright (c) 2010, Freddy Martens (http://atstechlab.wordpress.com),
  2. // MindTheRobot (http://mindtherobot.com/blog/) and contributors
  3. // All rights reserved.
  4. //
  5. // Redistribution and use in source and binary forms, with or without modification,
  6. // are permitted provided that the following conditions are met:
  7. //
  8. // * Redistributions of source code must retain the above copyright notice,
  9. // this list of conditions and the following disclaimer.
  10. // * Redistributions in binary form must reproduce the above copyright notice,
  11. // this list of conditions and the following disclaimer in the documentation
  12. // and/or other materials provided with the distribution.
  13. // * Neither the name of Ondrej Zara nor the names of its contributors may be used
  14. // to endorse or promote products derived from this software without specific
  15. // prior written permission.
  16. //
  17. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  18. // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19. // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  20. // IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  21. // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  22. // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  23. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  24. // OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  25. // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  26. // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. package nl.digitalthings.mebarista;
  28. import nl.digitalthings.mebarista.R;
  29. import android.content.Context;
  30. import android.graphics.Bitmap;
  31. import android.graphics.BitmapFactory;
  32. import android.graphics.BitmapShader;
  33. import android.graphics.Canvas;
  34. import android.graphics.Color;
  35. import android.graphics.LinearGradient;
  36. import android.graphics.Matrix;
  37. import android.graphics.Paint;
  38. import android.graphics.Path;
  39. import android.graphics.RadialGradient;
  40. import android.graphics.RectF;
  41. import android.graphics.Shader;
  42. import android.graphics.Typeface;
  43. import android.os.Bundle;
  44. import android.os.Parcelable;
  45. import android.util.AttributeSet;
  46. import android.util.Log;
  47. import android.view.View;
  48. import android.content.res.TypedArray;
  49. public final class Gauge extends View {
  50. private static final String TAG = Gauge.class.getSimpleName();
  51. // drawing tools
  52. private RectF rimRect;
  53. private Paint rimPaint;
  54. private Paint rimCirclePaint;
  55. private RectF faceRect;
  56. private Bitmap faceTexture;
  57. private Paint facePaint;
  58. private Paint rimShadowPaint;
  59. private Paint scalePaint;
  60. private RectF scaleRect;
  61. private RectF valueRect;
  62. private RectF rangeRect;
  63. private Paint rangeOkPaint;
  64. private Paint rangeWarningPaint;
  65. private Paint rangeErrorPaint;
  66. private Paint rangeAllPaint;
  67. private Paint valueOkPaint;
  68. private Paint valueWarningPaint;
  69. private Paint valueErrorPaint;
  70. private Paint valueAllPaint;
  71. private Paint unitPaint;
  72. private Path unitPath;
  73. private RectF unitRect;
  74. private Paint lowerTitlePaint;
  75. private Paint upperTitlePaint;
  76. private Path lowerTitlePath;
  77. private Path upperTitlePath;
  78. private RectF titleRect;
  79. private Paint handPaint;
  80. private Path handPath;
  81. private Paint handScrewPaint;
  82. private Paint backgroundPaint;
  83. // end drawing tools
  84. private Bitmap background; // holds the cached static part
  85. // scale configuration
  86. // Values passed as property. Defaults are set here.
  87. private boolean showHand = true;
  88. private boolean showGauge = false;
  89. private boolean showRange = false;
  90. private int totalNotches = 120; // Total number of notches on the scale.
  91. private int incrementPerLargeNotch = 10;
  92. private int incrementPerSmallNotch = 2;
  93. private int scaleColor = 0x9f004d0f;
  94. private int scaleCenterValue = 0; // the one in the top center (12 o'clock), this corresponds with -90 degrees
  95. private int scaleMinValue = -90;
  96. private int scaleMaxValue = 120;
  97. private int angleMinValue = -120;
  98. private int angleMaxValue = 120;
  99. private float degreeMinValue = 0;
  100. private float degreeMaxValue = 0;
  101. private int rangeOkColor = 0x9f00ff00;
  102. private int rangeOkMinValue = scaleMinValue;
  103. private int rangeOkMaxValue = 45;
  104. private float degreeOkMinValue = 0;
  105. private float degreeOkMaxValue = 0;
  106. private int rangeWarningColor = 0x9fff8800;
  107. private int rangeWarningMinValue = rangeOkMaxValue;
  108. private int rangeWarningMaxValue = 80;
  109. private float degreeWarningMinValue = 0;
  110. private float degreeWarningMaxValue = 0;
  111. private int rangeErrorColor = 0x9fff0000;
  112. private int rangeErrorMinValue = rangeWarningMaxValue;
  113. private int rangeErrorMaxValue = 120;
  114. private float degreeErrorMinValue = 0;
  115. private float degreeErrorMaxValue = 0;
  116. private String lowerTitle = "www.ats-global.com";
  117. private String upperTitle = "Visit http://atstechlab.wordpress.com";
  118. private String unitTitle = "\u2103";
  119. // Fixed values.
  120. private static float scalePosition = 0.10f; // The distance from the rim to the scale
  121. private static float valuePosition = 0.285f; // The distance from the rim to the ranges
  122. private static float rangePosition = 0.122f; // The distance from the rim to the ranges
  123. private static float titlePosition = 0.145f; // The Distance from the rim to the titles
  124. private static float unitPosition = 0.300f; // The distance from the rim to the unit
  125. private static float rimSize = 0.02f;
  126. private float degreesPerNotch = 360.0f/totalNotches;
  127. private static final int centerDegrees = -90; // the one in the top center (12 o'clock), this corresponds with -90 degrees
  128. // hand dynamics
  129. private boolean dialInitialized = false;
  130. private float currentValue = scaleCenterValue;
  131. private float targetValue = scaleCenterValue;
  132. private float dialVelocity = 0.0f;
  133. private float dialAcceleration = 0.0f;
  134. private long lastDialMoveTime = -1L;
  135. public Gauge(Context context) {
  136. super(context);
  137. init(context, null);
  138. }
  139. public Gauge(Context context, AttributeSet attrs) {
  140. super(context, attrs);
  141. init(context, attrs);
  142. }
  143. public Gauge(Context context, AttributeSet attrs, int defStyle) {
  144. super(context, attrs, defStyle);
  145. init(context, attrs);
  146. }
  147. @Override
  148. protected void onAttachedToWindow() {
  149. super.onAttachedToWindow();
  150. }
  151. @Override
  152. protected void onDetachedFromWindow() {
  153. super.onDetachedFromWindow();
  154. }
  155. @Override
  156. protected void onRestoreInstanceState(Parcelable state) {
  157. Bundle bundle = (Bundle) state;
  158. Parcelable superState = bundle.getParcelable("superState");
  159. super.onRestoreInstanceState(superState);
  160. dialInitialized = bundle.getBoolean("dialInitialized");
  161. currentValue = bundle.getFloat("currentValue");
  162. targetValue = bundle.getFloat("targetValue");
  163. dialVelocity = bundle.getFloat("dialVelocity");
  164. dialAcceleration = bundle.getFloat("dialAcceleration");
  165. lastDialMoveTime = bundle.getLong("lastDialMoveTime");
  166. }
  167. @Override
  168. protected Parcelable onSaveInstanceState() {
  169. Parcelable superState = super.onSaveInstanceState();
  170. Bundle state = new Bundle();
  171. state.putParcelable("superState", superState);
  172. state.putBoolean("dialInitialized", dialInitialized);
  173. state.putFloat("currentValue", currentValue);
  174. state.putFloat("targetValue", targetValue);
  175. state.putFloat("dialVelocity", dialVelocity);
  176. state.putFloat("dialAcceleration", dialAcceleration);
  177. state.putLong("lastDialMoveTime", lastDialMoveTime);
  178. return state;
  179. }
  180. private void init(Context context, AttributeSet attrs) {
  181. // Get the properties from the resource file.
  182. // setLayerType(View.LAYER_TYPE_SOFTWARE, null);
  183. if (context != null && attrs != null){
  184. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Dial);
  185. showRange = a.getBoolean(R.styleable.Dial_showRange, showRange);
  186. showGauge = a.getBoolean(R.styleable.Dial_showGauge, showGauge);
  187. //showHand = a.getBoolean(R.styleable.Dial_showHand, showHand);
  188. totalNotches = a.getInt(R.styleable.Dial_totalNotches, totalNotches);
  189. incrementPerLargeNotch = a.getInt(R.styleable.Dial_incrementPerLargeNotch, incrementPerLargeNotch);
  190. incrementPerSmallNotch = a.getInt(R.styleable.Dial_incrementPerSmallNotch, incrementPerSmallNotch);
  191. scaleCenterValue = a.getInt(R.styleable.Dial_scaleCenterValue, scaleCenterValue);
  192. scaleColor = a.getInt(R.styleable.Dial_scaleColor, scaleColor);
  193. scaleMinValue = a.getInt(R.styleable.Dial_scaleMinValue, scaleMinValue);
  194. scaleMaxValue = a.getInt(R.styleable.Dial_scaleMaxValue, scaleMaxValue);
  195. angleMinValue = a.getInt(R.styleable.Dial_angleMinValue, angleMinValue);
  196. angleMaxValue = a.getInt(R.styleable.Dial_angleMaxValue, angleMaxValue);
  197. rangeOkColor = a.getInt(R.styleable.Dial_rangeOkColor, rangeOkColor);
  198. rangeOkMinValue = a.getInt(R.styleable.Dial_rangeOkMinValue, rangeOkMinValue);
  199. rangeOkMaxValue = a.getInt(R.styleable.Dial_rangeOkMaxValue, rangeOkMaxValue);
  200. rangeWarningColor = a.getInt(R.styleable.Dial_rangeWarningColor, rangeWarningColor);
  201. rangeWarningMinValue = a.getInt(R.styleable.Dial_rangeWarningMinValue, rangeWarningMinValue);
  202. rangeWarningMaxValue = a.getInt(R.styleable.Dial_rangeWarningMaxValue, rangeWarningMaxValue);
  203. rangeErrorColor = a.getInt(R.styleable.Dial_rangeErrorColor, rangeErrorColor);
  204. rangeErrorMinValue = a.getInt(R.styleable.Dial_rangeErrorMinValue, rangeErrorMinValue);
  205. rangeErrorMaxValue = a.getInt(R.styleable.Dial_rangeErrorMaxValue, rangeErrorMaxValue);
  206. String unitTitle = a.getString(R.styleable.Dial_unitTitle);
  207. String lowerTitle = a.getString(R.styleable.Dial_lowerTitle);
  208. String upperTitle = a.getString(R.styleable.Dial_upperTitle);
  209. if (unitTitle != null) this.unitTitle = unitTitle;
  210. if (lowerTitle != null) this.lowerTitle = lowerTitle;
  211. if (upperTitle != null) this.upperTitle = upperTitle;
  212. }
  213. degreeMinValue = valueToAngle(scaleMinValue) + centerDegrees;
  214. degreeMaxValue = valueToAngle(scaleMaxValue) + centerDegrees;
  215. degreeOkMinValue = valueToAngle(rangeOkMinValue) + centerDegrees;
  216. degreeOkMaxValue = valueToAngle(rangeOkMaxValue) + centerDegrees;
  217. degreeWarningMinValue = valueToAngle(rangeWarningMinValue) + centerDegrees;
  218. degreeWarningMaxValue = valueToAngle(rangeWarningMaxValue) + centerDegrees;
  219. degreeErrorMinValue = valueToAngle(rangeErrorMinValue) + centerDegrees;
  220. degreeErrorMaxValue = valueToAngle(rangeErrorMaxValue) + centerDegrees;
  221. degreesPerNotch = ( angleMaxValue - angleMinValue )*1.0f / totalNotches;
  222. // initDrawingTools();
  223. }
  224. private void initDrawingTools() {
  225. // All drawing has been scaled to actual pixels because of :
  226. // https://groups.google.com/forum/#!msg/android-developers/eTxV4KPy1G4/tAe2zUPCjMcJ
  227. int w = getWidth(), h = getHeight();
  228. rimRect = new RectF(0.0f, 0.0f, getWidth() /* 1.0f */, getHeight() /* 1.0f */);
  229. scalePosition = 0.10f * getWidth(); // The distance from the rim to the scale
  230. valuePosition = 0.285f * getWidth(); // The distance from the rim to the ranges
  231. rangePosition = 0.122f * getWidth(); // The distance from the rim to the ranges
  232. titlePosition = 0.145f * getWidth(); // The Distance from the rim to the titles
  233. unitPosition = 0.300f * getWidth(); // The distance from the rim to the unit
  234. rimSize = 0.02f * getWidth();
  235. faceRect = new RectF();
  236. faceRect.set(rimRect.left + rimSize, rimRect.top + rimSize,
  237. rimRect.right - rimSize, rimRect.bottom - rimSize);
  238. scaleRect = new RectF();
  239. scaleRect.set(faceRect.left + scalePosition, faceRect.top + scalePosition,
  240. faceRect.right - scalePosition, faceRect.bottom - scalePosition);
  241. rangeRect = new RectF();
  242. rangeRect.set(faceRect.left + rangePosition, faceRect.top + rangePosition,
  243. faceRect.right - rangePosition, faceRect.bottom - rangePosition);
  244. valueRect = new RectF();
  245. valueRect.set(faceRect.left + valuePosition, faceRect.top + valuePosition,
  246. faceRect.right - valuePosition, faceRect.bottom - valuePosition);
  247. titleRect = new RectF();
  248. titleRect.set(faceRect.left + titlePosition, faceRect.top + titlePosition,
  249. faceRect.right - titlePosition, faceRect.bottom - titlePosition);
  250. unitRect = new RectF();
  251. unitRect.set(faceRect.left + unitPosition, faceRect.top + unitPosition,
  252. faceRect.right - unitPosition, faceRect.bottom - unitPosition);
  253. faceTexture = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.plastic);
  254. BitmapShader paperShader = new BitmapShader(faceTexture,
  255. Shader.TileMode.MIRROR,
  256. Shader.TileMode.MIRROR);
  257. Matrix paperMatrix = new Matrix();
  258. paperMatrix.setScale(1.0f / faceTexture.getWidth(),
  259. 1.0f / faceTexture.getHeight());
  260. paperShader.setLocalMatrix(paperMatrix);
  261. rimShadowPaint = new Paint();
  262. rimShadowPaint.setShader(new RadialGradient(0.5f * getWidth(), 0.5f * getHeight(), faceRect.width() / 2.0f,
  263. new int[]{0x00000000, 0x00000500, 0x50000500},
  264. new float[]{0.96f, 0.96f, 0.99f},
  265. Shader.TileMode.MIRROR));
  266. rimShadowPaint.setStyle(Paint.Style.FILL);
  267. // the linear gradient is a bit skewed for realism
  268. rimPaint = new Paint();
  269. rimPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
  270. rimPaint.setShader(new LinearGradient(0.40f * getWidth(), 0.0f, 0.60f * getWidth(), 1.0f * getHeight(),
  271. Color.rgb(0xf0, 0xf5, 0xf0),
  272. Color.rgb(0x30, 0x31, 0x30),
  273. Shader.TileMode.CLAMP));
  274. rimCirclePaint = new Paint();
  275. //rimCirclePaint.setAntiAlias(true);
  276. rimCirclePaint.setStyle(Paint.Style.STROKE);
  277. rimCirclePaint.setColor(Color.argb(0x4f, 0x33, 0x36, 0x33));
  278. rimCirclePaint.setStrokeWidth(0.005f * getWidth() );
  279. facePaint = new Paint();
  280. facePaint.setFilterBitmap(true);
  281. facePaint.setStyle(Paint.Style.FILL);
  282. facePaint.setShader(paperShader);
  283. scalePaint = new Paint();
  284. scalePaint.setStyle(Paint.Style.STROKE);
  285. scalePaint.setColor(scaleColor);
  286. scalePaint.setStrokeWidth(0.005f * getWidth() );
  287. scalePaint.setAntiAlias(true);
  288. //
  289. // scalePaint.setTextSize(0.045f);
  290. // http://stackoverflow.com/questions/13974129/android-4-2-1-wrong-character-kerning-spacing
  291. scalePaint.setTextSize( 0.045f * getWidth() );
  292. scalePaint.setLinearText(true);
  293. scalePaint.setTypeface(Typeface.SANS_SERIF);
  294. scalePaint.setTextScaleX(0.8f);
  295. scalePaint.setTextAlign(Paint.Align.CENTER);
  296. rangeOkPaint = new Paint();
  297. rangeOkPaint.setStyle(Paint.Style.STROKE);
  298. rangeOkPaint.setColor(rangeOkColor);
  299. rangeOkPaint.setStrokeWidth(0.012f * getWidth() );
  300. rangeOkPaint.setAntiAlias(true);
  301. rangeWarningPaint = new Paint();
  302. rangeWarningPaint.setStyle(Paint.Style.STROKE);
  303. rangeWarningPaint.setColor(rangeWarningColor);
  304. rangeWarningPaint.setStrokeWidth(0.012f * getWidth() );
  305. rangeWarningPaint.setAntiAlias(true);
  306. rangeErrorPaint = new Paint();
  307. rangeErrorPaint.setStyle(Paint.Style.STROKE);
  308. rangeErrorPaint.setColor(rangeErrorColor);
  309. rangeErrorPaint.setStrokeWidth(0.012f * getWidth() );
  310. rangeErrorPaint.setAntiAlias(true);
  311. rangeAllPaint = new Paint();
  312. rangeAllPaint.setStyle(Paint.Style.STROKE);
  313. rangeAllPaint.setColor(0xcff8f8f8);
  314. rangeAllPaint.setStrokeWidth(0.012f * getWidth() );
  315. rangeAllPaint.setAntiAlias(true);
  316. rangeAllPaint.setShadowLayer(0.005f, -0.002f, -0.002f, 0x7f000000);
  317. valueOkPaint = new Paint();
  318. valueOkPaint.setStyle(Paint.Style.STROKE);
  319. valueOkPaint.setColor(rangeOkColor);
  320. valueOkPaint.setStrokeWidth(0.20f * getWidth() );
  321. valueOkPaint.setAntiAlias(true);
  322. valueWarningPaint = new Paint();
  323. valueWarningPaint.setStyle(Paint.Style.STROKE);
  324. valueWarningPaint.setColor(rangeWarningColor);
  325. valueWarningPaint.setStrokeWidth(0.20f * getWidth() );
  326. valueWarningPaint.setAntiAlias(true);
  327. valueErrorPaint = new Paint();
  328. valueErrorPaint.setStyle(Paint.Style.STROKE);
  329. valueErrorPaint.setColor(rangeErrorColor);
  330. valueErrorPaint.setStrokeWidth(0.20f * getWidth() );
  331. valueErrorPaint.setAntiAlias(true);
  332. valueAllPaint = new Paint();
  333. valueAllPaint.setStyle(Paint.Style.STROKE);
  334. valueAllPaint.setColor(0xcff8f8f8);
  335. valueAllPaint.setStrokeWidth(0.20f * getWidth() );
  336. valueAllPaint.setAntiAlias(true);
  337. valueAllPaint.setShadowLayer(0.005f, -0.002f, -0.002f, 0x7f000000);
  338. unitPaint = new Paint();
  339. unitPaint.setColor(0xaf0c0c0c);
  340. unitPaint.setAntiAlias(true);
  341. unitPaint.setTypeface(Typeface.DEFAULT_BOLD);
  342. unitPaint.setTextAlign(Paint.Align.CENTER);
  343. unitPaint.setTextSize(0.05f * getWidth() );
  344. unitPaint.setTextScaleX(0.8f);
  345. upperTitlePaint = new Paint();
  346. upperTitlePaint.setColor(0xaf0c0c0c);
  347. upperTitlePaint.setAntiAlias(true);
  348. upperTitlePaint.setTypeface(Typeface.DEFAULT_BOLD);
  349. upperTitlePaint.setTextAlign(Paint.Align.CENTER);
  350. upperTitlePaint.setTextSize(0.09f * getWidth() );
  351. upperTitlePaint.setTextScaleX(0.8f);
  352. lowerTitlePaint = new Paint();
  353. lowerTitlePaint.setColor(0xaf0c0c0c);
  354. lowerTitlePaint.setAntiAlias(true);
  355. lowerTitlePaint.setTypeface(Typeface.DEFAULT_BOLD);
  356. lowerTitlePaint.setTextAlign(Paint.Align.CENTER);
  357. lowerTitlePaint.setTextSize(0.04f * getWidth() );
  358. lowerTitlePaint.setTextScaleX(0.8f);
  359. handPaint = new Paint();
  360. handPaint.setAntiAlias(true);
  361. handPaint.setColor(0xff392f2c);
  362. //handPaint.setShadowLayer(0.01f, -0.005f, -0.005f, 0x7f000000);
  363. handPaint.setStyle(Paint.Style.FILL);
  364. handPaint.setStrokeWidth(0.1f);
  365. //handPaint.setColor(android.graphics.Color.RED);
  366. //handPaint.setStyle(Paint.Style.STROKE);
  367. handScrewPaint = new Paint();
  368. handScrewPaint.setAntiAlias(true);
  369. //handScrewPaint.setColor(0xff493f3c);
  370. handScrewPaint.setColor(0xffff3f3c);
  371. handScrewPaint.setStyle(Paint.Style.FILL);
  372. backgroundPaint = new Paint();
  373. backgroundPaint.setFilterBitmap(true);
  374. unitPath = new Path();
  375. unitPath.addArc(unitRect, 180.0f, 180.0f);
  376. upperTitlePath = new Path();
  377. upperTitlePath.addArc(titleRect, 180.0f, 180.0f);
  378. lowerTitlePath = new Path();
  379. lowerTitlePath.addArc(titleRect, -180.0f, -180.0f);
  380. // The hand is drawn with the tip facing up. That means when the image is not rotated, the tip
  381. // faces north. When the the image is rotated -90 degrees, the tip is facing west and so on.
  382. handPath = new Path();
  383. // handPath.setFillType(Path.FillType.EVEN_ODD);// X Y
  384. handPath.moveTo(0.5f * w, (0.5f + 0.2f) * h); // 0.500, 0.700
  385. handPath.lineTo( ( 0.5f - 0.010f ) * w, ( 0.5f + 0.2f - 0.007f ) * h); // 0.490, 0.630
  386. handPath.lineTo( ( 0.5f - 0.002f ) * w, ( 0.5f - 0.40f ) * h); // 0.498, 0.100
  387. handPath.lineTo( ( 0.5f + 0.002f ) * w, ( 0.5f - 0.40f ) * h); // 0.502, 0.100
  388. handPath.lineTo((0.5f + 0.010f) * w, (0.5f + 0.2f - 0.007f) * h); // 0.510, 0.630
  389. //handPath.lineTo(0.5f * w, 0.5f + 0.2f * h); // 0.500, 0.700
  390. handPath.addCircle(0.5f * w, 0.5f * h, 0.025f * w, Path.Direction.CW);
  391. handPath.close();
  392. }
  393. @Override
  394. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  395. //Log.d(TAG, "Width spec: " + MeasureSpec.toString(widthMeasureSpec));
  396. //Log.d(TAG, "Height spec: " + MeasureSpec.toString(heightMeasureSpec));
  397. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  398. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  399. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  400. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  401. int chosenWidth = chooseDimension(widthMode, widthSize);
  402. int chosenHeight = chooseDimension(heightMode, heightSize);
  403. int chosenDimension = Math.min(chosenWidth, chosenHeight);
  404. setMeasuredDimension(chosenDimension, chosenDimension);
  405. }
  406. private int chooseDimension(int mode, int size) {
  407. if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
  408. return size;
  409. } else { // (mode == MeasureSpec.UNSPECIFIED)
  410. return getPreferredSize();
  411. }
  412. }
  413. // in case there is no size specified
  414. private int getPreferredSize() {
  415. return 250;
  416. }
  417. private void drawRim(Canvas canvas) {
  418. // first, draw the metallic body
  419. canvas.drawOval(rimRect, rimPaint);
  420. // now the outer rim circle
  421. canvas.drawOval(rimRect, rimCirclePaint);
  422. }
  423. private void drawFace(Canvas canvas) {
  424. canvas.drawOval(faceRect, facePaint);
  425. // draw the inner rim circle
  426. // was: canvas.drawOval(faceRect, rimCirclePaint);
  427. canvas.drawOval(faceRect, rimCirclePaint);
  428. // draw the rim shadow inside the face
  429. canvas.drawOval(faceRect, rimShadowPaint); // boosdoener
  430. }
  431. private void drawBackground(Canvas canvas) {
  432. if (background == null) {
  433. Log.w(TAG, "Background not created");
  434. } else {
  435. canvas.drawBitmap(background, 0, 0, backgroundPaint);
  436. }
  437. }
  438. private void fa( Canvas canvas, String text, Paint p, float x, float y ) {
  439. Path mTextOutlinePath = new Path();
  440. // adjust the x,y text origin to your personal needs
  441. p.getTextPath(text, 0, text.length(), x /* getPaddingLeft() */, y /* getBaseline() */, mTextOutlinePath);
  442. canvas.drawPath(mTextOutlinePath, p);
  443. // canvas.drawPath(mTextOutlinePath, mStrokePaint);
  444. }
  445. private void drawScale(Canvas canvas) {
  446. // Draw the circle
  447. canvas.drawOval(scaleRect, scalePaint);
  448. for (int i = 0; i <= totalNotches; ++i) {
  449. canvas.save(Canvas.MATRIX_SAVE_FLAG);
  450. canvas.rotate(degreeOkMinValue + 90 + degreesPerNotch * i, 0.5f * getWidth(), 0.5f * getHeight());
  451. float y1 = scaleRect.top;
  452. float y2 = y1 - 0.015f * getHeight();
  453. float y3 = y1 - 0.025f * getHeight();
  454. int value = notchToValue(i);
  455. if (i % (incrementPerLargeNotch/incrementPerSmallNotch) == 0) {
  456. /* if (value >= scaleMinValue && value <= scaleMaxValue) { */
  457. // draw a nick
  458. canvas.drawLine(0.5f * getWidth(), y1, 0.5f * getWidth(), y3, scalePaint);
  459. String valueString = Integer.toString(value);
  460. canvas.drawText(valueString, 0.5f * getWidth(), y3 - 0.015f * getWidth(), scalePaint);
  461. /* } */
  462. }
  463. else{
  464. /* if (value >= scaleMinValue && value <= scaleMaxValue) { */
  465. // draw a nick
  466. canvas.drawLine(0.5f * getWidth(), y1, 0.5f * getWidth(), y2, scalePaint);
  467. /* } */
  468. }
  469. canvas.restore();
  470. }
  471. }
  472. private void drawScaleRanges(Canvas canvas) {
  473. canvas.drawArc(rangeRect, degreeMinValue, degreeMaxValue - degreeMinValue, false, rangeAllPaint);
  474. canvas.drawArc(rangeRect, degreeOkMinValue, degreeOkMaxValue - degreeOkMinValue, false, rangeOkPaint);
  475. canvas.drawArc(rangeRect, degreeWarningMinValue, degreeWarningMaxValue - degreeWarningMinValue, false, rangeWarningPaint);
  476. canvas.drawArc(rangeRect, degreeErrorMinValue, degreeErrorMaxValue - degreeErrorMinValue, false, rangeErrorPaint);
  477. }
  478. private void drawTitle(Canvas canvas) {
  479. // Use a vertical offset when printing the upper title. The upper and lower title
  480. // use the same rectangular but the spacing between the title and the ranges
  481. // is not equal for the upper and lower title and therefore, the upper title is
  482. // moved downwards.
  483. canvas.drawTextOnPath(upperTitle, upperTitlePath, 0.0f, 0.02f * getHeight(), upperTitlePaint);
  484. canvas.drawTextOnPath(lowerTitle, lowerTitlePath, 0.0f, 0.0f, lowerTitlePaint);
  485. canvas.drawTextOnPath(unitTitle, unitPath, 0.0f, 0.0f, unitPaint);
  486. }
  487. int centerx, centery;
  488. private void drawHand(Canvas canvas) {
  489. if (true || dialInitialized) {
  490. float angle = valueToAngle(currentValue );
  491. canvas.save(Canvas.MATRIX_SAVE_FLAG);
  492. canvas.rotate(angle, centerx, centery);
  493. canvas.drawPath(handPath, handPaint);
  494. canvas.restore();
  495. canvas.drawCircle(0.5f * getWidth() , 0.5f * getHeight() , 0.01f * getWidth(), handScrewPaint);
  496. }
  497. }
  498. private void drawGauge(Canvas canvas) {
  499. if (dialInitialized) {
  500. // When currentValue is not rotated, the tip of the hand points
  501. // to n -90 degrees.
  502. float angle = valueToAngle(currentValue) - 90;
  503. if(targetValue <= rangeOkMaxValue){
  504. canvas.drawArc(valueRect, degreeMinValue, angle - degreeMinValue, false, valueOkPaint);
  505. }
  506. if((targetValue > rangeOkMaxValue) && (targetValue <= rangeWarningMaxValue)){
  507. canvas.drawArc(valueRect, degreeMinValue, degreeOkMaxValue - degreeMinValue, false, valueOkPaint);
  508. canvas.drawArc(valueRect, degreeWarningMinValue, angle - degreeWarningMinValue, false, valueWarningPaint);
  509. }
  510. if((targetValue > rangeWarningMaxValue) && (targetValue <= rangeErrorMaxValue)){
  511. canvas.drawArc(valueRect, degreeMinValue, degreeOkMaxValue - degreeMinValue, false, valueOkPaint);
  512. canvas.drawArc(valueRect, degreeWarningMinValue, degreeWarningMaxValue - degreeWarningMinValue, false, valueWarningPaint);
  513. canvas.drawArc(valueRect, degreeErrorMinValue, angle - degreeErrorMinValue, false, valueErrorPaint);
  514. }
  515. }
  516. }
  517. private void drawBezel(Canvas canvas) {
  518. // Draw the bevel in which the value is draw.
  519. canvas.save(Canvas.MATRIX_SAVE_FLAG);
  520. canvas.drawArc(valueRect, degreeMinValue, degreeMaxValue - degreeMinValue, false, valueAllPaint);
  521. canvas.restore();
  522. }
  523. /* Translate a notch to a value for the scale.
  524. * The notches are evenly spread across the scale, half of the notches on the left hand side
  525. * and the other half on the right hand side.
  526. * The raw value calculation uses a constant so that each notch represents a value n + 2.
  527. */
  528. private int notchToValue(int notch) {
  529. //int rawValue = ((notch < totalNotches / 2) ? notch : (notch - totalNotches)) * incrementPerSmallNotch;
  530. //int shiftedValue = rawValue + scaleCenterValue;
  531. //return shiftedValue;
  532. return notch * ( scaleMaxValue - scaleMinValue ) / totalNotches;
  533. }
  534. private float valueToAngle(float value) {
  535. // scaleCenterValue represents an angle of -90 degrees.
  536. //return (value - scaleCenterValue) / 2.0f * degreesPerNotch;
  537. // jaja
  538. float frac = ( value - scaleMinValue ) / ( scaleMaxValue - scaleMinValue );
  539. return angleMinValue + frac * ( angleMaxValue - angleMinValue );
  540. //return degreeOkMinValue - ( value / scaleMaxValue ) * ( degreeOkMinValue + 90 );
  541. }
  542. private float valueToAngle2(float value) {
  543. // scaleCenterValue represents an angle of -90 degrees.
  544. // return (value - scaleCenterValue) / 2.0f * degreesPerNotch;
  545. return valueToAngle(value);
  546. //return degreeOkMinValue - ( value / scaleMaxValue ) * 2 * ( degreeOkMinValue + 90 );
  547. }
  548. // Canvas backgroundCanvas ;
  549. @Override
  550. protected void onDraw(Canvas canvas) {
  551. drawBackground(canvas);
  552. // Draw the needle using the updated value
  553. if (showHand){
  554. drawHand( canvas );
  555. }
  556. // Calculate a new current value.
  557. calculateCurrentValue();
  558. }
  559. @Override
  560. protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  561. // Log.d(TAG, "Size changed to " + w + "x" + h);
  562. initDrawingTools();
  563. regenerateBackground();
  564. centerx = w / 2;
  565. centery = h / 2;
  566. }
  567. private void regenerateBackground() {
  568. // free the old bitmap
  569. if (background != null) {
  570. background.recycle();
  571. }
  572. background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
  573. Canvas backgroundCanvas = new Canvas(background);
  574. drawRim(backgroundCanvas);
  575. drawFace(backgroundCanvas);
  576. drawScale(backgroundCanvas);
  577. if (showRange){
  578. drawScaleRanges(backgroundCanvas);
  579. }
  580. if (showGauge) {
  581. drawBezel(backgroundCanvas);
  582. }
  583. drawTitle(backgroundCanvas);
  584. }
  585. // Move the hand slowly to the new position.
  586. private void calculateCurrentValue() {
  587. if (!(Math.abs(currentValue - targetValue) > 0.01f)) {
  588. return;
  589. }
  590. if (lastDialMoveTime != -1L) {
  591. long currentTime = System.currentTimeMillis();
  592. float delta = (currentTime - lastDialMoveTime) / 1000.0f;
  593. float direction = Math.signum(dialVelocity);
  594. if (Math.abs(dialVelocity) < 90.0f) {
  595. dialAcceleration = 5.0f * (targetValue - currentValue);
  596. } else {
  597. dialAcceleration = 0.0f;
  598. }
  599. currentValue += dialVelocity * delta;
  600. dialVelocity += dialAcceleration * delta;
  601. if ((targetValue - currentValue) * direction < 0.01f * direction) {
  602. currentValue = targetValue;
  603. dialVelocity = 0.0f;
  604. dialAcceleration = 0.0f;
  605. lastDialMoveTime = -1L;
  606. } else {
  607. lastDialMoveTime = System.currentTimeMillis();
  608. }
  609. invalidate();
  610. } else {
  611. lastDialMoveTime = System.currentTimeMillis();
  612. calculateCurrentValue();
  613. }
  614. }
  615. public void setValue(float value) {
  616. if (value < scaleMinValue) value = scaleMinValue;
  617. else if (value > scaleMaxValue) value = scaleMaxValue;
  618. targetValue = value;
  619. dialInitialized = true;
  620. invalidate(); // forces onDraw() to be called.
  621. }
  622. public float getValue() {
  623. return targetValue;
  624. }
  625. }