如何在Android中删除自制画布视图中不需要的填充

kcwpcxri  于 2023-05-05  发布在  Android
关注(0)|答案(1)|浏览(140)

我在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中的自制画布视图无法正确显示(更多),我尝试相应地更改onSizeChangedonMeasure方法,看起来像这样:

@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>
8i9zcol2

8i9zcol21#

我将在这里解释我的思考过程,这样如果你再次遇到这样的情况,你就可以参考一些你可以采取的步骤来解决问题。
这与您的热水箱问题非常相似,但有一些重要的区别。最大的区别是图像本身。这是你的温度计图像(在Photoshop中打开以清楚地看到背景)-注意温度计周围的填充:

由于此填充是图像本身的一部分,因此我们无法在Android Studio中减少图像边缘以外的填充(实际上我们可以,但只修改图像更容易)。这是第一步:裁剪图像以去除多余的填充。我在这里这样做了:

请注意,右侧仍有一些空间,以便温度计主体直接位于图像的中心。
接下来是温度计类本身的问题。修改onSizeChangedonMeasure方法是正确的,但它们不可能与水箱的方法完全相同-我们有不同的最终目标。在水箱类中,我们只是试图创建一个矩形来表示水箱中的水,但在这里,我们试图创建一个圆形和一个矩形来表示温度计中的流体。让我们从// init bitmap开始,在thermometer类中查看原始的onSizeChanged方法:

// 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);

我很难理解这段代码的意义。看起来我们得到了位图的宽度和高度,然后将每边乘以0.9,并将位图的尺寸设置为结果。我们现在先不修改尺寸,如果需要的话,我们可以稍后再回来。这段代码可以替换为一行:

bitmap = Bitmap.createScaledBitmap(bitmap,  getWidth(), getHeight(), 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);

一行接一行:
mInnerRadius = bitmap.getWidth() / 8;设置温度计底部红色圆圈的半径。半径将是位图的宽度除以8。请注意,因为我们将使用裁剪的温度计,所以我们将不得不使用这个数字来找到一个与新图像匹配的半径。
mInnerCirclePaint.setStrokeWidth((int)(bitmap.getWidth() / 10));设置温度计液体矩形部分的笔划宽度。和上面一样,我们必须修改它以适应我们的新图像。
接下来的三行,分别为lefttopinnerCircleCenter赋值,看起来对图像的生成没有影响,所以我们把它们去掉。同样,如果我们发现有什么东西坏了,我们可以稍后再回到这些。
circleHeight = (top + bitmap.getHeight()) - (int)(bitmap.getHeight() / 4.6f);确定在位图上的哪个位置绘制红色圆圈。像上面一样,我们必须修改这个值。
lineStartYlineEndY赋值的最后两行确定了红色矩形开始和结束的高度。我们也会修改这些。
下面是我们新的onSizeChanged方法,其中有新的值来适应我们新裁剪的温度计图像。您可以随意使用这些值,看看修改它们会如何改变红色矩形/圆形的大小和位置。

@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);
    }

onMeasure方法除了前两行之外都很好。这些需要匹配您正在使用的图像的纵横比。在你的上一个问题中,我使用了热水箱图像的尺寸,但尺寸和温度计的宽高比是不同的:

int desiredWidth = 558;
        int desiredHeight = 730;

最后,正如前一篇文章中提到的,你不应该同时指定一个layout_constraintHeight_percent和一个layout_constraintWidth_percent,因为这样做会扭曲你的图像。去掉其中一个约束,将layout_widthlayout_height设置为wrap_content。更新的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"/>

总而言之:首先,裁剪图像中多余的填充(或使用我上面裁剪的图像)。然后,修改onSizeChanged以考虑新图像中的大小差异。最后,编辑onMeasure中的尺寸以匹配新图像。
结果:

相关问题