我在Android中有一个定制的Canvas对象(温度计),不幸的是它周围有一个填充物,我想摆脱它,因为它使它很难在布局中定位。它看起来像这样:
下面是Thermoeter Java类的代码:
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);
}
}
下面是包含温度计对象的XML布局文件的代码:
<?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>
根据这个问题的答案,Android中的自制画布视图无法正确显示(更多),我尝试相应地更改onSizeChanged
和onMeasure
方法,看起来像这样:
@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);
}
但结果是这样的:
这显然不是我想要的,因为温度条太大了(尽管填充物消失了)。对我来说,在Android中设计这样的自制画布对象是非常困难的,因为我不知道要调整什么才能让它们看起来合适。你有什么想法,我可以做什么,以有一个适当的温度条在自制的画布对象,而没有填充?你如何处理这类问题?
更新:这里是R.drawable.thermometer_container
这里是R.styleable.Thermometer
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Thermometer">
<attr name="therm_color" format="color" />
</declare-styleable>
</resources>
1条答案
按热度按时间8i9zcol21#
我将在这里解释我的思考过程,这样如果你再次遇到这样的情况,你就可以参考一些你可以采取的步骤来解决问题。
这与您的热水箱问题非常相似,但有一些重要的区别。最大的区别是图像本身。这是你的温度计图像(在Photoshop中打开以清楚地看到背景)-注意温度计周围的填充:
由于此填充是图像本身的一部分,因此我们无法在Android Studio中减少图像边缘以外的填充(实际上我们可以,但只修改图像更容易)。这是第一步:裁剪图像以去除多余的填充。我在这里这样做了:
请注意,右侧仍有一些空间,以便温度计主体直接位于图像的中心。
接下来是温度计类本身的问题。修改
onSizeChanged
和onMeasure
方法是正确的,但它们不可能与水箱的方法完全相同-我们有不同的最终目标。在水箱类中,我们只是试图创建一个矩形来表示水箱中的水,但在这里,我们试图创建一个圆形和一个矩形来表示温度计中的流体。让我们从// init bitmap
开始,在thermometer类中查看原始的onSizeChanged
方法:我很难理解这段代码的意义。看起来我们得到了位图的宽度和高度,然后将每边乘以0.9,并将位图的尺寸设置为结果。我们现在先不修改尺寸,如果需要的话,我们可以稍后再回来。这段代码可以替换为一行:
接下来,我们有代码:
一行接一行:
mInnerRadius = bitmap.getWidth() / 8;
设置温度计底部红色圆圈的半径。半径将是位图的宽度除以8。请注意,因为我们将使用裁剪的温度计,所以我们将不得不使用这个数字来找到一个与新图像匹配的半径。mInnerCirclePaint.setStrokeWidth((int)(bitmap.getWidth() / 10));
设置温度计液体矩形部分的笔划宽度。和上面一样,我们必须修改它以适应我们的新图像。接下来的三行,分别为
left
、top
和innerCircleCenter
赋值,看起来对图像的生成没有影响,所以我们把它们去掉。同样,如果我们发现有什么东西坏了,我们可以稍后再回到这些。circleHeight = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4.6f);
确定在位图上的哪个位置绘制红色圆圈。像上面一样,我们必须修改这个值。为
lineStartY
和lineEndY
赋值的最后两行确定了红色矩形开始和结束的高度。我们也会修改这些。下面是我们新的
onSizeChanged
方法,其中有新的值来适应我们新裁剪的温度计图像。您可以随意使用这些值,看看修改它们会如何改变红色矩形/圆形的大小和位置。onMeasure
方法除了前两行之外都很好。这些需要匹配您正在使用的图像的纵横比。在你的上一个问题中,我使用了热水箱图像的尺寸,但尺寸和温度计的宽高比是不同的:最后,正如前一篇文章中提到的,你不应该同时指定一个
layout_constraintHeight_percent
和一个layout_constraintWidth_percent
,因为这样做会扭曲你的图像。去掉其中一个约束,将layout_width
或layout_height
设置为wrap_content
。更新的xml:总而言之:首先,裁剪图像中多余的填充(或使用我上面裁剪的图像)。然后,修改
onSizeChanged
以考虑新图像中的大小差异。最后,编辑onMeasure
中的尺寸以匹配新图像。结果: