SurfaceView onDraw,有时在绘制之前不刷新...为什么?

时间:2016-04-02 00:51:12

标签: 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中的插图,前两个周期是我的期望。然而,当它完成第二个循环时,它仍然全黑前进,这很奇怪,它没有删除以前的绘图。我希望它能像在第二个周期中一样重复自己。

这并不总是在第二个周期后发生。有时候,在发生之前的许多周期之后,应该等待一段时间,没有任何干扰(即不接触设备)。很难预测何时会发生。

它的原因是什么?如何在这个问题上进行调试?

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

附在自定义表面视图的完整代码下面,它是线程代码。

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

        }
    }
}

1 个答案:

答案 0 :(得分:2)

你正在混合两种不同的绘画方法。

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

通过继承SurfaceView并定义onDraw()方法,您将其视为自定义视图。你正在绘制视图部分,但你似乎没有先删除它,所以它应该仍然是大部分透明的。所以你会看到你在View上绘制的任何内容上绘制的内容。

看起来你实际上并没有在Surface上绘图,所以它应该只是黑色。

您正在创建一个单独的主题并尽可能快地调用postInvalidate()。您锁定和解锁Surface的事实并没有太大变化,只是为了让您在Surface图层上提交一个未绘制的合成缓冲区,这是一项很多工作,考虑到它真正让你获得的是60fps的速度。无效和绘制正在主UI线程上执行,它是调用onDraw()方法的View系统。

你应该采取某种方式。您可以使用显式绘制调用从PanelThread绘制图形(在这种情况下,我建议您根本不创建SurfaceView的子类),或者抛弃SurfaceView,只需使用custom View。一旦你完成整理,事情应该开始表现得更有意义。

更新:我怀疑您的更新似乎停止的原因是因为您有竞争条件。一个线程就是这样做的:

startCount++;

和另一个执行此操作的线程:

if (startCount == 360) startCount= 0;

如果主UI线程(执行第二行代码)看到每个增量,这只能正常运行。如果主UI线程停滞并且错过了更新,它可能会在359处看到startCount,然后在361处看到startCount。此时startCount >= 360将继续递增而不会被重置,并且您的绘制函数将填充整个圈子因为:

  

如果扫掠角度> = 360,则完全绘制椭圆。

* { margin: 0; padding: 0; } .container { width: 400px; } img { display: block; } .top, .bottom { width: 100%; } .corpus { display: block; width: 100%; height: 100px; background: url('http://f.cl.ly/items/1G3o0j0e1S32301k3y18/corpus.svg'); background-repeat: repeat-y; }可能修复问题时将测试更改为重新启动,但最好避免在线程之间分配逻辑。