Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customize a ProgressBar to become a Thermometer

How to customize a ProgressBar to look like a Thermometer ? with the possibility to change color.

Thermometer bleu fullThermometer bleu emptyThermometer red full Thermometer red empty

My suggestion was to rotate the progressBar 90° to become vertical then have it overlay an image of an empty Thermometer but it's bad and messy solution.

I Think the best will be to either to extends View or ProgressBar class and customize the draw method but I have no idea how to draw Thermometer, any Help would be appreciated.

like image 381
M'hamed Avatar asked Feb 11 '23 19:02

M'hamed


2 Answers

I created something like this for a project Thermometer Widget

    package com.janslab.thermometer.widgets;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Scroller;

import com.janslab.thermometer.R;



public class DummyThermometer extends View {

    private Paint mInnerCirclePaint;
    private Paint mOuterCirclePaint;
    private Paint mFirstOuterCirclePaint;

    //thermometer arc paint
    private Paint mFirstOuterArcPaint;


    //thermometer lines paints
    private Paint mInnerLinePaint;
    private Paint mOuterLinePaint;
    private Paint mFirstOuterLinePaint;


    //thermometer radii
    private int mOuterRadius;
    private int mInnerRadius;
    private int mFirstOuterRadius;


    //thermometer colors
    private int mThermometerColor = Color.rgb(200, 115, 205);

    //circles and lines  variables
    private float mLastCellWidth;
    private int mStageHeight;
    private float mCellWidth;
    private float mStartCenterY; //center of first cell
    private float mEndCenterY; //center of last cell
    private float mStageCenterX;
    private float mXOffset;
    private float mYOffset;

    // I   1st Cell     I  2nd Cell       I  3rd Cell  I
    private static final int NUMBER_OF_CELLS = 3; //three cells in all  ie.stageHeight divided into 3 equal cells

    //animation variables
    private float mIncrementalTempValue;
    private boolean mIsAnimating;
    private Animator mAnimator;


    public DummyThermometer(Context context) {
        this(context, null);
    }

    public DummyThermometer(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DummyThermometer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        if (attrs != null) {

            final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Thermometer, defStyle, 0);

            mThermometerColor = a.getColor(R.styleable.Thermometer_therm_color, mThermometerColor);

            a.recycle();
        }

        init();
    }


    private void init() {

        mInnerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mInnerCirclePaint.setColor(mThermometerColor);
        mInnerCirclePaint.setStyle(Paint.Style.FILL);
        mInnerCirclePaint.setStrokeWidth(17f);


        mOuterCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mOuterCirclePaint.setColor(Color.WHITE);
        mOuterCirclePaint.setStyle(Paint.Style.FILL);
        mOuterCirclePaint.setStrokeWidth(32f);


        mFirstOuterCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mFirstOuterCirclePaint.setColor(mThermometerColor);
        mFirstOuterCirclePaint.setStyle(Paint.Style.FILL);
        mFirstOuterCirclePaint.setStrokeWidth(60f);


        mFirstOuterArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mFirstOuterArcPaint.setColor(mThermometerColor);
        mFirstOuterArcPaint.setStyle(Paint.Style.STROKE);
        mFirstOuterArcPaint.setStrokeWidth(30f);


        mInnerLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mInnerLinePaint.setColor(mThermometerColor);
        mInnerLinePaint.setStyle(Paint.Style.FILL);
        mInnerLinePaint.setStrokeWidth(17f);

        mOuterLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mOuterLinePaint.setColor(Color.WHITE);
        mOuterLinePaint.setStyle(Paint.Style.FILL);


        mFirstOuterLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mFirstOuterLinePaint.setColor(mThermometerColor);
        mFirstOuterLinePaint.setStyle(Paint.Style.FILL);


    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mStageCenterX = getWidth() / 2;

        mStageHeight = getHeight();

        mCellWidth = mStageHeight / NUMBER_OF_CELLS;

        //center of first cell
        mStartCenterY = mCellWidth / 2;


        //move to 3rd cell
        mLastCellWidth = (NUMBER_OF_CELLS * mCellWidth);

        //center of last(3rd) cell
        mEndCenterY = mLastCellWidth - (mCellWidth / 2);


        // mOuterRadius is 1/4 of mCellWidth
        mOuterRadius = (int) (0.25 * mCellWidth);

        mInnerRadius = (int) (0.656 * mOuterRadius);

        mFirstOuterRadius = (int) (1.344 * mOuterRadius);

        mFirstOuterLinePaint.setStrokeWidth(mFirstOuterRadius);

        mOuterLinePaint.setStrokeWidth(mFirstOuterRadius / 2);

        mFirstOuterArcPaint.setStrokeWidth(mFirstOuterRadius / 4);

        mXOffset = mFirstOuterRadius / 4;
        mXOffset = mXOffset / 2;

        //get the d/f btn firstOuterLine and innerAnimatedline
        mYOffset = (mStartCenterY + (float) 0.875 * mOuterRadius) - (mStartCenterY + mInnerRadius);
        mYOffset = mYOffset / 2;

    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        drawFirstOuterCircle(canvas);

        drawOuterCircle(canvas);

        drawInnerCircle(canvas);

        drawFirstOuterLine(canvas);

        drawOuterLine(canvas);

        animateInnerLine(canvas);

        drawFirstOuterCornerArc(canvas);

    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //take care of paddingTop and paddingBottom
        int paddingY = getPaddingBottom() + getPaddingTop();

        //get height and width
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        height += paddingY;

        setMeasuredDimension(width, height);
    }


    private void drawInnerCircle(Canvas canvas) {
        drawCircle(canvas, mInnerRadius, mInnerCirclePaint);
    }

    private void drawOuterCircle(Canvas canvas) {
        drawCircle(canvas, mOuterRadius, mOuterCirclePaint);
    }


    private void drawFirstOuterCircle(Canvas canvas) {
        drawCircle(canvas, mFirstOuterRadius, mFirstOuterCirclePaint);
    }


    private void drawCircle(Canvas canvas, float radius, Paint paint) {
        canvas.drawCircle(mStageCenterX, mEndCenterY, radius, paint);
    }

    private void drawOuterLine(Canvas canvas) {

        float startY = mEndCenterY - (float) (0.875 * mOuterRadius);
        float stopY = mStartCenterY + (float) (0.875 * mOuterRadius);

        drawLine(canvas, startY, stopY, mOuterLinePaint);
    }


    private void drawFirstOuterLine(Canvas canvas) {

        float startY = mEndCenterY - (float) (0.875 * mFirstOuterRadius);
        float stopY = mStartCenterY + (float) (0.875 * mOuterRadius);

        drawLine(canvas, startY, stopY, mFirstOuterLinePaint);
    }


    private void drawLine(Canvas canvas, float startY, float stopY, Paint paint) {
        canvas.drawLine(mStageCenterX, startY, mStageCenterX, stopY, paint);
    }


    //simulate temperature measurement for now
    private void animateInnerLine(Canvas canvas) {

        if (mAnimator == null)
            measureTemperature();


        if (!mIsAnimating) {

            mIncrementalTempValue = mEndCenterY + (float) (0.875 * mInnerRadius);

            mIsAnimating = true;

        } else {

            mIncrementalTempValue = mEndCenterY + (float) (0.875 * mInnerRadius) - mIncrementalTempValue;

        }

        if (mIncrementalTempValue > mStartCenterY + mInnerRadius) {
            float startY = mEndCenterY + (float) (0.875 * mInnerRadius);
            drawLine(canvas, startY, mIncrementalTempValue, mInnerCirclePaint);

        } else {

            float startY = mEndCenterY + (float) (0.875 * mInnerRadius);
            float stopY = mStartCenterY + mInnerRadius;
            drawLine(canvas, startY, stopY, mInnerCirclePaint);
            mIsAnimating = false;
            stopMeasurement();

        }

    }


    private void drawFirstOuterCornerArc(Canvas canvas) {

        float y = mStartCenterY - (float) (0.875 * mFirstOuterRadius);

        RectF rectF = new RectF(mStageCenterX - mFirstOuterRadius / 2 + mXOffset, y + mFirstOuterRadius, mStageCenterX + mFirstOuterRadius / 2 - mXOffset, y + (2 * mFirstOuterRadius) + mYOffset);

        canvas.drawArc(rectF, -180, 180, false, mFirstOuterArcPaint);

    }


    public void setThermometerColor(int thermometerColor) {
        this.mThermometerColor = thermometerColor;

        mInnerCirclePaint.setColor(mThermometerColor);

        mFirstOuterCirclePaint.setColor(mThermometerColor);

        mFirstOuterArcPaint.setColor(mThermometerColor);

        mInnerLinePaint.setColor(mThermometerColor);

        mFirstOuterLinePaint.setColor(mThermometerColor);

        invalidate();
    }


    //simulate temperature measurement for now
    private void measureTemperature() {
        mAnimator = new Animator();
        mAnimator.start();
    }


    private class Animator implements Runnable {
        private Scroller mScroller;
        private final static int ANIM_START_DELAY = 1000;
        private final static int ANIM_DURATION = 4000;
        private boolean mRestartAnimation = false;

        public Animator() {
            mScroller = new Scroller(getContext(), new AccelerateDecelerateInterpolator());
        }

        public void run() {
            if (mAnimator != this)
                return;

            if (mRestartAnimation) {
                int startY = (int) (mStartCenterY - (float) (0.875 * mInnerRadius));
                int dy = (int) (mEndCenterY + mInnerRadius);
                mScroller.startScroll(0, startY, 0, dy, ANIM_DURATION);
                mRestartAnimation = false;
            }

            boolean isScrolling = mScroller.computeScrollOffset();
            mIncrementalTempValue = mScroller.getCurrY();

            if (isScrolling) {
                invalidate();
                post(this);
            } else {
                stop();
            }


        }

        public void start() {
            mRestartAnimation = true;
            postDelayed(this, ANIM_START_DELAY);
        }

        public void stop() {
            removeCallbacks(this);
            mAnimator = null;
        }

    }


    private void stopMeasurement() {
        if (mAnimator != null)
            mAnimator.stop();
    }


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

        measureTemperature();

    }

    @Override
    protected void onDetachedFromWindow() {
        stopMeasurement();

        super.onDetachedFromWindow();
    }

    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);
        switch (visibility) {
            case View.VISIBLE:

                measureTemperature();

                break;

            default:

                stopMeasurement();

                break;
        }
    }


}

attrs.xml file

    <?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="Thermometer">

        <attr name="therm_color" format="color" />

    </declare-styleable>

</resources>
like image 112
Kofi Gyan Avatar answered Feb 15 '23 11:02

Kofi Gyan


First I would provide 2 setters, one for color and one for the temperature value, normalized from 0 ... 1, where 0 means no visible bar, and 1 means a fully visible bar.

public void setColor(int color) {
    mColor = color;
    invalidate(); // important, this triggers onDraw
}

public void setValue(float value) {
    mValue = -(value - 1);
    invalidate(); // important, this triggers onDraw
}

Notice for value, I reverse the value, since we draw the bar from bottom up, instead from top down. It makes sense in the canvas.drawRect method.

If your CustomView may have custom sizes, set your size of the progressBar (I refer to the inner bar as progressBar) in onSizeChanged, as this gets called when the View has changed it's size. If it is a fixed size, you can just provide those values statically in an init function or the constructor.

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    mProgressRect = new Rect(
    /*your bar left offset relative to base bitmap*/,
    /*your bar top offset relative to base bitmap*/,
    /*your bar total width*/,
    /*your max bar height*/
    );
}

Then in ondraw, take these values into account and draw accordingly.

  • First draw the Bitmap, depending on your selected color (I would provide the thermometer base as a Bitmap, as long as it does not have to be completely dynamically drawn (special requirements)
  • Then draw the progress bar, with an height based on mValue * totalHeight of the bar, using the color provided in the setter.

For example:

@Override
protected void onDraw(Canvas canvas) {
    // draw your thermometer base, bitmap based on color value
    canvas.drawBitmap( /*your base thermometer bitmap here*/ );

    // draw the "progress"
    canvas.drawRect(mProgressRect.left, mProgressRect.top + (mValue * mProgressRect.bottom - mProgressRect.top), mProgressRect.right, mProgressRect.bottom, mPaint);

}

Hope that helps.

P.S.: If you want to have the thermometer base image also dynamically drawn, it's a slightly different story, it would involve creating a path first and draw it with a Paint object, instead of drawing the bitmap.

EDIT:

Even better, if you want a simple solution for the "roundness" of the bar, draw a line instead a rect.

Define a line paint object like this:

    mPaint = new Paint();
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mPaint.setStrokeWidth(20); // thickness of your bar

then in onDraw, instead drawRect:

    // draw the "progress"
    canvas.drawLine(mProgressRect.left, mProgressRect.top + (mValue * mProgressRect.bottom - mProgressRect.top), mProgressRect.left, mProgressRect.bottom, mPaint);

Be sure to adjust your mProgressRectaccordingly.

like image 32
Steffen Funke Avatar answered Feb 15 '23 09:02

Steffen Funke