Android Multiple SurfaceViews
Asked Answered
P

3

16

I'm trying to work with 3 SurfaceViews on one screen, one on top half (BoardView), one on bottom half (StatusView), and the last one as an extra layer above the top half (TileView) (see main.xml).

I created a class MySurfaceView, which is extended by BoardView, StatusView and TileView.

I've got multiple problems with this.

Let me first give the code.

main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/main_background">

    <com.niek.test.BoardView
        android:id="@+id/boardview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

    <FrameLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_below="@+id/boardview">
        <com.niek.test.StatusView
            android:id="@+id/statusview"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#F0931E"
            android:layout_below="@+id/boardview" />

            <com.niek.test.TileView
                android:id="@+id/tileview"
                android:layout_width="180dip"
                android:layout_height="60dip"
                android:layout_gravity="bottom"/>


    </FrameLayout>
</RelativeLayout>

MainActivity.java:

package com.niek.test;

public class MainActivity extends Activity {

    private Board board;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        board = new Board();
        BoardView boardView = (BoardView) findViewById(R.id.boardview);
        boardView.setBoard(board);
        StatusView statusView = (StatusView) findViewById(R.id.statusview);
        statusView.setBoard(board);
    }
}

MySurfaceView.java

package com.niek.test;

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {

    protected DrawThread drawThread;

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        getHolder().addCallback(this);
        setFocusable(true);

        drawThread = new DrawThread(getHolder());
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // TODO Auto-generated method stub

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        drawThread.setRunning(true);
        drawThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // we have to tell thread to shut down & wait for it to finish, or else
        // it might touch the Surface after we return and explode
        boolean retry = true;
        drawThread.setRunning(false);
        while (retry) {
            try {
                drawThread.join();
                retry = false;
            } catch (InterruptedException e) {
                // we will try it again and again...
            }
        }
    }

    protected class DrawThread extends Thread {
        private SurfaceHolder surfaceHolder;
        private boolean isRunning;

        public DrawThread(SurfaceHolder surfaceHolder) {
            this.surfaceHolder = surfaceHolder;
            isRunning = false;
        }

        public void setRunning(boolean run) {
            isRunning = run;
        }

        public void run() {
            Canvas c;
            while (isRunning) {
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    // TODO: handle exception
                }
                c = null;
                try {
                    c = surfaceHolder.lockCanvas(null);
                    synchronized (surfaceHolder) {
                        onDraw(c);
                        postInvalidate();
                    }
                } finally {
                    // do this in a finally so that if an exception is thrown
                    // during the above, we don't leave the Surface in an
                    // inconsistent state
                    if (c != null) {
                        surfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }
    }

}

These three classes extend MySurfaceView:

BoardView.java

package com.niek.test;


public class BoardView extends MySurfaceView {

    private int squareSize, marginX, marginY;

    private Board board;

    Paint boardBorder;

    public BoardView(Context context, AttributeSet attrs) {
        super(context, attrs);
        board = null;
    }

    public void setBoard(Board board) {
        this.board = board;
    }

    private void init(SurfaceHolder holder) {
        Canvas canvas = null;
        try {
            canvas = holder.lockCanvas();
            /* Initialize the board */
            squareSize = canvas.getWidth() / Board.GRIDSIZE;

            /* Size the view */
            LayoutParams lp = getLayoutParams();
            lp.height = (squareSize * Board.GRIDSIZE) + 4;
            setLayoutParams(lp);

            /* Place the board neatly in the center */
            marginX = (canvas.getWidth() - (squareSize * Board.GRIDSIZE)) / 2;
            marginY = 1;
        } finally {
            holder.unlockCanvasAndPost(canvas);
        }

        boardBorder = new Paint();
        boardBorder.setColor(Color.RED);
        boardBorder.setStyle(Style.STROKE);
    }

    @Override
    public void onDraw(Canvas canvas) {
        drawBoard(board, canvas);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        init(holder);
        super.surfaceCreated(holder);
    }

    private void drawBoard(Board board, Canvas canvas) {
        synchronized (board) {
            if (board != null) {
                for (Square[] ys : board.getSquares()) {
                    for (Square xs : ys) {
                        xs.onDraw(canvas, squareSize, squareSize, marginX, marginY);
                    }
                }
            }   
            canvas.drawRect(marginX - 1, marginY - 1, marginX + squareSize * Board.GRIDSIZE + 1, marginY + squareSize * Board.GRIDSIZE + 1, boardBorder);
        }
    }
}

StatusView.java

package com.niek.test;

public class StatusView extends MySurfaceView {

    private Board board;
    private Paint textPaint;

    public StatusView(Context context, AttributeSet attrs) {
        super(context, attrs);
        board = null;

        textPaint = new Paint();
        textPaint.setColor(Color.BLACK);
        textPaint.setTextSize(20);
        textPaint.setTypeface(Typeface.DEFAULT_BOLD);
    }

    public void setBoard(Board board) {
        this.board = board;
    }

    int tmp=0;
    @Override
    public void onDraw(Canvas c) {
        if (board != null) {
            c.drawText(tmp+"", 10, 20, textPaint);          
            tmp++;
            System.out.println(tmp);
        }
    }
}

TileView.java

package com.niek.test;

public class TileView extends MySurfaceView {

    public TileView(Context context, AttributeSet attrs) {
        super(context, attrs);
        System.out.println(0);
    }


    int tmp =0;
    @Override
    public void onDraw(Canvas c) {
        System.out.println(2);
        Paint p= new Paint();
        p.setColor(Color.RED);
        c.drawColor(Color.RED);
        c.drawText(tmp+"",10,10,p);
        tmp++;

    }

}

Now what are my problems?

First off, as you can see in MySurfaceView I've got this:

try {
    c = surfaceHolder.lockCanvas(null);
    synchronized (surfaceHolder) {
        onDraw(c);
        postInvalidate();
    }
}

When I only use onDraw(c), only the BoardView gets drawn, the StatusView doesn't get drawn, but the tmp increments in the onDraw of StatusView are being executed. When I only use postInvalidate(), same story, but only StatusView gets drawn, BoardView doesn't. So that's why I use both methods, and both Views get drawn.

Then there's TileView, the System.out(2) is being shown in logcat, but the view doesn't get drawn. It is a black square instead of the red square I ask it to be in the onDraw method.

When I turn the screen off and then on again, the TileView does get drawn, and the tmp increments are shown.

Who can help me?

For clarity, I've created this based on this tutorial.

Palmerpalmerston answered 13/4, 2011 at 10:40 Comment(0)
P
12

It looks like you are not supposed to create multiple SurfaceViews on one Layout. According to this two posts written by Android framework engineer:

The way surface view is implemented is that a separate surface is created and Z-ordered behind its containing window, and transparent pixels drawn into the rectangle where the SurfaceView is so you can see the surface behind. We never intended to allow for multiple surface view.

and

you should effectively think of SurfaceView as an overlay you embed inside your window, giving you an area in which you can directly draw independently of the normal view update system.

So, what you can do, is use one SurfaceView to draw all the graphics you want.

Phenolphthalein answered 5/10, 2011 at 12:58 Comment(2)
so can I add 2 surfaceview in different parents?Jacklight
This answer is misleading. From fadden's answer, following link to post by that engineer, we read: "Prior to 2.0 multiple surfaces on top of each other were not supported .. As of 2.0, there is a hack you can use to at least control the Z-order of two surface views". The key phrase is on top of each other. There is no problem with non-overlapping surfaces, even prior to 2.0. (See fadden's answer for link to Grafika source, which demonstrates this.) And after 2.0, even overlapping surfaces could be handled, with a little effort.Othelia
B
74

You can have multiple SurfaceViewsin one layout. The "Multi-surface test" activity in Grafika has three.

The first post cited in @nonsleepr's answer was followed up 9 months later with this post by the same author, which mentioned the existence of SurfaceView#setZOrderMediaOverlay().

The key thing to understand is that SurfaceView is not a regular view. When your app comes to the foreground it gets a surface to draw on. Everything in your app's UI is rendered onto the app's surface by the app, and then that surface is composited with other surfaces (like the status bar and navigation bar) by the system compositor. When you create a SurfaceView, it's actually creating an entirely new surface that is composited by the system, not by your app.

You can control the Z-ordering (i.e. "depth") of the SurfaceView surface very loosely. There are four positions, from top to bottom:

  • SurfaceView + ZOrderOnTop
  • (app UI goes here)
  • SurfaceView + ZOrderMediaOverlay
  • SurfaceView (default)

If you have two SurfaceViews at the same depth, and they overlap, the results are undefined -- one will "win", but you can't control which.

The system compositor on modern devices is very efficient when you have N surfaces. At N+1 surfaces you hit a performance cliff. So while you can have three SurfaceViews, you're generally better off keeping the number down. The value of N varies from device to device.

Update: if you really want to understand how SurfaceView works, see the Android System-Level Graphics doc.

Burseraceous answered 30/3, 2014 at 16:3 Comment(4)
@fadden: Hi, first of all, thanks for your answer! How about having two surfaceviews while one of them is always hidden (View.INVISIBLE)?Dance
For readers who get here, but are working with GLSurfaceView: HOWEVER, if you are trying to have two GLSurfaceViews, not just two SurfaceViews, that won't work AFAIK. (something about locking the GL thread.) Hence the approach Grafika uses, where they create their own GL thread, and manage access to different SurfaceViews on that thread.Othelia
This should be marked as THE ANSWER. Thank you @BurseraceousDomini
@Othelia Hey, can you please tell what exactly you're referring to in Grafika code? I need to use both GLSurfaceView and SurfaceView and facing similar issues. #61967075Wenn
P
12

It looks like you are not supposed to create multiple SurfaceViews on one Layout. According to this two posts written by Android framework engineer:

The way surface view is implemented is that a separate surface is created and Z-ordered behind its containing window, and transparent pixels drawn into the rectangle where the SurfaceView is so you can see the surface behind. We never intended to allow for multiple surface view.

and

you should effectively think of SurfaceView as an overlay you embed inside your window, giving you an area in which you can directly draw independently of the normal view update system.

So, what you can do, is use one SurfaceView to draw all the graphics you want.

Phenolphthalein answered 5/10, 2011 at 12:58 Comment(2)
so can I add 2 surfaceview in different parents?Jacklight
This answer is misleading. From fadden's answer, following link to post by that engineer, we read: "Prior to 2.0 multiple surfaces on top of each other were not supported .. As of 2.0, there is a hack you can use to at least control the Z-order of two surface views". The key phrase is on top of each other. There is no problem with non-overlapping surfaces, even prior to 2.0. (See fadden's answer for link to Grafika source, which demonstrates this.) And after 2.0, even overlapping surfaces could be handled, with a little effort.Othelia
R
1

It sounds like the SurfaceViews are being drawn, but transparency is not enabled for whichever one is on top. In your MySurfaceView class in the surfaceCreated() method, make sure you are calling holder.setFormat(PixelFormat.TRANSPARENT);

Reede answered 2/2, 2016 at 15:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.