SurfaceView flashes black on load
Asked Answered
S

8

34

I have a drawing app that takes about 2-5 seconds to load the drawing for complicated drawings (done via an AsyncTask). For a better user experience, during this time I flash the stored PNG version of the drawing I have from the app directory as an ImageView, and show a loading ProgressBar calling setContentView() in the Activity constructor:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
    android:id="@+id/flash"
    android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@color/note_bg_white"
        android:contentDescription="@string/content_desc_flash_img"
        android:focusable="false" />
<RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="6dp"
        android:paddingLeft="6dp"
    android:paddingRight="6dp"
        android:gravity="bottom|center_vertical">
        <ProgressBar 
            style="?android:attr/progressBarStyleHorizontal"
    android:id="@+id/toolbar_progress"
    android:layout_width="match_parent"
    android:layout_height="18dp"
    android:gravity="bottom|center_vertical" />
    </RelativeLayout>
</FrameLayout>

When the AsyncTask is complete, I then call setContentView() again with the new layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff">
    <com.my.package.DrawingCanvas
        android:id="@+id/canvas"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="true"
        android:background="#ffffff" />
</RelativeLayout>

When I was using a simple Canvas model, this worked perfectly, as the custom DrawingCanvas View would draw the drawing to the screen on the initial onDraw() before being shown to the user, but now using the SurfaceView with a drawing loop, I am seeing the flashed layout, then when the drawing is loaded, a black screen for about a second, then finally the new DrawingCanvas.

I am assuming that the reason is related to the start-up time of the drawing loop thread, and I've left my overide of onDraw() in the SurfaceView, and it gets called, but the canvas available in onDraw() does not seem to draw to the SurfaceView. I've also tried setting a solid color background in the XML above hoping at a minimum to show a white background, but those never seem to take affect, even setting it from code.

Any advice or an explanation of what I am seeing with the black screen?

EDIT:

Ok, have confirmed that onDraw() is drawing to the same canvas, so left my draw ops in there as well hoping that on the initial showing of the SurfaceView, the user would see those drawings like in the regular Canvas implementation, and when the drawing thread had spun up, it would overwrite the Canvas.

However, if I clear the drawing thread operations, I do see the onDraw() results, but again, AFTER the black screen flash. And if I remove the onDraw() override completely, I still see the black flash and then I see the layout with the white background from the XML.

So, it looks like no matter what, I am always going to see the black screen, unless perhaps instead of switching the layouts, I simply modify the existing 'flash' layout that is already active?

EDIT2:

Have tried using a ViewStub so that I can inflate the SurfaceView into the existing View after the note is loaded, but the same issu still applies. As near as I can tell, there is a sizable (~200ms) delay between the SurfaceView constructor and the call to surfaceCreated() executing, but not sure that this is where the black screen is happening, or why the screen is being drawn to black...

EDIT3:

My final attempt for now includes making the SurfaceView transparent. This combined with leaving the existing layout in place and simply adding to that layout via the ViewStub would have resulted in a working solution I though, but still, for a split second when the SurfaceView is loading the screen flashes black before the SurfaceView is shown, as transparent. If anyone has any other ideas to try, please post them.

Sitdown answered 7/1, 2012 at 20:28 Comment(4)
Some heavy operation in the onCreate could give such results.Latif
I show the 'flash' layout in onCreate(), no delay there. Delay/black screen comes once the drawing is loaded and I start the SurfaceView, which only involves starting thread.Sitdown
Can we see source for your DrawingCanvas class?Authorized
Unfortunately no, I can't post anything other than small fragments due to legal restrictions.Sitdown
I
151

I think I found the reason for the black flash. In my case I'm using a SurfaceView inside a Fragment and dynamically adding this fragment to the activity after some action. The moment when I add the fragment to the activity, the screen flashes black. I checked out grepcode for the SurfaceView source and here's what I found: when the surface view appears in the window the very fist time, it requests the window's parameters changing by calling a private IWindowSession.relayout(..) method. This method "gives" you a new frame, window, and window surface. I think the screen blinks right at that moment.

The solution is pretty simple: if your window already has appropriate parameters it will not refresh all the window's stuff and the screen will not blink. The simplest solution is to add a 0px height plain SurfaceView to the first layout of your activity. This will recreate the window before the activity is shown on the screen, and when you set your second layout it will just continue using the window with the current parameters.

UPDATE: Looks like after years this behavior is still there. I would recommend to use TextureView instead of SurfaceView. This is literally a newer implementation of same thing that don't have this side effect as well as don't have a problem of black background when you moving it (for instance within ScrollView, ViewPager, RecyclerView etc).

Instructive answered 28/9, 2012 at 8:22 Comment(13)
As crazy as this answer sounds, adding a 0px * 0px SurfaceView in the layout of your activity (the activity of the root layout), actually removes the black Flashing of the SurfaceView in your fragment! I did this and it works perfectly!Nonreturnable
@ErikZ: thanks, the empty SurfaceView solved my problem!Selfdeprecating
This solution removed some heavy flickering from the CameraPreview as described in the documentation - especially when starting the preview.Lurcher
Well... the solution is dirty but effective :)Wellfavored
I am implementing the surfaceView for a MediaPlayer inside of a fragment, which is part of a fragment viewpager and I tried to fix this issue for hours. Thought it is the MediaPlayer loading. But it was the surfaceView...darn. This works! Great!Mozzarella
Hours of tries of optimizations... I even didn't realize it was because of VideoView. I thought my layout was too complex and inflating takes enough time to produce a visible flickering. Your fix worked, thanks!Wombat
It's stupid that this is needed to fix the flickering. I upvoted, because this still applies (Should be the correct answet btw). Is it a bug or something?Faddish
You, sir, just earned an honorable line of comment in an ugly piece of workaround-code! Thank you!Counterclaim
This does work. Just to add... If you happen to be dynamically adding the SurfaceView to a child layout rather than the root layout then the 0px plain SurfaceView that you add to your layout xml MUST be a child of the layout that you are dynamically adding the SurfaceView to. Hopefully that makes sense...Maibach
you saved my life! I wonder how u arrived at this solution..:-)Antipater
It's messy but it works. Had this issue with a device running Android 4.3, but another device running Android 6.0 is OK. I wonder in which version did Google fixed this.Diseased
There is a less messy approach, just put getWindow().setFormat(PixelFormat.TRANSLUCENT); in the host activity's onCreate() callback before calling setContentView().Diseased
@Diseased the above solution with pixel format wasn't working for me at the moment when i found this bug since the whole window was recreated.Instructive
A
5

I would not do this with two separate layouts.

Try making your root element a RelativeLayout and then having the SurfaceView and ImageView be sibling children of that. Whatever happens with your threading etc, the ImageView should be drawn wholly opaque on top of the SurfaceView, so you won't get a flash.

Once your worker thread has loaded the drawing and the SurfaceView can draw itself, remove the progress bar and ImageView from the view hierarchy.

Authorized answered 16/1, 2012 at 12:2 Comment(6)
I had tried this without success, but may revisit this shortly if I can't find the root cause of the flash.Sitdown
Should work. Maybe it'd help if you posted the code for com.my.package.DrawingCanvas?Authorized
This doesn't explain the issue, but appears to work as a workaround, so bounty awarded as hackbod never expanded on what might be the root cause, and everything I've tried related to that line of thinking seems to fail.Sitdown
I will create a demo project and email it to you, if you give me an address to send to.Authorized
Email with demo project sent.Authorized
can someone give an example?Goodrow
M
2

You need to make sure that onSurfaceChanged() doesn't return until you have completely drawn and posted the contents of the SurfaceView's Surface.

Minivet answered 17/1, 2012 at 0:14 Comment(4)
How do I ensure that the Surfaceview gets completely drawn and posted? What could cause a call to onSurfaceChanged() prior to a complete post and draw? I've confirmed that the View is calling onSurfaceChanged() after the start of my drawing thread, but prior to the drawing loop being able to complete it's first operation (triggered at the lockCanvas() call?)Sitdown
hackbod is a senior dev on the Android team, she's probably right. It's never occurred to me to try to draw from onSurfaceChanged() but it's kinda obvious now she suggests it. She means in your onSurfaceChanged() you call holder.lockCanvas(), then draw something, then call holder.unlockCanvasAndPost(). If you have a placeholder imageview also in the view tree, simply filling the surfaceview's canvas with total transparency should achieve the desired effect (of not obscuring it).Authorized
Adding the drawing code to onSurfaceChanged() did nothing unfortunately - still seeing the black flash.Sitdown
This is what solved my flicker problem on app startup. Although I'm using Vulkan in a native program, I was experiencing a similar issue. I found that redrawing the window before onNativeWindowRedrawNeeded() returned is what was needed. It's even documented in the native activity callback reference. In the Java/Kotlin side, it would be the surfaceRedrawNeeded() method.Wellfound
V
2

I assume that you're drawing some heavy code in your surface view because as far as I know, the surface view will show the complete view after drawing everything one time. I suggest you first go to the onDraw() method of surface view then set on background of canvas then call forcefully invalidate to avoid that black screen. Add a condition to be sure that this forcefully invalidate is only called once.

Vaas answered 17/1, 2012 at 11:40 Comment(0)
D
2

I facing the same issue but thank for this answer

There is a less messy approach, just put getWindow().setFormat(PixelFormat.TRANSLUCENT); in the host activity's onCreate() callback before calling setContentView().

Drysalt answered 26/5, 2017 at 7:36 Comment(0)
S
1

If you use NavigationDrawer with fragments, than Evos solution will not work!
Try to use NavigationDrawer with Activities instead of fragments, this will help you 100%

How to implement NavDrawer with activities: link
Another helpful link. (in case of SurfaceView will draw on top of SlidingMenu)

Sudorific answered 6/11, 2014 at 22:51 Comment(1)
Well it's not even related to navigation drawer, it's more about dynamically added fragment with surface inside.Instructive
P
1

CrazyOrr's solution worked for me, but it was deep in the comments for the top answer by Evos, so here it is again:

There is a less messy approach, just put getWindow().setFormat(PixelFormat.TRANSLUCENT); in the host activity's onCreate() callback before calling setContentView(). – CrazyOrr May 5 '16 at 7:29

Simply putting

getWindow().setFormat(PixelFormat.TRANSLUCENT); 

in the activity's onCreate() worked for me.

Putumayo answered 6/6, 2017 at 10:26 Comment(0)
A
0

" the canvas available in onDraw() does not seem to draw to the SurfaceView" - Are you sure that you do not create the second (or third...) instance of the Surface View? And some of them could be black and be shown for a second. I don't see the code here, so, I can't tell surely, but if it were in my application, I would check for this error at first.

Allimportant answered 14/1, 2012 at 22:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.