Thursday 31 May 2018

How do I guarantee that my Android SurfaceView is transparent instead of black?

I have a custom view that extends SurfaceView overlaying the rest of my interface, it works on emulators and when the debugger is connected on my phone, but but when the phone is running on battery the view never clears.

public class CustomView extends SurfaceView {

    private final Paint paint;
    private final SurfaceHolder holder;
    private final Context context;

    private float strokeWidth = 4;

    private boolean canvasAlreadyLocked = false;

    public CustomView(Context viewContext, AttributeSet attrs)
    {
        super(viewContext, attrs);
        Log.i("CustomView", "CustomView create context & attrs");
        holder = getHolder();
        context = viewContext;
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(strokeWidth);
        drawLine();
        setZOrderOnTop(true);
        holder.setFormat(PixelFormat.TRANSPARENT);
    }

    public void resume() {
        Log.i("CustomView", "Resume the customview display.");
        setZOrderOnTop(true);
        holder.setFormat(PixelFormat.TRANSPARENT);
    }

    @Override
    public void onAttachedToWindow(){
        super.onAttachedToWindow();
        setZOrderOnTop(true);
    }

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

    protected void drawLine() {
        if (!canvasAlreadyLocked) {
            invalidate();
            if (holder.getSurface().isValid()) {
                try {
                    final Canvas canvas = holder.lockCanvas();
                    canvasAlreadyLocked = true;
                    if (canvas != null) {
                        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                        paint.setColor(Color.BLACK);
                        paint.setStrokeWidth(strokeWidth * 2);
                        canvas.drawLine(0, getY(), getWidth(), getY(), paint);
                        paint.setColor(Color.WHITE);
                        paint.setStrokeWidth(strokeWidth);
                        canvas.drawLine(0, getY(), getWidth(), getY(), paint);
                        holder.unlockCanvasAndPost(canvas);
                        canvasAlreadyLocked = false;
                    }
                }
                catch (IllegalArgumentException iae)
                {
                    Log.w("CustomView", "Exception trying to lock canvas: "+iae.getMessage());
                    Log.getStackTraceString(iae);

                }
            }
        }
    } 

    private float getY() {
        getHeight()/2;
    }

}

I'm aware that some of the calls here are redundant - that is mostly the legacy of trying lots of different things to try to make it work. You will notice that I have already done everything recommended in this answer.

The layout works like this:

<FrameLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.custom.CustomViewApp">

    <FrameLayout
        android:id="@+id/control"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true">

        <com.custom.AutoFitTextureView
            android:id="@+id/texture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true" />

        <ImageButton
            android:id="@+id/gpsNotification"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/gps_unfixed"
            android:layout_gravity="right"
            android:tint="@color/gps_unfixed"
            android:background="@null" />

        <ProgressBar
            android:id="@+id/camera_spinner"
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal|center_vertical"
            android:gravity="center"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:visibility="invisible"
            />

        <com.custom.CustomView
            android:id="@+id/custom_view"
            android:background="@color/transparent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true" />

    </FrameLayout>
</FrameLayout>

This is pulled in from a ViewFragment:

@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
    mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
    gpsNotification = (ImageButton) view.findViewById(R.id.gpsNotification);
    customView = (CustomView) view.findViewById(R.id.custom_view);
    spinner = (ProgressBar) view.findViewById(R.id.camera_spinner);
    spinner.setVisibility(VISIBLE);
}

I have tried to simplify this as far as I can and obviously there is a lot more happening in this scenario, but hopefully this is enough to indicate where the problem might be coming from.

The AutoFitTextureView is displaying the view from the camera.

  • When I run it in an emulator, everything displays as expected, regardless of the battery settings.
  • When I run it on my phone connected by USB everything displays as expected.
  • When I run it on my phone disconnected, the view will usually, but not always, show as plain black - the AutoFitTextureView is completely obscured, but the line on the CustomView is drawn. The other components are visible, which leads me to suspect there is a problem with when or how I call setZOrderOnTop().
  • If I hit a breakpoint in the fragment and set the visibility of the CustomView to INVISIBLE I can immediately see everything else, but as you might expect I lose the overlay. Manually calling setZOrderOnTop at that point doesn't seem to change anything.
  • When it is running correctly, I can examine the structure with the Layout Inspector tool, but when the SurfaceView is opaque the Layout Inspector raises an Unexpected Error: Empty View Hierarchy message. I haven't been able to locate a corresponding error message in Logcat.

How do I ensure my SurfaceView-derived View is always transparent? If I can't guarantee that, is there any way I can test whether it currently is transparent so that I can intercede if it is not?



from How do I guarantee that my Android SurfaceView is transparent instead of black?

No comments:

Post a Comment