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:
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:
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
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:
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?
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:
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:
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:
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);
}
}