尽管使用 drawColor() 仍显示自定义 canvas 工件

Custom canvas artifacts being shown despite using drawColor()

我创建了一个绘制千分表的自定义视图。如果我为针的角度设置一个静态值,则仪表会按预期绘制(请参见下面的第一张图片)。如果我尝试通过 runOnUiThread 设置针的角度,则会出现奇怪的针伪影(参见第二张图片)。

每次调用 onDraw() 时,我都会调用 canvas.drawColor(color.BLACK),所以我不确定工件是如何被继承的。我最好的猜测是我没有在调用 runOnUiThread() 的方法中正确处理线程。

DialGaugeView class:

package net.dynu.kubie.redneksldhlr;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

public class DialGaugeView extends View {

private int height, width, min = 0;
private int numberFontSize, titleFontSize = 0;
private float dialEdgeStroke, dialEdgeRadius = 0;
private float dialFaceRadius = 0;
private float tickMajorStroke, tickSweep, tickAngleStart, tickAngleEnd, tickEdgeRadius, tickInnerMajorRadius, tickInnerMinorRadius = 0;
private int tickMajorCount = 0;
private float tickMinorStroke = 0;
private int tickMinorCount = 0;
private int tickTotalCount = 0;
private int numberMin, numberMax = 0;
private float numberRadius = 0;
private float arrowTipRadius, arrowRearRadius = 0;
private float arrowCenterRadius, arrowPinRadius = 0;
private float sensorInput = 0;
private float temp = 0;
private Paint paint;
private boolean isInit;
private float titleRadius = 0;
private Rect rect = new Rect();
private Path path = new Path();
private String titleStr = "";

//Variables common to drawing functions
private float center_x, center_y = 0;
private float x1, x2, x3, y1, y2, y3 = 0;
private float angle = 0;
private int number = 0;
private String str = "";


public DialGaugeView(Context context) {
    super(context);
}

public DialGaugeView(Context context, AttributeSet attrs) {
    super(context, attrs);
    TypedArray a = context.getTheme().obtainStyledAttributes(
            attrs,
            R.styleable.DialGaugeView,
            0, 0);

    try {
        tickMajorCount = a.getInteger(R.styleable.DialGaugeView_tickCountMajor, 5);
        tickMinorCount = a.getInteger(R.styleable.DialGaugeView_tickCountMinor, 0);
        tickSweep = a.getFloat(R.styleable.DialGaugeView_tickSweep, 270) / 2;
        numberMin = a.getInteger(R.styleable.DialGaugeView_displayMin, 0);
        numberMax = a.getInteger(R.styleable.DialGaugeView_displayMax, 1);
        titleStr = a.getString(R.styleable.DialGaugeView_displayTitle);
    } finally {
        a.recycle();
    }
}

public DialGaugeView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

public int getTickMajorCount() {
    return tickMajorCount;
}

public void setTickMajorCount(int i) {
    tickMajorCount = i;
    isInit = false;
    invalidate();
    requestLayout();
}

public int getTickMinorCount() {
    return tickMinorCount;
}

public void setTickMinorCount(int i) {
    tickMinorCount = i;
    isInit = false;
    invalidate();
    requestLayout();
}

public float getTickSweep() {
    return tickSweep;
}

public void setTickSweep(float i) {
    tickSweep = i / 2;
    isInit = false;
    invalidate();
    requestLayout();
}

public int getDisplayMin() {
    return numberMin;
}

public void setDisplayMin(int i) {
    numberMin = i;
    isInit = false;
    invalidate();
    requestLayout();
}

public int getDisplayMax() {
    return numberMax;
}

public void setDisplayMax(int i) {
    numberMax = i;
    isInit = false;
    invalidate();
    requestLayout();
}

public float getSensorInput() {
    return sensorInput;
}

public void setSensorInput(float i) {
    sensorInput = i;
    isInit = false;
    invalidate();
    requestLayout();
}

public String getTitle() {
    return titleStr;
}

public void setTitle(String i) {
    titleStr = i;
    invalidate();
    requestLayout();
}

private void initGauge() {
    //Common variables
    height = getHeight();
    width = getWidth();
    min = Math.min(height, width);
    center_x = width / 2;
    center_y = height / 2;
    paint = new Paint();
    isInit = true;

    //Dial face variables
    dialEdgeStroke = min / 20;
    dialEdgeRadius = min / 2 - dialEdgeStroke / 2;
    dialFaceRadius = dialEdgeRadius - dialEdgeStroke / 2;

    //Tick variables
    //tickMajorCount = 7; //TODO - Class input needed
    //tickMinorCount = 1; //TODO - Class input needed
    //tickSweep = 270 / 2; //TODO - Class input needed //Degrees +/- from 90 degrees
    tickEdgeRadius = dialFaceRadius - dialEdgeStroke / 2;
    tickInnerMajorRadius = (float) (tickEdgeRadius - dialEdgeStroke * 1.5);
    tickInnerMinorRadius = ((tickEdgeRadius + tickInnerMajorRadius) / 2);
    tickMajorStroke = min / 75;
    tickAngleStart = 90 + tickSweep;
    tickAngleEnd = 90 - tickSweep;
    tickMinorStroke = tickMajorStroke / 2;
    tickTotalCount = tickMajorCount + (tickMajorCount - 1) * tickMinorCount;

    //Numeral variables
    //numberMin = 0; //TODO - Class input needed
    //numberMax = 120; //TODO - Class input needed
    numberRadius = (tickInnerMajorRadius); // - (tickEdgeRadius - tickInnerMajorRadius) * 1.25);
    numberFontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, min / 40,
            getResources().getDisplayMetrics());

    //Title variables
    titleRadius = tickInnerMajorRadius;
    titleFontSize = numberFontSize;

    //Arrow variables
    //sensorInput = (float) (numberMax * .4965); //TODO - Class input needed
    arrowTipRadius = tickEdgeRadius;
    arrowRearRadius = arrowTipRadius / 2;
    arrowCenterRadius = (arrowTipRadius * 1 / 8);
    arrowPinRadius = (arrowTipRadius * 1 / 24);




}

@Override
protected void onDraw(Canvas canvas) {
    if (!isInit) {
        initGauge();
    }

    canvas.drawColor(Color.BLACK);

    drawDialFace(canvas);
    drawTicks(canvas);
    drawNumeral(canvas);
    drawTitle(canvas);
    drawArrow(canvas);

    postInvalidateDelayed(500);
    invalidate();
    requestLayout();
}

private void drawDialFace(Canvas canvas) {
    paint.reset();
    paint.setColor(getResources().getColor(android.R.color.black));
    paint.setStrokeWidth(dialEdgeStroke);
    paint.setStyle(Paint.Style.STROKE);
    paint.setAntiAlias(true);
    canvas.drawCircle(center_x, center_y, dialEdgeRadius, paint);

    paint.reset();
    paint.setColor(getResources().getColor(android.R.color.white));
    paint.setStyle(Paint.Style.FILL);
    paint.setAntiAlias(true);
    canvas.drawCircle(center_x, center_y, dialFaceRadius, paint);
}

private void drawTicks(Canvas canvas) {
    paint.reset();
    paint.setColor(getResources().getColor(android.R.color.black));
    paint.setStyle(Paint.Style.STROKE);
    paint.setAntiAlias(true);

    for(int i = 0; i < tickTotalCount; i++) {
        angle = (float) (((tickAngleEnd - tickAngleStart) * i / (tickTotalCount - 1) + tickAngleStart) / 180 * Math.PI);
        x1 = (float) (center_x + Math.cos(angle) * tickEdgeRadius);
        y1 = (float) (center_y - Math.sin(angle) * tickEdgeRadius);
        if((i % (tickMinorCount + 1)) == 0) {
            paint.setStrokeWidth(tickMajorStroke);
            x2 = (float) (center_x + Math.cos(angle) * tickInnerMajorRadius);
            y2 = (float) (center_y - Math.sin(angle) * tickInnerMajorRadius);
        } else {
            paint.setStrokeWidth(tickMinorStroke);
            x2 = (float) (center_x + Math.cos(angle) * tickInnerMinorRadius);
            y2 = (float) (center_y - Math.sin(angle) * tickInnerMinorRadius);
        }
        canvas.drawLine(x1, y1, x2, y2, paint);
    }
}

private void drawNumeral(Canvas canvas) {
    paint.reset();
    paint.setTextSize(numberFontSize);
    paint.setColor(getResources().getColor(android.R.color.black));

    for(int i = 0; i < tickMajorCount; i++) {
        angle = (float) (((tickAngleEnd - tickAngleStart) * i / (tickMajorCount - 1) + tickAngleStart) / 180 * Math.PI);
        number = (numberMax - numberMin) * i / (tickMajorCount - 1) + numberMin;
        str = String.valueOf(number);
        paint.getTextBounds(str, 0, str.length(), rect);


        double c = Math.cos(angle);
        double s = Math.sin(angle);
        if(rect.width() * Math.abs(s) < rect.height() * Math.abs(c)) {
            x2 = (float) (Math.signum(c) * rect.width() / 2);
            y2 = (float) (Math.tan(angle) * x2);
        } else {
            y2 = (float) (Math.signum(s) * rect.height() / 2);
            x2 = (float) (1 / Math.tan(angle) * y2);//Math.cotg(angle) * y;
        }





        x1 = (float) (center_x + Math.cos(angle) * numberRadius - rect.width() / 2 - x2 * 1.25);
        y1 = (float) (center_y - Math.sin(angle) * numberRadius + rect.height() / 2 + y2 * 1.25);
        canvas.drawText(str, x1, y1, paint);
    }
}

private void drawTitle(Canvas canvas) {
    paint.reset();
    paint.setTextSize(titleFontSize);
    paint.setColor(getResources().getColor(android.R.color.black));
    angle = (float) ((270.0 / 180) * Math.PI);
    paint.getTextBounds(titleStr, 0, str.length(), rect);
    x1 = (float) (center_x + Math.cos(angle) * titleRadius - rect.width() / 2);
    y1 = (float) (center_y - Math.sin(angle) * titleRadius + rect.height() / 3);
    canvas.drawText(titleStr, x1, y1, paint);
}

private void drawArrow(Canvas canvas) {
    paint.reset();
    paint.setColor(getResources().getColor(android.R.color.holo_red_dark));
    paint.setStyle(Paint.Style.FILL);
    paint.setAntiAlias(true);

    //Calculate (x,y) coordinates for the arrow path.
    temp = (tickAngleEnd - tickAngleStart) * sensorInput / numberMax + tickAngleStart;
    angle = (float) (temp / 180 * Math.PI);
    x1 = (float) (center_x + Math.cos(angle) * arrowTipRadius);
    y1 = (float) (center_y - Math.sin(angle) * arrowTipRadius);
    angle = (float) ((temp + 170) / 180 * Math.PI);
    x2 = (float) (center_x + Math.cos(angle) * arrowRearRadius);
    y2 = (float) (center_y - Math.sin(angle) * arrowRearRadius);
    angle = (float) ((temp - 170) / 180 * Math.PI);
    x3 = (float) (center_x + Math.cos(angle) * arrowRearRadius);
    y3 = (float) (center_y - Math.sin(angle) * arrowRearRadius);

    //Draw arrow path using calculated coordinates.
    path.moveTo(x1, y1);
    path.lineTo(x2, y2);
    path.lineTo(x3, y3);
    path.close();
    canvas.drawPath(path, paint);

    //Calculate (x,y) coordinates for dial center.
    x1 = center_x;
    y1 = center_y;

    canvas.drawCircle(x1, y1, arrowCenterRadius, paint);

    paint.setColor(getResources().getColor(android.R.color.darker_gray));
    canvas.drawCircle(x1, y1, arrowPinRadius, paint);

}
}

我的主要活动:

package net.dynu.kubie.redneksldhlr;

import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import org.w3c.dom.Text;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    hideSystemUI();

    startGenerating();
}

@Override
protected void onResume() {
    super.onResume();

    hideSystemUI();
    //demoData();
}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        hideSystemUI();
        //demoData();
    }
}
private void hideSystemUI() {
    // Enables regular immersive mode.
    // For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
    // Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
    View decorView = getWindow().getDecorView();
    decorView.setSystemUiVisibility(
            View.SYSTEM_UI_FLAG_IMMERSIVE
                    // Set the content to appear under the system bars so that the
                    // content doesn't resize when the system bars hide and show.
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    // Hide the nav bar and status bar
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_FULLSCREEN);
}


private void startGenerating() {
    DoSomethingThread randomWork = new DoSomethingThread();
    randomWork.start();
}


public class DoSomethingThread extends Thread {
    private static final String TAG = "DoSomethingThread";
    private static final int DELAY = 15000; // 5 seconds
    private static final int RANDOM_MULTIPLIER = 120;

    @Override
    public void run() {
        //Log.v(TAG, "doing work in Random Number Thread");
        while (true) {
            float randNum = (float) (Math.random() * RANDOM_MULTIPLIER);
            // need to publish the random number back on the UI at this point in the code through the publishProgress(randNum) call
            publishProgress(randNum);
            try {
                Thread.sleep(DELAY);
            } catch (InterruptedException e) {
               // Log.v(TAG, "Interrupting and stopping the Random Number Thread");
                return;
            }
        }
    }
}



private void publishProgress(float randNum) {
    //Log.v(TAG, "reporting back from the Random Number Thread");
    //final String text = String.format(getString(R.string.service_msg), randNum);
    //final String text = Integer.toString(randNum);
    final float text = randNum;
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            updateResults(text);
        }
    });
}


public void updateResults(float results) {
    //TextView myTextView = (TextView) findViewById(R.id.testTextView);
    DialGaugeView myDial = findViewById(R.id.my_gauge);
    myDial.setSensorInput(results);
}


}

我最终意识到我的问题根本不是人工制品。我的箭头被绘制为一系列路径。如果我在为新箭头添加坐标之前不重置路径,则之前的箭头只会被拖走。添加 path.reset();在添加第一个箭头坐标之前解决了我的问题。

//Draw arrow path using calculated coordinates.
path.reset();
path.moveTo(x1, y1);
path.moveTo(x2, y2);
path.moveTo(x3, y3);
path.close();
canvas.drawPath(path, paint);