0

I'm trying to draw some shapes onto a SurfaceView from within a thread, but nothing is being rendered to the screen. I've looked at similar questions by people having the same problem, but none of the answers have led me to a solution, suggesting a different cause in my particular case.

I've created a simplified version of my code to demonstrate the problem. Rendering is handled by the RenderingTestView class, which implements a custom view derived from SurfaceView. The rendering thread is implemented as a Runnable inside RenderingTestView:

package com.example.renderingtest.app;

import android.content.Context;
import android.graphics.*;
import android.os.Build;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class RenderingTestView extends SurfaceView {
    private SurfaceHolder holder;
    private Paint paint;
    private boolean surfaceCreated = false;
    private Thread videoThread;

    public RenderingTestView(Context context) {
        super(context);
        init_view(context);
    }

    public RenderingTestView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init_view(context);
    }

    public RenderingTestView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init_view(context);
    }

    private void init_view(Context context)
    {
        if (Build.VERSION.SDK_INT >= 11)
            setLayerType(android.view.View.LAYER_TYPE_SOFTWARE, null);

        paint = new Paint();
        paint.setColor(Color.RED);

        holder = getHolder();

        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                surfaceCreated = true;

                videoThread = new Thread(videoRunnable);
                videoThread.start();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                // do nothing
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                surfaceCreated = false;
            }
        });
    }

    private Runnable videoRunnable = new Runnable() {
        @Override
        public void run() {
            Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

            while (true) {
                if (!surfaceCreated || !holder.getSurface().isValid())
                    continue;

                Canvas c = holder.lockCanvas(null);
                try {
                    synchronized (holder) {
                        if (c != null)
                            Draw(c);
                    }
                }
                finally {
                    if (c != null)
                        holder.unlockCanvasAndPost(c);
                }
            }
        }
    };

    protected void Draw(Canvas canvas)
    {
        canvas.drawCircle(0, 0, 100, paint);
    }
}

Placing a breakpoint inside Draw() confirms that it's being called successfully.

The layout file looks like this:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.example.renderingtest.app.RenderingTest" android:background="#000000">

    <com.example.renderingtest.app.RenderingTest
            android:id="@+id/renderingTestView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true"
            android:layout_alignParentEnd="true" android:layout_alignParentTop="true"/>

</RelativeLayout>

Overriding onDraw() in RenderingTestView, like this:

@Override
public void onDraw(Canvas canvas)
{
     super.onDraw(canvas);
     Draw(canvas);
}

... and calling setWillNotDraw(false) inside init_view() does in fact produce the desired output, but I want to render from inside the Runnable rather than wait for invalidate() to produce a call to onDraw().

Adrian Lopez
  • 1,695
  • 1
  • 16
  • 22
  • It's hard to picture what you're doing and why it's going wrong. Do understand that a SurfaceView is different from other Views -- it has a View part and a Surface part. The View part is a transparent placeholder for layout purposes, and should be ignored. The Surface is a completely separate layer that is drawn behind all UI elements (unless you change the Z-order with the SurfaceHolder API). Check your layout and make sure you're not clearing the View portion to an opaque color. – fadden Sep 04 '14 at 15:38
  • What I'm doing is rendering shapes onto a SurfaceView's canvas. The problem is that the exact code I've posted is not drawing anything onto the canvas, when in fact it should. In any case, I've finally discovered the cause and will post the answer. – Adrian Lopez Sep 04 '14 at 17:55

1 Answers1

1

After further testing, I've discovered the problem is caused by the following code:

if (Build.VERSION.SDK_INT >= 11)
    setLayerType(android.view.View.LAYER_TYPE_SOFTWARE, null);

It turns out calling setLayerType() as above somehow prevents SurfaceView from rendering anything onto the Canvas, and is in any case unnecessary since SurfaceView rendering is always performed in software. When first testing my draw calls I was using a regular View rather than a SurfaceView, and the offending lines were carried over from that.

Adrian Lopez
  • 1,695
  • 1
  • 16
  • 22