Reputation: 2301
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;
}
Upvotes: 4
Views: 2139
Reputation: 28228
OBSERVATION:
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
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:
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
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