Recently, in a client project, I ran into an issue with VideoViews overlapping each other, even though they were part of different Fragments. As is the case with most Views, when adding a Fragment on top of another, the top Fragment will have its Views drawn on top of the bottom Fragment. This however, is not the case when it comes to VideoViews, which require you to call `videoView.setZOrderMediaOverlay(true)` on the VideoView you would like to appear on top.
I have created a sample project here which shows the issue and where the code snippets below will be taken from. I have reported the issue and though I probably wouldn’t consider this a bug in the Android framework, I would consider it unexpected behavior. There is no need to download the sample project as the code snippets below will be enough explanation. Let’s dive in.
In the `onCreate` of the Activity, the `MainFragment` is added:
getSupportFragmentManager().beginTransaction().add(R.id.content_layout, MainFragment.newInstance(), "MAIN_FRAGMENT").commit();
The layout of our MainFragment is:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Main Fragment" /> <VideoView android:id="@+id/video_view" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
`onViewCreated` in `MainFragment` will setup the VideoView and begin playing the sample video.
videoView = (VideoView) view.findViewById(R.id.video_view); videoView.setMediaController(new MediaController(getContext())); Uri video = Uri.parse("android.resource://" + getContext().getPackageName() + "/" + R.raw.video); videoView.setVideoURI(video); videoView.start();
Here is what the MainFragment looks like:
Next up, we simply need to add another Fragment that contains a VideoView to the `content_layout` container, the same container holding our MainFragment. In the sample project clicking the FAB will execute the following Fragment transaction:
The NewFragment instantiates a layout very similar to MainFragment except it has a colored background and an offset VideoView so that it only partially overlaps with the VideoView from MainFragment:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/holo_red_light" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="100dp" android:text="New Fragment" /> <VideoView android:id="@+id/video_view" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
Just like in MainFragment, in `onViewCreated` the NewFragment finds a reference it its VideoView and begins playing it. The NewFragment plays an animated video instead of a regular video to better illustrate the issue. For reference, if the NewFragment was not added on top of our `MainFragment,` but rather existed on its own, it would look like this:
It doesn’t look like this however, which leads us to the problem.
After the NewFragment has been instantiated, it looks like this:
Observe now that NewFragment is completely covering MainFragment. The “Main Fragment” text that used to be at the top has been replaced with “New Fragment.” The white background of MainFragment has also been completely covered by the red background of NewFragment. The only remaining part of the MainFragment that is still visible is the VideoView. The reason that it is only partially visible is because the VideoView in `NewFragment` is lower than the VideoView of the MainFragment.
If the NewFragment VideoView overlaid exactly the VideoView of the MainFragment, it would look like this:
Observe that the MainFragment VideoView is still completely visible while the NewFragment VideoView displaying the animated video is completely obscured.
The reason that the MainFragment VideoView is only visible where it overlaps with the NewFragment VideoView is explained rather eloquently in the SurfaceView documentation, “the SurfaceView punches a hole in its window to allow its surface to be displayed.” This hole punching is what differentiates a SurfaceView (VideoView and GLSurfaceView both extend SurfaceView) from a regular View. This Stack Overflow answer offers further explanation from an Android framework engineer discussing stacking SurfaceViews:
“Sorry, you can’t do this — because surface views are very special and not really views (the surface is a separate window Z-ordered with your own), their Z-ordering does not match your views. A surface view is a big, heavy object; you are not intended to treat SurfaceView like a regular view in this way.”
To ensure the VideoView of the NewFragment appears on top, simply call`setZOrderMediaOverlay(true)`on the VideoView in the NewFragment. This method “Controls whether the surface view’s surface is placed on top of another regular surface view in the window (but still behind the window itself). This is typically used to place overlays on top of an underlying media surface view.”
It may sound obvious, but if you are running into a problem there is a good chance an API is available that will help you out. Always review the API documentation to determine if this is the case. Be sure to not only look at the documentation of the class you are having trouble with, but also any super superclasses that your class extends. This was especially important in my case because VideoView didn’t expose any methods that would have solved this issue, I had to go a level deeper to its superclass SurfaceView.
Besides using `setZOrderMediaOverlay`, there are a couple other solutions that didn’t end up fitting my needs, but may fit yours.
Stopping playback of MainFragment VideoView and setting it to GONE will prevent the `NewFragment` VideoView from being overlapped.
Instead of adding the NewFragment during the transaction, do a replace:
This solution didn’t work for me because I wanted to retain my `MainFragment`.
Before executing the FragmentTransaction to add the NewFragment, I grabbed the View of the MainFragment and set it to GONE. This had no effect on the VideoView issue.
MainFragment fragment = (MainFragment) getSupportFragmentManager().findFragmentByTag("MAIN_FRAGMENT"); fragment.getView().setVisibility(View.GONE); getSupportFragmentManager().beginTransaction().add(R.id.content_layout, NewFragment.newInstance()).commit();
As part of the transaction to add the NewFragment, I hid the MainFragment. This also had no effect on the VideoView issue.
MainFragment fragment = (MainFragment) getSupportFragmentManager().findFragmentByTag("MAIN_FRAGMENT"); getSupportFragmentManager().beginTransaction().hide(fragment).add(R.id.content_layout, NewFragment.newInstance()).commit();
If you enjoyed this post, you can follow me on Twitter @dickclucas, where I talk about Android, technology, and other related topics. Thanks for reading and please feel free to reach out in the comments section below or directly to our team at Raizlabs!