vanlooverenkoen
vanlooverenkoen

Reputation: 2301

Canvas is to slow, Surfaceview doesn't show content

I'm creating an android game. With in the middle a circle that needs to be spinning all the time. I first created a Class that extended from View, that way I could draw on the canvas with the onDraw method. It works but when you are playing for a couple of minutes (sometimes even after a couple of seconds) the game starts to lag very hard. I search on the internet for sollutions. So I found I could also use a SurfaceView where the drawings happend on a seperate Thread. This way I could do all the drawings in another thread when the ui thread is still available for all user interaction (my game also contains a lot of animations) But I got a couple of problems:

Problem 1

When I start drawing on the SurfaceView my complete SurfaceView is Black. I searched for this issue and added this line of code:

    getHolder().setFormat(PixelFormat.TRANSLUCENT);

This worked for getting the right background color. But my question is: Is this the right way of doing this? And why am I getting this Black overlay (couldn't find a good explaination)

Problem 2

When I draw on the surfaceview I don't get any drawings visible. For this I searched the web and found this:

    setZOrderOnTop(true);

This works for showing my drawings but is not the right answer. I have an View "Game Over" that will be shown on top of the surfaceview when the users does something wrong. So how can I fix this that I can see my drawings and still see my GameOverView?

For the View with the onDraw(...) I calculated all my variables and drawings in the onDraw(...) method. Because I need to check the width and height of the canvas for my logic.

For the Surfaceview I use a Thread with this Runnabable run function:

@Override
public void run() {
    setZOrderOnTop(true);
    getHolder().setFormat(PixelFormat.TRANSLUCENT);
    while (isRunning) {
        if (!surfaceHolder.getSurface().isValid())
            continue;
        try {
            Thread.sleep(16);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Canvas canvas = surfaceHolder.lockCanvas();
        draw(canvas);
        surfaceHolder.unlockCanvasAndPost(canvas);
    }
}

EDIT 1

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ads="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="be.vanlooverenkoen.advancedwheel.ui.GameActivity">

    <be.vanlooverenkoen.advancedwheel.ui.CustomTextView
        android:id="@+id/pause_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:clickable="true"
        android:padding="20dp"
        android:text="@string/pause"
        android:textSize="30sp" />

    <be.vanlooverenkoen.advancedwheel.ui.CustomTextView
        android:id="@+id/current_points_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:padding="20dp"
        android:text="@string/default_score"
        android:textSize="120sp" />

    <be.vanlooverenkoen.advancedwheel.ui.CustomTextView
        android:id="@+id/title_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:gravity="center"
        android:keepScreenOn="true"
        android:padding="16dp"
        android:text="@string/name_title"
        android:textSize="@dimen/title_font"
        android:textStyle="bold" />

    <LinearLayout
        android:id="@+id/best_score_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:clickable="true"
        android:gravity="end"
        android:orientation="vertical"
        android:paddingBottom="20dp"
        android:paddingTop="20dp">


        <be.vanlooverenkoen.advancedwheel.ui.CustomTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="end"
            android:paddingEnd="20dp"
            android:paddingStart="20dp"
            android:text="BEST"
            android:textSize="15sp" />

        <be.vanlooverenkoen.advancedwheel.ui.CustomTextView
            android:id="@+id/high_score_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingEnd="20dp"
            android:paddingStart="20dp"
            android:text="10"
            android:textSize="30sp" />
    </LinearLayout>

    <be.vanlooverenkoen.advancedwheel.ui.CircleView
        android:id="@+id/circleview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/control_panel"
        android:layout_below="@+id/title_tv" />

    <LinearLayout
        android:id="@+id/control_panel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:gravity="center"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/volume_imgv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:clickable="true"
                android:padding="20dp"
                android:src="@drawable/volume_on" />

            <ImageView
                android:id="@+id/settings_imgv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:clickable="true"
                android:padding="20dp"
                android:src="@drawable/settings" />

            <ImageView
                android:id="@+id/infoImgv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:clickable="true"
                android:padding="20dp"
                android:src="@drawable/info" />
        </LinearLayout>

        <com.google.android.gms.ads.AdView
            android:id="@+id/ad_banner"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:visibility="gone"
            ads:adSize="BANNER"
            ads:adUnitId="ca-app-pub-2228582628850456/4663114920" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:gravity="center"
        android:orientation="vertical">

        <be.vanlooverenkoen.advancedwheel.ui.CustomTextView
            android:id="@+id/replay_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="20dp"
            android:text="Replay"
            android:textSize="@dimen/title_font"
            android:visibility="gone" />

        <com.google.android.gms.ads.AdView
            android:id="@+id/gameover_ad_banner"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:visibility="gone"
            ads:adSize="BANNER"
            ads:adUnitId="ca-app-pub-2228582628850456/4663114920" />
    </LinearLayout>

    <be.vanlooverenkoen.advancedwheel.ui.CustomTextView
        android:id="@+id/game_over_tv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:keepScreenOn="true"
        android:padding="16dp"
        android:text="@string/game_over"
        android:textSize="@dimen/title_font"
        android:textStyle="bold" />

    <be.vanlooverenkoen.advancedwheel.ui.SettingsView
        android:id="@+id/settings_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true"
        android:padding="15dp" />

    <be.vanlooverenkoen.advancedwheel.ui.InfoView
        android:id="@+id/info_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true"
        android:padding="15dp" />

</RelativeLayout>

EDIT 2 my draw method:

private void draw(Canvas canvas) {
    int extra = (canvas.getWidth() - canvas.getHeight()) / 2;
    if (!isRunning)
        return;
    //The piechart of colors
    int offset = 0;
    if (canvas.getWidth() < canvas.getHeight()) {
        offset = canvas.getHeight() - canvas.getWidth();
        offset = offset / 2;
        rect.set(marginOuterCircle, marginOuterCircle + offset, canvas.getWidth() - marginOuterCircle, canvas.getWidth() + offset - marginOuterCircle);
    } else {
        rect.set(marginOuterCircle + extra, marginOuterCircle, canvas.getHeight() - marginOuterCircle + extra, canvas.getHeight() - marginOuterCircle);
    }
    //get value for 100%
    int sum = amountOfColors;
    //initalize painter
    paintPieChart.setStyle(Paint.Style.STROKE);
    paintPieChart.setStrokeWidth(1f);
    paintPieChart.setStyle(Paint.Style.FILL_AND_STROKE);
    double start = startAngle;
    for (int i = 0; i < amountOfColors; i++) {
        //draw slice
        paintPieChart.setColor(colors[i]);
        float angle;
        angle = ((360.0f / sum) * 1);
        if ((start <= 270 && start + angle >= 270) || (start <= -90 && start + angle >= -90)) {
            if (previousColor != paintPieChart.getColor()) {
                if (previousColor == paintStick.getColor()
                        && isPlaying)
                    wrongHit();
                if (inDemo && circleListener != null) {
                    circleListener.onWrongHit(score);
                }
                previousColor = currentColor;
            }
            if (inDemo && previousColor == paintStick.getColor() && circleListener != null)
                circleListener.onCorrectHit(0);
            currentColor = paintPieChart.getColor();
        }
        canvas.drawArc(rect, (float) start, angle, true, paintPieChart);
        start += angle;
        if (start > 360)
            start -= 360;
        if (start < -360)
            start += 360;
    }

    //Innercircle
    if (canvas.getWidth() < canvas.getHeight()) {
        rect.set(marginInnerCircle, marginInnerCircle + offset, canvas.getWidth() - marginInnerCircle, canvas.getWidth() + offset - marginInnerCircle);
    } else {
        //// TODO: 18/03/2017 FIX for height
        rect.set(marginInnerCircle + extra, marginInnerCircle, canvas.getHeight() - marginInnerCircle + extra, canvas.getHeight() - marginInnerCircle);
    }
    canvas.drawArc(rect, 0, 360, true, paintBackground);
    if (canvas.getWidth() < canvas.getHeight()) {
        rect.set(marginOverlayCircle, marginOverlayCircle + offset, canvas.getWidth() - marginOverlayCircle, canvas.getWidth() + offset - marginOverlayCircle);
    } else {
        //// TODO: 18/03/2017 FIX for height
        rect.set(marginOverlayCircle + extra, marginOverlayCircle, canvas.getHeight() - marginOverlayCircle + extra, canvas.getHeight() - marginOverlayCircle);
    }
    canvas.drawArc(rect, 0, 360, true, paintOverlay);
    //Stick
    rect.set((canvas.getWidth() / 2) - stickThickness
            , rect.top
            , (canvas.getWidth() / 2) + stickThickness
            , rect.bottom - ((rect.bottom - rect.top) / 2));
    canvas.drawRoundRect(rect, 2, 2, paintStick);

    if (revese)
        startAngle -= speed;
    else
        startAngle += speed;
    if (startAngle > 360)
        startAngle -= 360;
    if (startAngle < -360)
        startAngle += 360;
    index++;
    if (index >= colors.length)
        index = 0;
}

EDIT 3 this is my picture

Upvotes: 4

Views: 2139

Answers (2)

Zoe - Save the data dump
Zoe - Save the data dump

Reputation: 28228

OBSERVATION:

  • You are trying to render views above/under each other
  • You cannot see anything rendered in your surfaceview
  • In your layout there are several views covering others, and some aren't even visible

1) Rendering objects above each other

You are rendering views above each other, but many are visible over others. This means view X renders over view Y, view Y is being drawn to the screen but is invisible. But the visibility of the view is not changed, so you waste resources on views that aren't visible, but should be.

2) You cannot see anything rendered in your surfaceview

(The following can be wrong as a result of having to replace a lot of your views with different views to get it to render properly on my screen)

The view with ID "default_score" covers a large portion of the SurfaceView. But what is probably the cause of the slow rendering and slow reaction is this:

You have many layouts set to "match_parent" even if they aren't supposed to appear until later. Set the visibility of the views you don't need to GONE:

android:visibility="gone"
view.setVisibility(GONE);

One of those, depending on where you are. YOu only need to set (say a layout) to GONE. That hides its children as well, so you do not need to change visibility of all of them.

Setting them to INVISIBLE means they are still there and can be pressed, so it does use more memory. GONE means it doesn't occupy space, doesn't listen for touchevents and doesn't render. That saves memory and should speed your view up.

I see in your layout the children of two LinearLayouts are set to GONE. set the layout to GONE instead, and remove the visibility call on the children

3) Views covering other views

There are several views covering other views. These views aren't set to GONE(or invisible) and can be causing the problems you are having. See summary at the end for more information


Suggestion to solutions

As this is a game, there are several things you do not need in the layout all the time. Here are some of my suggestions:

  • Fullscreen dialog on the "game over"-screen, and other things you do not need visible constantly. Alternatively, split your layout into several views. Each view will contain a part of the game. If you do not need view 1 visible, set it to GONE and show view 2 or 3. (3 is in this case just a random number. you split the layout into as many pieces as you need). Anything that isn't visible at the same time, should be set to GONE(meaning the parent layout should be set to INVISIBLE. its children are affected by it)
  • Make sure the invisible views(with visibility set to GONE) are those you do not need. And make sure all the views you do not need visible at a given time have their visibility set to GONE

Your screenshot:

What I see here are two overlappings: "advanced wheel" and the number "0".

"game over" and your surfaceview. Set the ones that aren't visible on startup to "GONE".

Switch to LinearLayout where possible. Meaning if the two bars(top and bottom) and the surfaceview are placed linearly, you do not need that to be a RelativeLayout. If the content of a bar is not linear(meaning you cannot use LinearLayout to get that result), use Relative(or COnstraint)Layout

Move for an instance end stuff(text saying game over, total score, replay button, main menu button, etc) to a fullscreen dialog. You can add a SurfaceView in a fullscreen dialog if you want to use it, or you have some content you want to draw on. I recommend you use DialogFragment for this part. You can also pass things like score, the SurfaceView class(if it contains game logic) to this Dialog. Then, on the buttons, you can do things like calling the SurfaceView to replay and dismiss the dialog, or get the activity and create an intent to go to the menu


Answers to some comments

I just tried to remove the AdView from admob and the app goes a lot smoother? How is this possible?

May be because you load it, meaning it wants to(and does) render, but it isn't shown. You use resources you shouldn't be using(on a admob banner that isn't even visible). I haven't seen that part of your code, so this may be wrong.

But the biggest question and most important one is why is it so slow?

When you are running a SurfaceView, and have a ton of views drawn over and next to it, there are several things you have to take into consideration. My suggestion is you change the main view(meaning the root) to LinearLayout. So you can have the less-complex views(for an instance the bar layouts, etc) lined up, but the contents of for an instance the bars are RelativeLayouts. LinearLayout uses less memory, because it is (well,) linear. It doesn't need to store "this is rendered here and this is rendered here above this and under that", it stores the order and width/height

Upvotes: 2

Dimezis
Dimezis

Reputation: 1595

Problem 1:

The answer is inside of the SurfaceView's draw() method, you can check out its sources:

        @Override
        public void draw(Canvas canvas) {
            if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
                // draw() is not called when SKIP_DRAW is set
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
                    // punch a whole in the view-hierarchy below us
                    canvas.drawColor(0, PorterDuff.Mode.CLEAR);
                }
            }
            super.draw(canvas);
        }

So what it's doing, is drawing the black color - #000000 to "punch a hole" in view hierarchy to not overlap with any other View's drawing. Default pixel format is non-transparent, so this color virtually becomes #FF000000 (non-transparent black), because the system doesn't read color's alpha channel. But with PixelFormat.TRANSLUCENT it uses proper fully transparent black - #00000000. So yeah, this solution is completely legit.

You can read about details of how SurfaceView works here - https://source.android.com/devices/graphics/arch-sv-glsv

Problem 2 (EDIT):

I would suggest to draw this Game Over overlay manually inside of SurfaceView.

Another possible solution is to set setZOrderOnTop(false); and clear your background with a solid (non-transparent) color between frames.

Also you can try calling setZOrderMediaOverlay(true) instead.

What will work for sure - is replacing your SurfaceView with TextureView, because it's capable of proper composing with other Views on the screen - https://source.android.com/devices/graphics/arch-tv

Upvotes: 3

Related Questions