// Copyright (c) 2010, Freddy Martens (http://atstechlab.wordpress.com), // MindTheRobot (http://mindtherobot.com/blog/) and contributors // All rights reserved. // // 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 nl.digitalthings.mebarista; import nl.digitalthings.mebarista.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 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 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; // end drawing tools private Bitmap background; // holds the cached static part // scale configuration // Values passed as property. Defaults are set here. private boolean showHand = true; private boolean showGauge = false; private boolean showRange = 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 int angleMinValue = -120; private int angleMaxValue = 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 String lowerTitle = "www.ats-global.com"; private String upperTitle = "Visit http://atstechlab.wordpress.com"; private String unitTitle = "\u2103"; // Fixed values. private static float scalePosition = 0.10f; // The distance from the rim to the scale private static float valuePosition = 0.285f; // The distance from the rim to the ranges private static float rangePosition = 0.122f; // The distance from the rim to the ranges private static float titlePosition = 0.145f; // The Distance from the rim to the titles private static float unitPosition = 0.300f; // The distance from the rim to the unit private static 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 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) { // Get the properties from the resource file. // setLayerType(View.LAYER_TYPE_SOFTWARE, null); 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); 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); angleMinValue = a.getInt(R.styleable.Dial_angleMinValue, angleMinValue); angleMaxValue = a.getInt(R.styleable.Dial_angleMaxValue, angleMaxValue); 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); 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; } 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; degreesPerNotch = ( angleMaxValue - angleMinValue )*1.0f / totalNotches; // initDrawingTools(); } private void initDrawingTools() { // All drawing has been scaled to actual pixels because of : // https://groups.google.com/forum/#!msg/android-developers/eTxV4KPy1G4/tAe2zUPCjMcJ int w = getWidth(), h = getHeight(); rimRect = new RectF(0.0f, 0.0f, getWidth() /* 1.0f */, getHeight() /* 1.0f */); scalePosition = 0.10f * getWidth(); // The distance from the rim to the scale valuePosition = 0.285f * getWidth(); // The distance from the rim to the ranges rangePosition = 0.122f * getWidth(); // The distance from the rim to the ranges titlePosition = 0.145f * getWidth(); // The Distance from the rim to the titles unitPosition = 0.300f * getWidth(); // The distance from the rim to the unit rimSize = 0.02f * getWidth(); 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); 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 * getWidth(), 0.5f * getHeight(), 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 * getWidth(), 0.0f, 0.60f * getWidth(), 1.0f * getHeight(), 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 * getWidth() ); 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 * getWidth() ); scalePaint.setAntiAlias(true); // // scalePaint.setTextSize(0.045f); // http://stackoverflow.com/questions/13974129/android-4-2-1-wrong-character-kerning-spacing scalePaint.setTextSize( 0.045f * getWidth() ); scalePaint.setLinearText(true); 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 * getWidth() ); rangeOkPaint.setAntiAlias(true); rangeWarningPaint = new Paint(); rangeWarningPaint.setStyle(Paint.Style.STROKE); rangeWarningPaint.setColor(rangeWarningColor); rangeWarningPaint.setStrokeWidth(0.012f * getWidth() ); rangeWarningPaint.setAntiAlias(true); rangeErrorPaint = new Paint(); rangeErrorPaint.setStyle(Paint.Style.STROKE); rangeErrorPaint.setColor(rangeErrorColor); rangeErrorPaint.setStrokeWidth(0.012f * getWidth() ); rangeErrorPaint.setAntiAlias(true); rangeAllPaint = new Paint(); rangeAllPaint.setStyle(Paint.Style.STROKE); rangeAllPaint.setColor(0xcff8f8f8); rangeAllPaint.setStrokeWidth(0.012f * getWidth() ); 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 * getWidth() ); valueOkPaint.setAntiAlias(true); valueWarningPaint = new Paint(); valueWarningPaint.setStyle(Paint.Style.STROKE); valueWarningPaint.setColor(rangeWarningColor); valueWarningPaint.setStrokeWidth(0.20f * getWidth() ); valueWarningPaint.setAntiAlias(true); valueErrorPaint = new Paint(); valueErrorPaint.setStyle(Paint.Style.STROKE); valueErrorPaint.setColor(rangeErrorColor); valueErrorPaint.setStrokeWidth(0.20f * getWidth() ); valueErrorPaint.setAntiAlias(true); valueAllPaint = new Paint(); valueAllPaint.setStyle(Paint.Style.STROKE); valueAllPaint.setColor(0xcff8f8f8); valueAllPaint.setStrokeWidth(0.20f * getWidth() ); 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 * getWidth() ); 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.09f * getWidth() ); 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 * getWidth() ); 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); handPaint.setStrokeWidth(0.1f); //handPaint.setColor(android.graphics.Color.RED); //handPaint.setStyle(Paint.Style.STROKE); 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(); // handPath.setFillType(Path.FillType.EVEN_ODD);// X Y handPath.moveTo(0.5f * w, (0.5f + 0.2f) * h); // 0.500, 0.700 handPath.lineTo( ( 0.5f - 0.010f ) * w, ( 0.5f + 0.2f - 0.007f ) * h); // 0.490, 0.630 handPath.lineTo( ( 0.5f - 0.002f ) * w, ( 0.5f - 0.40f ) * h); // 0.498, 0.100 handPath.lineTo( ( 0.5f + 0.002f ) * w, ( 0.5f - 0.40f ) * h); // 0.502, 0.100 handPath.lineTo((0.5f + 0.010f) * w, (0.5f + 0.2f - 0.007f) * h); // 0.510, 0.630 //handPath.lineTo(0.5f * w, 0.5f + 0.2f * h); // 0.500, 0.700 handPath.addCircle(0.5f * w, 0.5f * h, 0.025f * w, Path.Direction.CW); handPath.close(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //Log.d(TAG, "Width spec: " + MeasureSpec.toString(widthMeasureSpec)); //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 // was: canvas.drawOval(faceRect, rimCirclePaint); canvas.drawOval(faceRect, rimCirclePaint); // draw the rim shadow inside the face canvas.drawOval(faceRect, rimShadowPaint); // boosdoener } private void drawBackground(Canvas canvas) { if (background == null) { Log.w(TAG, "Background not created"); } else { canvas.drawBitmap(background, 0, 0, backgroundPaint); } } private void fa( Canvas canvas, String text, Paint p, float x, float y ) { Path mTextOutlinePath = new Path(); // adjust the x,y text origin to your personal needs p.getTextPath(text, 0, text.length(), x /* getPaddingLeft() */, y /* getBaseline() */, mTextOutlinePath); canvas.drawPath(mTextOutlinePath, p); // canvas.drawPath(mTextOutlinePath, mStrokePaint); } private void drawScale(Canvas canvas) { // Draw the circle canvas.drawOval(scaleRect, scalePaint); for (int i = 0; i <= totalNotches; ++i) { canvas.save(Canvas.MATRIX_SAVE_FLAG); canvas.rotate(degreeOkMinValue + 90 + degreesPerNotch * i, 0.5f * getWidth(), 0.5f * getHeight()); float y1 = scaleRect.top; float y2 = y1 - 0.015f * getHeight(); float y3 = y1 - 0.025f * getHeight(); int value = notchToValue(i); if (i % (incrementPerLargeNotch/incrementPerSmallNotch) == 0) { /* if (value >= scaleMinValue && value <= scaleMaxValue) { */ // draw a nick canvas.drawLine(0.5f * getWidth(), y1, 0.5f * getWidth(), y3, scalePaint); String valueString = Integer.toString(value); canvas.drawText(valueString, 0.5f * getWidth(), y3 - 0.015f * getWidth(), scalePaint); /* } */ } else{ /* if (value >= scaleMinValue && value <= scaleMaxValue) { */ // draw a nick canvas.drawLine(0.5f * getWidth(), y1, 0.5f * getWidth(), y2, scalePaint); /* } */ } canvas.restore(); } } private void drawScaleRanges(Canvas canvas) { 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); } 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 * getHeight(), upperTitlePaint); canvas.drawTextOnPath(lowerTitle, lowerTitlePath, 0.0f, 0.0f, lowerTitlePaint); canvas.drawTextOnPath(unitTitle, unitPath, 0.0f, 0.0f, unitPaint); } int centerx, centery; private void drawHand(Canvas canvas) { if (true || dialInitialized) { float angle = valueToAngle(currentValue ); canvas.save(Canvas.MATRIX_SAVE_FLAG); canvas.rotate(angle, centerx, centery); canvas.drawPath(handPath, handPaint); canvas.restore(); canvas.drawCircle(0.5f * getWidth() , 0.5f * getHeight() , 0.01f * getWidth(), 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(); } /* 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; return notch * ( scaleMaxValue - scaleMinValue ) / totalNotches; } private float valueToAngle(float value) { // scaleCenterValue represents an angle of -90 degrees. //return (value - scaleCenterValue) / 2.0f * degreesPerNotch; // jaja float frac = ( value - scaleMinValue ) / ( scaleMaxValue - scaleMinValue ); return angleMinValue + frac * ( angleMaxValue - angleMinValue ); //return degreeOkMinValue - ( value / scaleMaxValue ) * ( degreeOkMinValue + 90 ); } private float valueToAngle2(float value) { // scaleCenterValue represents an angle of -90 degrees. // return (value - scaleCenterValue) / 2.0f * degreesPerNotch; return valueToAngle(value); //return degreeOkMinValue - ( value / scaleMaxValue ) * 2 * ( degreeOkMinValue + 90 ); } // Canvas backgroundCanvas ; @Override protected void onDraw(Canvas canvas) { drawBackground(canvas); // Draw the needle using the updated value if (showHand){ drawHand( canvas ); } // Calculate a new current value. calculateCurrentValue(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // Log.d(TAG, "Size changed to " + w + "x" + h); initDrawingTools(); regenerateBackground(); centerx = w / 2; centery = h / 2; } 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); 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; } }