Search code examples
javaandroidcanvasandroid-view

How to remove unwanted padding from self made canvas view in Android


I have a custom made Canvas object in Android (thermometer) that unfortunately has a padding around it that I want to get rid of as it makes it quite difficult to position it within the layout. Here is how it looks like:

enter image description here

Here is the code of the Thermoeter Java class:

package com.example.game;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class Thermometer extends View {
    private Paint mInnerCirclePaint;
    private int mInnerRadius;
    private int mThermometerColor = Color.RED;
    private Bitmap bitmap;
    private int left;
    private int top;
    private int innerCircleCenter;
    private int circleHeight;
    private int lineEndY;
    private int lineStartY;
    double positionOfTemperatureBar = 0.2;

    //0.378= 20°C, 0.2 = 21 °C, 0.022 = 22°C, 0.41 = lower limit, -0.03 = upper limit
    final double value_positionOfTemperatureBar_20Degrees = 0.378;
    final double value_positionOfTemperatureBar_22Degrees = 0.022;
    final double value_positionOfTemperatureBar_upperLimit = -0.03 ;
    final double value_positionOfTemperatureBar_lowerLimit = 0.41;




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

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

    public Thermometer(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);

        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.thermometer_container);
    }

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


        // init bitmap
        int scaledHeight;
        int scaledWidth;
        int width = getWidth();
        int height = getHeight();
        if (width > height) {
            scaledHeight = (int) (height * 0.90);
            scaledWidth = scaledHeight * bitmap.getWidth() / bitmap.getHeight();
        } else {
            scaledWidth = (int) (width * 0.90);
            scaledHeight = scaledWidth * bitmap.getHeight() / bitmap.getWidth();
        }

        bitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);

        mInnerRadius = bitmap.getWidth() / 8;

        mInnerCirclePaint.setStrokeWidth((int)(bitmap.getWidth() / 10));
        left = (getWidth() - bitmap.getWidth()) / 2;
        top = (getHeight() - bitmap.getHeight()) / 2;
        innerCircleCenter = (left + left + bitmap.getWidth() + (Math.min(width, height) / 72)) / 2;
        circleHeight = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4.6f);


        lineStartY = ((int)(bitmap.getHeight() / 4.6f) + top) + (int) (positionOfTemperatureBar * bitmap.getHeight());
        lineEndY = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4f);

    }

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

        drawThermometer(canvas);
    }


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

        //takes 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 drawThermometer(Canvas canvas) {
        canvas.drawCircle(innerCircleCenter, circleHeight, mInnerRadius, mInnerCirclePaint);
        canvas.drawLine(innerCircleCenter, lineStartY, innerCircleCenter, lineEndY, mInnerCirclePaint);
        canvas.drawBitmap(bitmap, left, top, new Paint());
    }

    public void setThermometerColor(int thermometerColor) {
        this.mThermometerColor = thermometerColor;
        mInnerCirclePaint.setColor(mThermometerColor);
        invalidate();
    }

    public void changeTemperature( double percentageChangeOfTheWholeBar) {
        double appliedPercentageChangeOfTheWholeBar = percentageChangeOfTheWholeBar / 100;
        if (appliedPercentageChangeOfTheWholeBar>1) {
            appliedPercentageChangeOfTheWholeBar = 1;
        }
        if (appliedPercentageChangeOfTheWholeBar <-1) {
            appliedPercentageChangeOfTheWholeBar = -1;
        }

        double absolutValueSpanForTheWholeBar = value_positionOfTemperatureBar_22Degrees - value_positionOfTemperatureBar_20Degrees;

        positionOfTemperatureBar = positionOfTemperatureBar + appliedPercentageChangeOfTheWholeBar * absolutValueSpanForTheWholeBar;
        if (positionOfTemperatureBar < value_positionOfTemperatureBar_upperLimit) {
            positionOfTemperatureBar = value_positionOfTemperatureBar_upperLimit;
        }

        if(positionOfTemperatureBar > value_positionOfTemperatureBar_lowerLimit) {
            positionOfTemperatureBar = value_positionOfTemperatureBar_lowerLimit;
        }


        lineStartY = ((int)(bitmap.getHeight() / 4.6f) + top) + (int) (positionOfTemperatureBar * bitmap.getHeight());
        lineEndY = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4f);
    }

    public void setTemperature( double setTemperatureDegreesCelsius) {
        double appliedSetTemperature = setTemperatureDegreesCelsius;
        if (appliedSetTemperature < 20) {
            appliedSetTemperature = 20;
        }
        if (appliedSetTemperature >22) {
            appliedSetTemperature = 22;
        }

        double absolutValueSpanForTheWholeBar = value_positionOfTemperatureBar_22Degrees - value_positionOfTemperatureBar_20Degrees;

        positionOfTemperatureBar = value_positionOfTemperatureBar_20Degrees + ((setTemperatureDegreesCelsius - 20 )/2)  * absolutValueSpanForTheWholeBar;



        lineStartY = ((int)(bitmap.getHeight() / 4.6f) + top) + (int) (positionOfTemperatureBar * bitmap.getHeight());
        lineEndY = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4f);
    }


}

And here is the code of the XML layout file that contains the thermometer object:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/constraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    tools:context=".MainActivity">


    <com.example.game.Thermometer
        android:id="@+id/thermometer"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.3"
        app:layout_constraintHorizontal_bias="0.529"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.795"
        app:layout_constraintWidth_percent="0.15" />

    <Button
        android:id="@+id/button_action"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="Action"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.102"
        app:layout_constraintHorizontal_bias="0.373"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.745"
        app:layout_constraintWidth_percent="0.13" />

</androidx.constraintlayout.widget.ConstraintLayout>

Following the answer to this question Self-made canvas view in Android is not correctly displayed (any more) I tried to change the onSizeChanged and onMeasure method accordingly which looked like this:

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

    // init bitmap

    int width = getWidth();
    int height = getHeight();

    mInnerCirclePaint.setStrokeWidth( (float) (width * 0.9));

    bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);

    //innerCircleCenter = (left + left + bitmap.getWidth() + (Math.min(width, height) / 72));
    innerCircleCenter = getWidth() / 2;

    left = (getWidth() - bitmap.getWidth()) / 2;
    top = (getHeight() - bitmap.getHeight()) / 2;


    lineStartY = ((int)(bitmap.getHeight() / 4.6f) + top) + (int) (positionOfTemperatureBar * bitmap.getHeight());
    lineEndY = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 7f);

}

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

    drawThermometer(canvas);
}


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

    // the actual dimensions of your water tank image
    // these are just here to set the aspect ratio and the 'max' dimensions (we will make it smaller in the xml)
    int desiredWidth = 421;
    int desiredHeight = 693;

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

    int width;
    int height;

    //Measure Height
    if (heightMode == MeasureSpec.EXACTLY) {
        //Must be this size
        height = heightSize;
    } else if (heightMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        height = Math.min(desiredHeight, heightSize);
    } else {
        //Be whatever you want
        height = desiredHeight;
    }

    //Measure Width
    if (widthMode == MeasureSpec.EXACTLY) {
        //Must be this size
        width = widthSize;
    } else if (widthMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        width = heightSize * desiredWidth / desiredHeight;
    } else {
        //Be whatever you want
        width = desiredWidth;
    }

    setMeasuredDimension(width, height);
}

But the result looked like this: enter image description here

This is obviously not what I want because the temperature bar is way to big (altough the padding disappeared). For me it is extremely difficult to design such self-made canvas objects in Android as I don't know what to adjust in order to make them look properly. Do you have any idea what I can do in order to have a proper temperature bar in the self-made canvas object while having no padding? How do you approach such kind of problems?

Update: Here is the R.drawable.thermometer_container enter image description here

And here is the R.styleable.Thermometer

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Thermometer">
        <attr name="therm_color" format="color" />
    </declare-styleable>
</resources>

Update: I tried the approach suggested by Rob but unfortunately there are 3 problems with it as you can see in the screenshot: enter image description here

  1. I can't place the view precisely as the blue area aroung the view wraps horizontally to the boundaries which is not what I want
  2. Layout Editor: The red circle inside the thermometer is too small and the whole red bar is too much on the right and not in the center.
  3. In the Emulator the custom view looks extremely bad as you can see on the screenshotenter image description here

Here is the updated code of the Thermometer.class

package com.example.game;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class Thermometer extends View {
    private Paint mInnerCirclePaint;
    private int mInnerRadius;
    private int mThermometerColor = Color.RED;
    private Bitmap bitmap;
    private int left;
    private int top;
    private int innerCircleCenter;
    private int circleHeight;
    private int lineEndY;
    private int lineStartY;
    double positionOfTemperatureBar = 0.2;

    //0.378= 20°C, 0.2 = 21 °C, 0.022 = 22°C, 0.41 = lower limit, -0.03 = upper limit
    final double value_positionOfTemperatureBar_20Degrees = 0.378;
    final double value_positionOfTemperatureBar_22Degrees = 0.022;
    final double value_positionOfTemperatureBar_upperLimit = -0.03 ;
    final double value_positionOfTemperatureBar_lowerLimit = 0.41;




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

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

    public Thermometer(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);

        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.thermometer_container);
    }

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

        bitmap = Bitmap.createScaledBitmap(bitmap,  getWidth(), getHeight(), true);

        mInnerRadius = bitmap.getWidth() / 5;
        innerCircleCenter = getWidth()/ 2;
        circleHeight = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 5f);

        mInnerCirclePaint.setStrokeWidth((int)(bitmap.getWidth() / 7));
        lineStartY = ((int)(bitmap.getHeight() / 6.4f) + top) + (int) (positionOfTemperatureBar * bitmap.getHeight());
        lineEndY = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 2f);
    }

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

        drawThermometer(canvas);
    }


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

        int desiredWidth = 558;
        int desiredHeight = 730;

        //takes 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(desiredWidth, desiredHeight);
        //setMeasuredDimension(width, height);
    }

    private void drawThermometer(Canvas canvas) {
        canvas.drawCircle(innerCircleCenter, circleHeight, mInnerRadius, mInnerCirclePaint);
        canvas.drawLine(innerCircleCenter, lineStartY, innerCircleCenter, lineEndY, mInnerCirclePaint);
        canvas.drawBitmap(bitmap, left, top, new Paint());
    }

    public void setThermometerColor(int thermometerColor) {
        this.mThermometerColor = thermometerColor;
        mInnerCirclePaint.setColor(mThermometerColor);
        invalidate();
    }

    public void changeTemperature( double percentageChangeOfTheWholeBar) {
        double appliedPercentageChangeOfTheWholeBar = percentageChangeOfTheWholeBar / 100;
        if (appliedPercentageChangeOfTheWholeBar>1) {
            appliedPercentageChangeOfTheWholeBar = 1;
        }
        if (appliedPercentageChangeOfTheWholeBar <-1) {
            appliedPercentageChangeOfTheWholeBar = -1;
        }

        double absolutValueSpanForTheWholeBar = value_positionOfTemperatureBar_22Degrees - value_positionOfTemperatureBar_20Degrees;

        positionOfTemperatureBar = positionOfTemperatureBar + appliedPercentageChangeOfTheWholeBar * absolutValueSpanForTheWholeBar;
        if (positionOfTemperatureBar < value_positionOfTemperatureBar_upperLimit) {
            positionOfTemperatureBar = value_positionOfTemperatureBar_upperLimit;
        }

        if(positionOfTemperatureBar > value_positionOfTemperatureBar_lowerLimit) {
            positionOfTemperatureBar = value_positionOfTemperatureBar_lowerLimit;
        }


        lineStartY = ((int)(bitmap.getHeight() / 4.6f) + top) + (int) (positionOfTemperatureBar * bitmap.getHeight());
        lineEndY = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4f);
    }

    public void setTemperature( double setTemperatureDegreesCelsius) {
        double appliedSetTemperature = setTemperatureDegreesCelsius;
        if (appliedSetTemperature < 20) {
            appliedSetTemperature = 20;
        }
        if (appliedSetTemperature >22) {
            appliedSetTemperature = 22;
        }

        double absolutValueSpanForTheWholeBar = value_positionOfTemperatureBar_22Degrees - value_positionOfTemperatureBar_20Degrees;

        positionOfTemperatureBar = value_positionOfTemperatureBar_20Degrees + ((setTemperatureDegreesCelsius - 20 )/2)  * absolutValueSpanForTheWholeBar;



        lineStartY = ((int)(bitmap.getHeight() / 4.6f) + top) + (int) (positionOfTemperatureBar * bitmap.getHeight());
        lineEndY = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4f);
    }


}

And here the XML layout file:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/constraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    tools:context=".MainActivity">

    <View
        android:id="@+id/imageView_TargetRectangle"
        android:layout_width="0dp"
        android:layout_height="0dp"


        android:background="@drawable/rectangle"
        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.16"
        app:layout_constraintHorizontal_bias="0.535"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.01"
        app:layout_constraintWidth_percent="0.10" />


    <com.example.game.Thermometer
        android:id="@+id/thermometer"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.3"
        app:layout_constraintHorizontal_bias="0.529"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.795"/>

    <Button
        android:id="@+id/button_action"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="Action"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.102"
        app:layout_constraintHorizontal_bias="0.343"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.745"
        app:layout_constraintWidth_percent="0.13" />

</androidx.constraintlayout.widget.ConstraintLayout>

Do you know what to do in order to solve these issues?


Solution

  • I'll try to explain my thought process here, so if you encounter a situation like this again you'll have a reference on some steps you can take to solve the problem.

    This is very similar to your hot water tank question, but with some important differences. The major difference is the image itself. This is your thermometer image (opened in Photoshop to clearly see the background)- notice the padding around the thermometer:

    Original thermometer image

    Because this padding is part of the image itself, we can't reduce the padding in Android Studio beyond the edges of the image (actually we can, but it's easier to just modify the image). So that would be the first step: crop the image to get rid of the excess padding. I have done that here:

    enter image description here

    Note that there is still some space on the right side so that the thermometer body is directly in the center of the image.

    Next, the issues with the thermometer class itself. You were on the right track modifying the onSizeChanged and the onMeasure methods, but they can't be exactly the same as they were for the water tank - we have different end goals here. In the water tank class, we were just trying to create a rectangle to represent the water inside the tank, but here we're trying to create a circle and a rectangle to represent the fluid inside the thermometer. Let's go through your original onSizeChanged method in the thermometer class, starting with // init bitmap:

            // init bitmap
            int scaledHeight;
            int scaledWidth;
            int width = getWidth();
            int height = getHeight();
            if (width > height) {
                scaledHeight = (int) (height * 0.90);
                scaledWidth = scaledHeight * bitmap.getWidth() / bitmap.getHeight();
            } else {
                scaledWidth = (int) (width * 0.90);
                scaledHeight = scaledWidth * bitmap.getHeight() / bitmap.getWidth();
            }
    
            bitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
    

    It's hard for me to understand the point of this code. It seems like we're getting the width and the height of the bitmap, then multiplying each side by 0.9 and setting the bitmap's dimensions to the result. Let's just not modify the dimensions for now, and we can come back to it later if we need to. This code can be replaced by the single line:

    bitmap = Bitmap.createScaledBitmap(bitmap,  getWidth(), getHeight(), true);
    

    Next, we have the code:

            mInnerRadius = bitmap.getWidth() / 8;
    
            mInnerCirclePaint.setStrokeWidth((int)(bitmap.getWidth() / 10));
            left = (getWidth() - bitmap.getWidth()) / 2;
            top = (getHeight() - bitmap.getHeight()) / 2;
            innerCircleCenter = (left + left + bitmap.getWidth() + (Math.min(width, height) / 72)) / 2;
            circleHeight = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4.6f);
    
    
            lineStartY = ((int)(bitmap.getHeight() / 4.6f) + top) + (int) (positionOfTemperatureBar * bitmap.getHeight());
            lineEndY = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4f);
    

    Line by line:

    mInnerRadius = bitmap.getWidth() / 8; sets the radius of the red circle at the bottom of the thermometer. The radius will be the width of the bitmap divided by 8. Note that because we're going to use the cropped thermometer, we're going to have to play around with this number to find a radius that looks good with our new image.

    mInnerCirclePaint.setStrokeWidth((int)(bitmap.getWidth() / 10)); sets the stroke width of the rectangular part of the thermometer's fluid. Same as above, we have to modify this to fit our new image.

    The next three lines, assigning values to left, top, and innerCircleCenter, seem to have no effect on the generation of the image, so let's just get rid of them. Again, we can come back to these later if we find out something breaks.

    circleHeight = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4.6f); determines where on the bitmap to draw the red circle. Like above, we'll have to modify this value.

    The last two lines assigning values to lineStartY and lineEndY determine the height at which the red rectangle will start and end. We'll modify these as well.

    So, here is our new onSizeChanged method, with new values to fit our newly cropped thermometer image. Feel free to play around with the values to see how modifying them changes the size and position of the red rectangle/circle.

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
    
            bitmap = Bitmap.createScaledBitmap(bitmap,  getWidth(), getHeight(), true);
    
            mInnerRadius = bitmap.getWidth() / 6;
            innerCircleCenter = getWidth()/ 2;
            circleHeight = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 5f);
    
            mInnerCirclePaint.setStrokeWidth((int)(bitmap.getWidth() / 7));
            lineStartY = ((int)(bitmap.getHeight() / 6.4f) + top) + (int) (positionOfTemperatureBar * bitmap.getHeight());
            lineEndY = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 2f);
        }
    

    The onMeasure method is fine except for the first two lines. These need to match the aspect ratio of the image you're using. In your previous question, I used the dimensions of the hot water tank image, but the dimensions and the aspect ratio of the thermometer are different:

            int desiredWidth = 558;
            int desiredHeight = 730;
    

    Finally, as mentioned in the previous post, you should not be assigning both a layout_constraintHeight_percent and a layout_constraintWidth_percent, because doing so will warp your image. Get rid of one of these constraints and instead set the layout_width or layout_height to wrap_content. Updated xml:

        <com.example.test2.Thermometer
            android:id="@+id/thermometer"
            android:layout_width="wrap_content"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHeight_percent="0.3"
            app:layout_constraintHorizontal_bias="0.529"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.795"/>
    

    In sum: First, crop the excess padding in the image (or use the image I cropped above). Then, modify the onSizeChanged to account for the size differences in the new image. Finally, edit the dimensions in onMeasure to match the new image.

    Result:

    Final result

    Here is the complete code for my Thermometer.java:

    package com.example.test2;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.util.AttributeSet;
    import android.view.View;
    
    public class Thermometer extends View {
        private Paint mInnerCirclePaint;
        private int mInnerRadius;
        private int mThermometerColor = Color.RED;
        private Bitmap bitmap;
        private int left;
        private int top;
        private int innerCircleCenter;
        private int circleHeight;
        private int lineEndY;
        private int lineStartY;
        double positionOfTemperatureBar = 0.2;
    
        //0.378= 20°C, 0.2 = 21 °C, 0.022 = 22°C, 0.41 = lower limit, -0.03 = upper limit
        final double value_positionOfTemperatureBar_20Degrees = 0.378;
        final double value_positionOfTemperatureBar_22Degrees = 0.022;
        final double value_positionOfTemperatureBar_upperLimit = -0.03 ;
        final double value_positionOfTemperatureBar_lowerLimit = 0.41;
    
    
    
        public Thermometer(Context context) {
            this(context, null);
        }
    
        public Thermometer(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public Thermometer(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);
    
            bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.updated_therm);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
    
            bitmap = Bitmap.createScaledBitmap(bitmap,  getWidth(), getHeight(), true);
    
            mInnerRadius = bitmap.getWidth() / 6;
            innerCircleCenter = getWidth() / 2;
            circleHeight = (bitmap.getHeight()) - (int)(bitmap.getHeight() / 5f);
    
            mInnerCirclePaint.setStrokeWidth((int)(bitmap.getWidth() / 7));
            lineStartY = ((int)(bitmap.getHeight() / 6.4f)) + (int) (positionOfTemperatureBar * bitmap.getHeight());
            lineEndY = (bitmap.getHeight()) - (int)(bitmap.getHeight() / 4f);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            drawThermometer(canvas);
        }
    
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int desiredWidth = 558;
            int desiredHeight = 730;
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            int width;
            int height;
    
            //Measure Height
            if (heightMode == MeasureSpec.EXACTLY) {
                //Must be this size
                height = heightSize;
            } else if (heightMode == MeasureSpec.AT_MOST) {
                //Can't be bigger than...
                height = Math.min(desiredHeight, heightSize);
            } else {
                //Be whatever you want
                height = desiredHeight;
            }
    
            //Measure Width
            if (widthMode == MeasureSpec.EXACTLY) {
                //Must be this size
                width = widthSize;
            } else if (widthMode == MeasureSpec.AT_MOST) {
                //Can't be bigger than...
                width = heightSize * desiredWidth / desiredHeight;
            } else {
                //Be whatever you want
                width = desiredWidth;
            }
    
            setMeasuredDimension(width, height);
        }
    
        private void drawThermometer(Canvas canvas) {
            canvas.drawCircle(innerCircleCenter, circleHeight, mInnerRadius, mInnerCirclePaint);
            canvas.drawLine(innerCircleCenter, lineStartY, innerCircleCenter, lineEndY, mInnerCirclePaint);
            canvas.drawBitmap(bitmap, left, top, new Paint());
        }
    
        public void setThermometerColor(int thermometerColor) {
            this.mThermometerColor = thermometerColor;
            mInnerCirclePaint.setColor(mThermometerColor);
            invalidate();
        }
    
        public void changeTemperature( double percentageChangeOfTheWholeBar) {
            double appliedPercentageChangeOfTheWholeBar = percentageChangeOfTheWholeBar / 100;
            if (appliedPercentageChangeOfTheWholeBar>1) {
                appliedPercentageChangeOfTheWholeBar = 1;
            }
            if (appliedPercentageChangeOfTheWholeBar <-1) {
                appliedPercentageChangeOfTheWholeBar = -1;
            }
    
            double absolutValueSpanForTheWholeBar = value_positionOfTemperatureBar_22Degrees - value_positionOfTemperatureBar_20Degrees;
    
            positionOfTemperatureBar = positionOfTemperatureBar + appliedPercentageChangeOfTheWholeBar * absolutValueSpanForTheWholeBar;
            if (positionOfTemperatureBar < value_positionOfTemperatureBar_upperLimit) {
                positionOfTemperatureBar = value_positionOfTemperatureBar_upperLimit;
            }
    
            if(positionOfTemperatureBar > value_positionOfTemperatureBar_lowerLimit) {
                positionOfTemperatureBar = value_positionOfTemperatureBar_lowerLimit;
            }
    
    
            lineStartY = ((int)(bitmap.getHeight() / 4.6f) + top) + (int) (positionOfTemperatureBar * bitmap.getHeight());
            lineEndY = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4f);
        }
    
        public void setTemperature( double setTemperatureDegreesCelsius) {
            double appliedSetTemperature = setTemperatureDegreesCelsius;
            if (appliedSetTemperature < 20) {
                appliedSetTemperature = 20;
            }
            if (appliedSetTemperature >22) {
                appliedSetTemperature = 22;
            }
    
            double absolutValueSpanForTheWholeBar = value_positionOfTemperatureBar_22Degrees - value_positionOfTemperatureBar_20Degrees;
    
            positionOfTemperatureBar = value_positionOfTemperatureBar_20Degrees + ((setTemperatureDegreesCelsius - 20 )/2)  * absolutValueSpanForTheWholeBar;
    
    
    
            lineStartY = ((int)(bitmap.getHeight() / 4.6f) + top) + (int) (positionOfTemperatureBar * bitmap.getHeight());
            lineEndY = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4f);
        }
    }