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:
- 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
- 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.
- In the Emulator the custom view looks extremely bad as you can see on 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?