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.

Community
  • 1
  • 1
Unpossible
  • 10,607
  • 22
  • 75
  • 113

8 Answers8

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).

starball
  • 20,030
  • 7
  • 43
  • 238
Evos
  • 3,915
  • 2
  • 18
  • 21
  • 16
    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! – Erik Živković Jan 16 '13 at 20:13
  • @ErikZ: thanks, the empty SurfaceView solved my problem! – Oliver Jun 23 '14 at 08:31
  • This solution removed some heavy flickering from the [CameraPreview as described in the documentation](http://developer.android.com/guide/topics/media/camera.html#camera-preview) - especially when starting the preview. – miracula Aug 15 '14 at 13:15
  • Well... the solution is dirty but effective :) – Bartek Lipinski May 06 '15 at 08:20
  • 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! – JacksOnF1re May 08 '15 at 19:53
  • 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! – vir us Oct 13 '15 at 20:00
  • 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? – Teilmann Nov 11 '15 at 10:45
  • You, sir, just earned an honorable line of comment in an ugly piece of workaround-code! Thank you! – muetzenflo Feb 04 '16 at 18:43
  • 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... – Greg Ellis Feb 13 '16 at 21:12
  • you saved my life! I wonder how u arrived at this solution..:-) – Sudhasri Apr 06 '16 at 09:56
  • 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. – CrazyOrr May 04 '16 at 08:11
  • 13
    There is a less messy approach, just put `getWindow().setFormat(PixelFormat.TRANSLUCENT);` in the host activity's onCreate() callback before calling setContentView(). – CrazyOrr May 05 '16 at 07:29
  • @CrazyOrr 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. – Evos Jun 07 '16 at 16:40
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.

Reuben Scratton
  • 38,595
  • 9
  • 77
  • 86
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().

tej shah
  • 2,995
  • 2
  • 25
  • 35
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.

hackbod
  • 90,665
  • 16
  • 140
  • 154
  • 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?) – Unpossible Jan 17 '12 at 00:27
  • 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). – Reuben Scratton Jan 17 '12 at 11:33
  • Adding the drawing code to `onSurfaceChanged()` did nothing unfortunately - still seeing the black flash. – Unpossible Jan 17 '12 at 19:01
  • 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. – John Meschke Sep 12 '22 at 02:32
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.

Cypress Frankenfeld
  • 2,317
  • 2
  • 28
  • 40
Tofeeq Ahmad
  • 11,935
  • 4
  • 61
  • 87
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)

Community
  • 1
  • 1
Valentin Baryshev
  • 2,195
  • 3
  • 15
  • 24
  • Well it's not even related to navigation drawer, it's more about dynamically added fragment with surface inside. – Evos Jun 07 '16 at 16:45
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.

Björn Kechel
  • 7,933
  • 3
  • 54
  • 57
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.

Gangnus
  • 24,044
  • 16
  • 90
  • 149