android - SurfaceView onDraw,有时在绘制之前不会刷新自己......为什么?

标签 android surfaceview ondraw

我有一个简单的 onDraw 方法,如下所示(在“SurfaceView”中),其中“startCount”将从 1 到 360,绘制实心圆。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //canvas.drawColor(0xFFEEEEEE);
    if (startCount == 360) startCount= 0;
    canvas.drawArc(mShadowBounds,
            0, startCount, true, mPiePaint);
}

完成一个循环后。它会像雷达探测器移动一样从头开始不断刷新和重绘。

奇怪的是,有时,在一个完整的周期之后,它不会刷新,因此会显示大黑圆圈。

https://youtu.be/sc56FYUqV7M中的插图,前 2 个周期是我所期望的。然而,当它完成第二个周期时,它仍然保持全黑向前移动,奇怪的是它没有删除之前的绘制。我预计它会像第二个周期那样重演。

这并不总是在第二个周期后发生。有时,人们应该在多次循环后等待一段时间,然后才发生,没有任何干扰(即不触摸设备)。很难预测它什么时候会发生。

这是什么原因呢?如何调试这个问题?

(仅供引用。我可以在“View”类中的“onDraw”上使用相同的算法。这个问题根本不会发生。)

下面附有自定义表面 View 的完整代码及其线程代码。

public class TimerSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

    private Paint mPiePaint;
    private RectF mShadowBounds;
    private float diameter;

    int startCount = 0;
    private PanelThread thread;

    public TimerSurfaceView(Context context) {
        super(context);
        init();
    }

    public TimerSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

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

    @TargetApi(VERSION_CODES.LOLLIPOP)
    public TimerSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        getHolder().addCallback(this);
        mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPiePaint.setStyle(Paint.Style.FILL);
        mPiePaint.setColor(0xff000000);

        setZOrderOnTop(true);
        getHolder().setFormat(PixelFormat.TRANSLUCENT);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        // Account for padding
        float xpad = (float)(getPaddingLeft() + getPaddingRight());
        float ypad = (float)(getPaddingTop() + getPaddingBottom());

        float ww = (float)w - xpad;
        float hh = (float)h - ypad;

        // Figure out how big we can make the pie.
        diameter = Math.min(ww, hh);
        mShadowBounds = new RectF(0, 0, diameter, diameter);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //canvas.drawColor(0xFFEEEEEE);
        if (startCount == 360) startCount= 0;
        canvas.drawArc(mShadowBounds,
                0, startCount, true, mPiePaint);
    }

    public void incrementCount() {
        startCount++;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
        int w = resolveSizeAndState(minw, widthMeasureSpec, 1);
        int h = resolveSizeAndState(MeasureSpec.getSize(w), heightMeasureSpec, 0);
        setMeasuredDimension(w, h);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        setWillNotDraw(false); //Allows us to use invalidate() to call onDraw()
        thread = new PanelThread(getHolder(), this); //Start the thread that
        thread.setRunning(true);                     //will make calls to
        thread.start();                              //onDraw()
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // tell the thread to shut down and wait for it to finish
        // this is a clean shutdown
        if (thread != null) {
            boolean retry = true;
            while (retry) {
                try {
                    thread.setRunning(false);                //Tells thread to stop
                    thread.join();                           //Removes thread from mem.
                    retry = false;
                } catch (InterruptedException e) {
                    // try again shutting down the thread
                }
            }
            thread = null;
        }
    }
}

和Thread类

class PanelThread extends Thread {
    private SurfaceHolder surfaceHolder;
    private TimerSurfaceView panel;
    private boolean startRunning = false;


    public PanelThread(SurfaceHolder surfaceHolder, TimerSurfaceView panel) {
        this.surfaceHolder = surfaceHolder;
        this.panel = panel;
    }


    public void setRunning(boolean run) { //Allow us to stop the thread
        startRunning = run;
    }


    @Override
    public void run() {
        Canvas c;
        while (startRunning) {     //When setRunning(false) occurs, startRunning is
            c = null;      //set to false and loop ends, stopping thread
            try {
                c = surfaceHolder.lockCanvas(null);
                synchronized (surfaceHolder) {
                    //Insert methods to modify positions of items in onDraw()
                    panel.incrementCount();
                    panel.postInvalidate();
                }
            } finally {
                if (c != null) {
                    surfaceHolder.unlockCanvasAndPost(c);
                }
            }

        }
    }
}

最佳答案

您混合了两种不同的绘图方法。

SurfaceView 有两部分,Surface 和 View。 View 部分通常只是一个透明的矩形,可让您“透视”View UI 层。 Surface 是一个独立的层,默认情况下位于 View UI 层后面。

通过子类化 SurfaceView 并定义 onDraw() 方法,您可以将其视为自定义 View。您正在 View 部分上绘图,但您似乎没有先删除它,因此它应该仍然大部分是透明的。因此,您将看到在 View 上绘制的任何内容位于表面上显示的任何内容之上。

看起来您实际上并不是在 Surface 上绘图,因此应该只是黑色。

您正在创建一个单独的线程并尽快调用 postInvalidate()。锁定和解锁 Surface 的事实并没有太大变化,除了导致您提交未绘制的缓冲区以在 Surface 层上进行合成之外,考虑到它真正为您提供的只是 60fps 的速度,这是一项大量工作。无效和绘制是在主 UI 线程上执行的,并且是 View 系统调用您的 onDraw() 方法。

你应该选择其中一种方式。要么使用显式绘制调用从 PanelThread 进行绘图(在这种情况下,我建议您根本不要子类化 SurfaceView),或者放弃 SurfaceView 并仅使用 custom View反而。一旦你解决了这个问题,事情就应该开始以更有意义的方式运作。

更新:我怀疑您的更新似乎停止的原因是因为您存在竞争条件。一个线程执行此操作:

startCount++;

另一个线程执行此操作:

if (startCount == 360) startCount= 0;

仅当主 UI 线程(执行第二行代码)看到每个增量时,此行为才会正确。如果主 UI 线程停滞并错过更新,它可能会看到 startCount 为 359,然后为 361。此时 startCount 将继续递增而不会重置,你的绘制函数将填充整个圆,因为:

If the sweep angle is >= 360, then the oval is drawn completely.

将测试更改为在 startCount >= 360 时重新启动可能会解决问题,但最好避免跨线程分配逻辑。

关于android - SurfaceView onDraw,有时在绘制之前不会刷新自己......为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36367983/

相关文章:

Android Studio 更新卡住清理?

java - 如何在 Java 中处理来自 Google Sheets API v4 的 JSON 字符串

java - 如何滚动屏幕内存位图

Android:如何制作圆形的Camera Preview?

Android - 使用 SurfaceView 和 Camera2 API 处理屏幕方向

android - 测量 TextInputLayout 错误容器的高度

android - 具有不断增长的 TextView 的 ConstraintLayout 在需要时声明相等的空间

android - Opengl-es onTouchEvents 问题还是绘图问题?

android - 我的android应用程序在我的模拟器上运行良好,但是在设备上崩溃了

Android:如何获得更快的 onDraw()