Android Open Source - HarleyDroid Gauge






From Project

Back to project page HarleyDroid.

License

The source code is released under:

GNU General Public License

If you think the Android project HarleyDroid listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

// Copyright (c) 2010, Freddy Martens (http://atstechlab.wordpress.com),
// MindTheRobot (http://mindtherobot.com/blog/)  and contributors
// All rights reserved.
//// w  ww  .j  a v a  2 s.  c om
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//  * Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//  * Neither the name of Ondrej Zara nor the names of its contributors may be used
//    to endorse or promote products derived from this software without specific
//    prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package org.harleydroid;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.harleydroid.R;

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.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.content.res.TypedArray;

public final class Gauge extends View {

  private static final boolean D = false;
  private static final String TAG = Gauge.class.getSimpleName();

  // drawing tools
  private RectF rimRect;
  private Paint rimPaint;
  private Paint rimCirclePaint;

  private RectF faceRect;
  private Bitmap faceTexture;
  private Paint facePaint;
  private Paint rimShadowPaint;

  private Paint scalePaint;
  private RectF scaleRect;

  private RectF valueRect;
  private RectF rangeRect;

  private RectF odoRect;

  private Paint rangeOkPaint;
  private Paint rangeWarningPaint;
  private Paint rangeErrorPaint;
  private Paint rangeAllPaint;

  private Paint valueOkPaint;
  private Paint valueWarningPaint;
  private Paint valueErrorPaint;
  private Paint valueAllPaint;

  private Paint unitPaint;
  private Path  unitPath;
  private RectF unitRect;

  private Paint lowerTitlePaint;
  private Paint upperTitlePaint;
  private Path  lowerTitlePath;
  private Path  upperTitlePath;
  private RectF titleRect;

  private Paint handPaint;
  private Path handPath;

  private Paint handScrewPaint;

  private Paint backgroundPaint;

  private Paint odoPaint;
  private Path odoPath;
  private Paint odoBackgroundPaint;

  // end drawing tools

  private Bitmap background; // holds the cached static part

  // scale configuration
  // Values passed as property. Defaults are set here.
  private boolean showHand                 = false;
  private boolean showGauge                = false;
  private boolean showRange                = false;
  private boolean showOdo                  = false;

  private int totalNotches                 = 120; // Total number of notches on the scale.
  private int incrementPerLargeNotch       = 10;
  private int incrementPerSmallNotch       = 2;

  private int scaleColor                   = 0x9f004d0f;
  private int scaleCenterValue             = 0; // the one in the top center (12 o'clock), this corresponds with -90 degrees
  private int scaleMinValue                = -90;
  private int scaleMaxValue                = 120;
  private float degreeMinValue             = 0;
  private float degreeMaxValue             = 0;

  private int rangeOkColor                 = 0x9f00ff00;
  private int rangeOkMinValue              = scaleMinValue;
  private int rangeOkMaxValue              = 45;
  private float degreeOkMinValue           = 0;
  private float degreeOkMaxValue           = 0;

  private int rangeWarningColor            = 0x9fff8800;
  private int rangeWarningMinValue         = rangeOkMaxValue;
  private int rangeWarningMaxValue         = 80;
  private float degreeWarningMinValue      = 0;
  private float degreeWarningMaxValue      = 0;

  private int rangeErrorColor              = 0x9fff0000;
  private int rangeErrorMinValue           = rangeWarningMaxValue;
  private int rangeErrorMaxValue           = 120;
  private float degreeErrorMinValue        = 0;
  private float degreeErrorMaxValue        = 0;

  private int odoColor                     = 0xff00ff00;
  private int odoBackgroundColor           = 0xff000000;

  private String lowerTitle                = "www.ats-global.com";
  private String upperTitle                = "Visit http://atstechlab.wordpress.com";
  private String unitTitle                 = "\u2103";

  // Fixed values.
  private static final float scalePosition = 0.10f;  // The distance from the rim to the scale
  private static final float valuePosition = 0.285f; // The distance from the rim to the ranges
  private static final float rangePosition = 0.122f; // The distance from the rim to the ranges
  private static final float titlePosition = 0.145f; // The Distance from the rim to the titles
  private static final float unitPosition  = 0.300f; // The distance from the rim to the unit
  private static final float rimSize       = 0.02f;

  private float degreesPerNotch            = 360.0f/totalNotches;
  private static final int centerDegrees   =  -90; // the one in the top center (12 o'clock), this corresponds with -90 degrees

  // hand dynamics
  private boolean dialInitialized         = false;
  private float currentValue              = scaleCenterValue;
  private float targetValue               = scaleCenterValue;
  private float targetOdoValue            = 0.0f;
  private float dialVelocity              = 0.0f;
  private float dialAcceleration          = 0.0f;
  private long lastDialMoveTime           = -1L;


  public Gauge(Context context) {
    super(context);
    init(context, null);
  }

  public Gauge(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs);
  }

  public Gauge(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(context, attrs);
  }

  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();
  }

  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
  }

  @Override
  protected void onRestoreInstanceState(Parcelable state) {
    Bundle bundle = (Bundle) state;
    Parcelable superState = bundle.getParcelable("superState");
    super.onRestoreInstanceState(superState);

    dialInitialized  = bundle.getBoolean("dialInitialized");
    currentValue     = bundle.getFloat("currentValue");
    targetValue       = bundle.getFloat("targetValue");
    dialVelocity     = bundle.getFloat("dialVelocity");
    dialAcceleration = bundle.getFloat("dialAcceleration");
    lastDialMoveTime = bundle.getLong("lastDialMoveTime");
  }

  @Override
  protected Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();

    Bundle state = new Bundle();
    state.putParcelable("superState", superState);
    state.putBoolean("dialInitialized", dialInitialized);
    state.putFloat("currentValue", currentValue);
    state.putFloat("targetValue", targetValue);
    state.putFloat("dialVelocity", dialVelocity);
    state.putFloat("dialAcceleration", dialAcceleration);
    state.putLong("lastDialMoveTime", lastDialMoveTime);
    return state;
  }

  private void init(Context context, AttributeSet attrs) {

    try {
        Method setLayerTypeMethod = getClass().getMethod("setLayerType", new Class[] {int.class, Paint.class});
        setLayerTypeMethod.invoke(this, new Object[] {LAYER_TYPE_SOFTWARE, null});
    } catch (NoSuchMethodException e) {
        // Older OS, no HW acceleration anyway
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }

    // Get the properties from the resource file.
    if (context != null && attrs != null){
      TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Dial);
      showRange              = a.getBoolean(R.styleable.Dial_showRange,          showRange);
      showGauge              = a.getBoolean(R.styleable.Dial_showGauge,          showGauge);
      showHand               = a.getBoolean(R.styleable.Dial_showHand,           showHand);
      showOdo                = a.getBoolean(R.styleable.Dial_showOdo,            showOdo);

      totalNotches           = a.getInt(R.styleable.Dial_totalNotches,           totalNotches);
      incrementPerLargeNotch = a.getInt(R.styleable.Dial_incrementPerLargeNotch, incrementPerLargeNotch);
      incrementPerSmallNotch = a.getInt(R.styleable.Dial_incrementPerSmallNotch, incrementPerSmallNotch);
      scaleCenterValue       = a.getInt(R.styleable.Dial_scaleCenterValue,       scaleCenterValue);
      scaleColor             = a.getInt(R.styleable.Dial_scaleColor,             scaleColor);
      scaleMinValue          = a.getInt(R.styleable.Dial_scaleMinValue,          scaleMinValue);
      scaleMaxValue          = a.getInt(R.styleable.Dial_scaleMaxValue,          scaleMaxValue);
      rangeOkColor           = a.getInt(R.styleable.Dial_rangeOkColor,           rangeOkColor);
      rangeOkMinValue        = a.getInt(R.styleable.Dial_rangeOkMinValue,        rangeOkMinValue);
      rangeOkMaxValue        = a.getInt(R.styleable.Dial_rangeOkMaxValue,        rangeOkMaxValue);
      rangeWarningColor      = a.getInt(R.styleable.Dial_rangeWarningColor,      rangeWarningColor);
      rangeWarningMinValue   = a.getInt(R.styleable.Dial_rangeWarningMinValue,   rangeWarningMinValue);
      rangeWarningMaxValue   = a.getInt(R.styleable.Dial_rangeWarningMaxValue,   rangeWarningMaxValue);
      rangeErrorColor        = a.getInt(R.styleable.Dial_rangeErrorColor,        rangeErrorColor);
      rangeErrorMinValue     = a.getInt(R.styleable.Dial_rangeErrorMinValue,     rangeErrorMinValue);
      rangeErrorMaxValue     = a.getInt(R.styleable.Dial_rangeErrorMaxValue,     rangeErrorMaxValue);
      odoColor               = a.getInt(R.styleable.Dial_odoColor,               odoColor);
      odoBackgroundColor     = a.getInt(R.styleable.Dial_odoBackgroundColor,     odoBackgroundColor);
      String unitTitle       = a.getString(R.styleable.Dial_unitTitle);
      String lowerTitle      = a.getString(R.styleable.Dial_lowerTitle);
      String upperTitle      = a.getString(R.styleable.Dial_upperTitle);
      if (unitTitle != null) this.unitTitle = unitTitle;
      if (lowerTitle != null) this.lowerTitle = lowerTitle;
      if (upperTitle != null) this.upperTitle = upperTitle;
      a.recycle();
    }
    degreesPerNotch       = 360.0f/totalNotches;
    degreeMinValue        = valueToAngle(scaleMinValue)        + centerDegrees;
    degreeMaxValue        = valueToAngle(scaleMaxValue)        + centerDegrees;
    degreeOkMinValue      = valueToAngle(rangeOkMinValue)      + centerDegrees;
    degreeOkMaxValue      = valueToAngle(rangeOkMaxValue)      + centerDegrees;
    degreeWarningMinValue = valueToAngle(rangeWarningMinValue) + centerDegrees;
    degreeWarningMaxValue = valueToAngle(rangeWarningMaxValue) + centerDegrees;
    degreeErrorMinValue   = valueToAngle(rangeErrorMinValue)   + centerDegrees;
    degreeErrorMaxValue   = valueToAngle(rangeErrorMaxValue)   + centerDegrees;

    initDrawingTools(context);
  }

  private void initDrawingTools(Context context) {
    rimRect = new RectF(0.0f, 0.0f, 1.0f, 1.0f);

    faceRect = new RectF();
    faceRect.set(rimRect.left  + rimSize, rimRect.top    + rimSize,
        rimRect.right - rimSize, rimRect.bottom - rimSize);

    scaleRect = new RectF();
    scaleRect.set(faceRect.left + scalePosition, faceRect.top + scalePosition,
        faceRect.right - scalePosition, faceRect.bottom - scalePosition);

    rangeRect = new RectF();
    rangeRect.set(faceRect.left  + rangePosition, faceRect.top    + rangePosition,
        faceRect.right - rangePosition, faceRect.bottom - rangePosition);

    valueRect = new RectF();
    valueRect.set(faceRect.left  + valuePosition, faceRect.top    + valuePosition,
        faceRect.right - valuePosition, faceRect.bottom - valuePosition);

    titleRect = new RectF();
    titleRect.set(faceRect.left  + titlePosition, faceRect.top    + titlePosition,
        faceRect.right - titlePosition, faceRect.bottom - titlePosition);

    unitRect = new RectF();
    unitRect.set(faceRect.left  + unitPosition, faceRect.top    + unitPosition,
        faceRect.right - unitPosition, faceRect.bottom - unitPosition);

    odoRect = new RectF(0.345f, 0.72f, 0.655f, 0.62f);

    faceTexture = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.plastic);
    BitmapShader paperShader = new BitmapShader(faceTexture,
        Shader.TileMode.MIRROR,
        Shader.TileMode.MIRROR);
    Matrix paperMatrix = new Matrix();
    paperMatrix.setScale(1.0f / faceTexture.getWidth(),
        1.0f / faceTexture.getHeight());

    paperShader.setLocalMatrix(paperMatrix);

    rimShadowPaint = new Paint();
    rimShadowPaint.setShader(new RadialGradient(0.5f, 0.5f, faceRect.width() / 2.0f,
        new int[] { 0x00000000, 0x00000500, 0x50000500 },
        new float[] { 0.96f, 0.96f, 0.99f },
        Shader.TileMode.MIRROR));
    rimShadowPaint.setStyle(Paint.Style.FILL);

    // the linear gradient is a bit skewed for realism
    rimPaint = new Paint();
    rimPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
    rimPaint.setShader(new LinearGradient(0.40f, 0.0f, 0.60f, 1.0f,
        Color.rgb(0xf0, 0xf5, 0xf0),
        Color.rgb(0x30, 0x31, 0x30),
        Shader.TileMode.CLAMP));

    rimCirclePaint = new Paint();
    rimCirclePaint.setAntiAlias(true);
    rimCirclePaint.setStyle(Paint.Style.STROKE);
    rimCirclePaint.setColor(Color.argb(0x4f, 0x33, 0x36, 0x33));
    rimCirclePaint.setStrokeWidth(0.005f);

    facePaint = new Paint();
    facePaint.setFilterBitmap(true);
    facePaint.setStyle(Paint.Style.FILL);
    facePaint.setShader(paperShader);

    scalePaint = new Paint();
    scalePaint.setStyle(Paint.Style.STROKE);
    scalePaint.setColor(scaleColor);
    scalePaint.setStrokeWidth(0.005f);
    scalePaint.setAntiAlias(true);
    scalePaint.setTextSize(0.045f);
    scalePaint.setTypeface(Typeface.SANS_SERIF);
    scalePaint.setTextScaleX(0.8f);
    scalePaint.setTextAlign(Paint.Align.CENTER);

    rangeOkPaint = new Paint();
    rangeOkPaint.setStyle(Paint.Style.STROKE);
    rangeOkPaint.setColor(rangeOkColor);
    rangeOkPaint.setStrokeWidth(0.012f);
    rangeOkPaint.setAntiAlias(true);

    rangeWarningPaint = new Paint();
    rangeWarningPaint.setStyle(Paint.Style.STROKE);
    rangeWarningPaint.setColor(rangeWarningColor);
    rangeWarningPaint.setStrokeWidth(0.012f);
    rangeWarningPaint.setAntiAlias(true);

    rangeErrorPaint = new Paint();
    rangeErrorPaint.setStyle(Paint.Style.STROKE);
    rangeErrorPaint.setColor(rangeErrorColor);
    rangeErrorPaint.setStrokeWidth(0.012f);
    rangeErrorPaint.setAntiAlias(true);

    rangeAllPaint = new Paint();
    rangeAllPaint.setStyle(Paint.Style.STROKE);
    rangeAllPaint.setColor(0xcff8f8f8);
    rangeAllPaint.setStrokeWidth(0.012f);
    rangeAllPaint.setAntiAlias(true);
    rangeAllPaint.setShadowLayer(0.005f, -0.002f, -0.002f, 0x7f000000);

    valueOkPaint = new Paint();
    valueOkPaint.setStyle(Paint.Style.STROKE);
    valueOkPaint.setColor(rangeOkColor);
    valueOkPaint.setStrokeWidth(0.20f);
    valueOkPaint.setAntiAlias(true);

    valueWarningPaint = new Paint();
    valueWarningPaint.setStyle(Paint.Style.STROKE);
    valueWarningPaint.setColor(rangeWarningColor);
    valueWarningPaint.setStrokeWidth(0.20f);
    valueWarningPaint.setAntiAlias(true);

    valueErrorPaint = new Paint();
    valueErrorPaint.setStyle(Paint.Style.STROKE);
    valueErrorPaint.setColor(rangeErrorColor);
    valueErrorPaint.setStrokeWidth(0.20f);
    valueErrorPaint.setAntiAlias(true);

    valueAllPaint = new Paint();
    valueAllPaint.setStyle(Paint.Style.STROKE);
    valueAllPaint.setColor(0xcff8f8f8);
    valueAllPaint.setStrokeWidth(0.20f);
    valueAllPaint.setAntiAlias(true);
    valueAllPaint.setShadowLayer(0.005f, -0.002f, -0.002f, 0x7f000000);

    unitPaint = new Paint();
    unitPaint.setColor(0xaf0c0c0c);
    unitPaint.setAntiAlias(true);
    unitPaint.setTypeface(Typeface.DEFAULT_BOLD);
    unitPaint.setTextAlign(Paint.Align.CENTER);
    unitPaint.setTextSize(0.05f);
    unitPaint.setTextScaleX(0.8f);

    upperTitlePaint = new Paint();
    upperTitlePaint.setColor(0xaf0c0c0c);
    upperTitlePaint.setAntiAlias(true);
    upperTitlePaint.setTypeface(Typeface.DEFAULT_BOLD);
    upperTitlePaint.setTextAlign(Paint.Align.CENTER);
    upperTitlePaint.setTextSize(0.04f);
    upperTitlePaint.setTextScaleX(0.8f);

    lowerTitlePaint = new Paint();
    lowerTitlePaint.setColor(0xaf0c0c0c);
    lowerTitlePaint.setAntiAlias(true);
    lowerTitlePaint.setTypeface(Typeface.DEFAULT_BOLD);
    lowerTitlePaint.setTextAlign(Paint.Align.CENTER);
    lowerTitlePaint.setTextSize(0.04f);
    lowerTitlePaint.setTextScaleX(0.8f);


    handPaint = new Paint();
    handPaint.setAntiAlias(true);
    handPaint.setColor(0xff392f2c);
    handPaint.setShadowLayer(0.01f, -0.005f, -0.005f, 0x7f000000);
    handPaint.setStyle(Paint.Style.FILL);

    handScrewPaint = new Paint();
    handScrewPaint.setAntiAlias(true);
    //handScrewPaint.setColor(0xff493f3c);
    handScrewPaint.setColor(0xffff3f3c);
    handScrewPaint.setStyle(Paint.Style.FILL);

    backgroundPaint = new Paint();
    backgroundPaint.setFilterBitmap(true);

    unitPath = new Path();
    unitPath.addArc(unitRect, 180.0f, 180.0f);

    upperTitlePath = new Path();
    upperTitlePath.addArc(titleRect, 180.0f, 180.0f);

    lowerTitlePath = new Path();
    lowerTitlePath.addArc(titleRect, -180.0f, -180.0f);

    // The hand is drawn with the tip facing up. That means when the image is not rotated, the tip
    // faces north. When the the image is rotated -90 degrees, the tip is facing west and so on.
    handPath = new Path();                                              //   X      Y
    handPath.moveTo(0.5f, 0.5f + 0.2f);                                 // 0.500, 0.700
    handPath.lineTo(0.5f - 0.010f, 0.5f + 0.2f - 0.007f);               // 0.490, 0.630
    handPath.lineTo(0.5f - 0.002f, 0.5f - 0.40f);                       // 0.498, 0.100
    handPath.lineTo(0.5f + 0.002f, 0.5f - 0.40f);                       // 0.502, 0.100
    handPath.lineTo(0.5f + 0.010f, 0.5f + 0.2f - 0.007f);               // 0.510, 0.630
    handPath.lineTo(0.5f, 0.5f + 0.2f);                                 // 0.500, 0.700
    handPath.addCircle(0.5f, 0.5f, 0.025f, Path.Direction.CW);

    odoPaint = new Paint();
    Typeface lcd = Typeface.createFromAsset(context.getAssets(), "fonts/digital-7-mono.ttf");
    odoPaint.setColor(odoColor);
    odoPaint.setAntiAlias(true);
    odoPaint.setTextSize(0.08f);
    odoPaint.setTypeface(lcd);
    odoPaint.setTextAlign(Paint.Align.RIGHT);

    odoPath = new Path();
    odoPath.moveTo(0, 0.697f);
    odoPath.lineTo(0.635f, 0.697f);

    odoBackgroundPaint = new Paint();
    odoBackgroundPaint.setColor(odoBackgroundColor);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (D) Log.d(TAG, "Width spec: " + MeasureSpec.toString(widthMeasureSpec));
    if (D) Log.d(TAG, "Height spec: " + MeasureSpec.toString(heightMeasureSpec));

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int chosenWidth = chooseDimension(widthMode, widthSize);
    int chosenHeight = chooseDimension(heightMode, heightSize);

    int chosenDimension = Math.min(chosenWidth, chosenHeight);

    setMeasuredDimension(chosenDimension, chosenDimension);
  }

  private int chooseDimension(int mode, int size) {
    if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
      return size;
    } else { // (mode == MeasureSpec.UNSPECIFIED)
      return getPreferredSize();
    }
  }

  // in case there is no size specified
  private int getPreferredSize() {
    return 250;
  }

  private void drawRim(Canvas canvas) {
    // first, draw the metallic body
    canvas.drawOval(rimRect, rimPaint);
    // now the outer rim circle
    canvas.drawOval(rimRect, rimCirclePaint);
  }

  private void drawFace(Canvas canvas) {
    canvas.drawOval(faceRect, facePaint);
    // draw the inner rim circle
    canvas.drawOval(faceRect, rimCirclePaint);
    // draw the rim shadow inside the face
    canvas.drawOval(faceRect, rimShadowPaint);
  }


  private void drawBackground(Canvas canvas) {
    if (background == null) {
      Log.w(TAG, "Background not created");
    } else {
      canvas.drawBitmap(background, 0, 0, backgroundPaint);
    }
  }

  private void drawScale(Canvas canvas) {
    // Draw the circle
    canvas.drawOval(scaleRect, scalePaint);

    canvas.save(Canvas.MATRIX_SAVE_FLAG);
    for (int i = 0; i < totalNotches; ++i) {
      float y1 = scaleRect.top;
      float y2 = y1 - 0.015f;
      float y3 = y1 - 0.025f;

      int value = notchToValue(i);

      if (i % (incrementPerLargeNotch/incrementPerSmallNotch) == 0) {
        if (value >= scaleMinValue && value <= scaleMaxValue) {
          // draw a nick
          canvas.drawLine(0.5f, y1, 0.5f, y3, scalePaint);

          String valueString = Integer.toString(value);
          // Draw the text 0.15 away from y3 which is the long nick.
          Path path = new Path();
          path.moveTo(0.4f, y3 - 0.015f);
          path.lineTo(0.6f, y3 - 0.015f);
          canvas.drawTextOnPath(valueString, path, 0, 0, scalePaint);
        }
      }
      else{
        if (value >= scaleMinValue && value <= scaleMaxValue) {
          // draw a nick
          canvas.drawLine(0.5f, y1, 0.5f, y2, scalePaint);
        }
      }

      canvas.rotate(degreesPerNotch, 0.5f, 0.5f);
    }
    canvas.restore();
  }

  private void drawScaleRanges(Canvas canvas) {
    canvas.save(Canvas.MATRIX_SAVE_FLAG);
    canvas.drawArc(rangeRect, degreeMinValue,        degreeMaxValue -        degreeMinValue,        false, rangeAllPaint);
    canvas.drawArc(rangeRect, degreeOkMinValue,      degreeOkMaxValue      - degreeOkMinValue,      false, rangeOkPaint);
    canvas.drawArc(rangeRect, degreeWarningMinValue, degreeWarningMaxValue - degreeWarningMinValue, false, rangeWarningPaint);
    canvas.drawArc(rangeRect, degreeErrorMinValue,   degreeErrorMaxValue   - degreeErrorMinValue,   false, rangeErrorPaint);
    canvas.restore();
  }

  private void drawTitle(Canvas canvas) {
    // Use a vertical offset when printing the upper title. The upper and lower title
    // use the same rectangular but the spacing  between the title and the ranges
    // is not equal for the upper and lower title and therefore, the upper title is
    // moved downwards.
    canvas.drawTextOnPath(upperTitle, upperTitlePath, 0.0f, 0.02f, upperTitlePaint);
    canvas.drawTextOnPath(lowerTitle, lowerTitlePath, 0.0f, 0.0f,  lowerTitlePaint);
    canvas.drawTextOnPath(unitTitle,  unitPath,       0.0f, 0.0f,  unitPaint);
  }

  private void drawHand(Canvas canvas) {
    if (dialInitialized) {
      float angle = valueToAngle(currentValue);
      canvas.save(Canvas.MATRIX_SAVE_FLAG);
      canvas.rotate(angle, 0.5f, 0.5f);
      canvas.drawPath(handPath, handPaint);
      canvas.restore();

      canvas.drawCircle(0.5f, 0.5f, 0.01f, handScrewPaint);
    }
  }

  private void drawGauge(Canvas canvas) {
    if (dialInitialized) {
      // When currentValue is not rotated, the tip of the hand points
      // to n -90 degrees.
      float angle = valueToAngle(currentValue) - 90;

      if(targetValue <= rangeOkMaxValue){
        canvas.drawArc(valueRect, degreeMinValue, angle - degreeMinValue, false, valueOkPaint);
      }
      if((targetValue > rangeOkMaxValue) && (targetValue <= rangeWarningMaxValue)){
        canvas.drawArc(valueRect, degreeMinValue, degreeOkMaxValue - degreeMinValue, false, valueOkPaint);
        canvas.drawArc(valueRect, degreeWarningMinValue, angle - degreeWarningMinValue, false, valueWarningPaint);
      }
      if((targetValue > rangeWarningMaxValue) && (targetValue <= rangeErrorMaxValue)){
        canvas.drawArc(valueRect, degreeMinValue, degreeOkMaxValue - degreeMinValue, false, valueOkPaint);
        canvas.drawArc(valueRect, degreeWarningMinValue, degreeWarningMaxValue - degreeWarningMinValue, false, valueWarningPaint);
        canvas.drawArc(valueRect, degreeErrorMinValue, angle - degreeErrorMinValue, false, valueErrorPaint);
      }
    }
  }

  private void drawBezel(Canvas canvas) {
    // Draw the bevel in which the value is draw.
    canvas.save(Canvas.MATRIX_SAVE_FLAG);
    canvas.drawArc(valueRect, degreeMinValue, degreeMaxValue - degreeMinValue, false, valueAllPaint);
    canvas.restore();
  }

  private void drawOdo(Canvas canvas) {
    canvas.drawRect(odoRect, odoBackgroundPaint);
    /*
     * use drawTextOnPath() instead of drawText() because of:
     * https://code.google.com/p/android/issues/detail?id=39755
     */
    canvas.drawTextOnPath(String.format("%4.2f", targetOdoValue), odoPath, 0, 0, odoPaint);
  }

  /* Translate a notch to a value for the scale.
   * The notches are evenly spread across the scale, half of the notches on the left hand side
   * and the other half on the right hand side.
   * The raw value calculation uses a constant so that each notch represents a value n + 2.
   */
  private int notchToValue(int notch) {
    int rawValue = ((notch < totalNotches / 2) ? notch : (notch - totalNotches)) * incrementPerSmallNotch;
    int shiftedValue = rawValue + scaleCenterValue;
    return shiftedValue;
  }

  private float valueToAngle(float value) {
    // scaleCenterValue represents an angle of -90 degrees.
    return (value - scaleCenterValue) / incrementPerSmallNotch * degreesPerNotch;
  }

  @Override
  protected void onDraw(Canvas canvas) {
    drawBackground(canvas);

    float scale = (float) getWidth();
    canvas.save(Canvas.MATRIX_SAVE_FLAG);
    canvas.scale(scale, scale);
    // Draw the needle using the updated value
    if (showGauge){
      drawGauge(canvas);
    }

    if (showOdo){
      drawOdo(canvas);
    }

    // Draw the needle using the updated value
    if (showHand){
      drawHand(canvas);
    }

    canvas.restore();

    // Calculate a new current value.
    calculateCurrentValue();
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    if (D) Log.d(TAG, "Size changed to " + w + "x" + h);
    regenerateBackground();
  }

  private void regenerateBackground() {
    // free the old bitmap
    if (background != null) {
      background.recycle();
    }

    background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
    Canvas backgroundCanvas = new Canvas(background);
    float scale = (float) getWidth();
    backgroundCanvas.scale(scale, scale);

    drawRim(backgroundCanvas);
    drawFace(backgroundCanvas);
    drawScale(backgroundCanvas);
    if (showRange){
      drawScaleRanges(backgroundCanvas);
    }
    if (showGauge){
      drawBezel(backgroundCanvas);
    }
    drawTitle(backgroundCanvas);
  }

  // Move the hand slowly to the new position.
  private void calculateCurrentValue() {
    if (!(Math.abs(currentValue - targetValue) > 0.01f)) {
      return;
    }

    if (lastDialMoveTime != -1L) {
      long currentTime = System.currentTimeMillis();
      float delta = (currentTime - lastDialMoveTime) / 1000.0f;

      float direction = Math.signum(dialVelocity);
      if (Math.abs(dialVelocity) < 90.0f) {
        dialAcceleration = 5.0f * (targetValue - currentValue);
      } else {
        dialAcceleration = 0.0f;
      }
      currentValue += dialVelocity * delta;
      dialVelocity += dialAcceleration * delta;
      if ((targetValue - currentValue) * direction < 0.01f * direction) {
        currentValue = targetValue;
        dialVelocity = 0.0f;
        dialAcceleration = 0.0f;
        lastDialMoveTime = -1L;
      } else {
        lastDialMoveTime = System.currentTimeMillis();
      }
      invalidate();
    } else {
      lastDialMoveTime = System.currentTimeMillis();
      calculateCurrentValue();
    }
  }

  public void setValue(float value) {
    if      (value < scaleMinValue) value = scaleMinValue;
    else if (value > scaleMaxValue) value = scaleMaxValue;

    targetValue = value;
    dialInitialized = true;

    invalidate(); // forces onDraw() to be called.
  }

  public float getValue() {
    return targetValue;
  }

  public void setOdoValue(float value) {
    targetOdoValue = value;
    invalidate();
  }
}




Java Source Code List

org.harleydroid.About.java
org.harleydroid.ELM327Interface.java
org.harleydroid.EmulatorInterface.java
org.harleydroid.Eula.java
org.harleydroid.Gauge.java
org.harleydroid.HarleyDataDashboardListener.java
org.harleydroid.HarleyDataDiagnosticsListener.java
org.harleydroid.HarleyDataRawListener.java
org.harleydroid.HarleyData.java
org.harleydroid.HarleyDroidDashboardView.java
org.harleydroid.HarleyDroidDashboard.java
org.harleydroid.HarleyDroidDiagnosticsView.java
org.harleydroid.HarleyDroidDiagnostics.java
org.harleydroid.HarleyDroidGPS.java
org.harleydroid.HarleyDroidInterface.java
org.harleydroid.HarleyDroidLogger.java
org.harleydroid.HarleyDroidService.java
org.harleydroid.HarleyDroidSettings.java
org.harleydroid.HarleyDroid.java
org.harleydroid.J1850Interface.java
org.harleydroid.J1850.java
org.harleydroid.NonBlockingBluetoothSocket.java
org.harleydroid.VINDecoder.java