56

I'm trying to implement new Android Google Maps API (v2). However it doesn't seem to go well with SlidingMenu. As you may know, MapFragment implementation is based on SurfaceView. The problem is that the SurfaceView doesn't like moving it around - I mean placing it in movable views:

When you move it, it leaves a black hole in the place where its pixels originally layed. It looks something like this.

This problem can be partially solved by specifying a transparent background on the SurfaceView or even placing a transparent View over it. I just can't figure if only for me the results are unacceptable, or if I have a different problem which makes it look how it looks.

To see how it looks, please watch THIS VIDEO.

(sorry for the quality, but the problem can be seen easily anyway)

When a normal listview (the orange screen) is pulled away while opening SlidingMenu, the animation is smooth and nice. But as soon as the MapFragment appears, there's some weird refreshing/flickering/synchronization issue on the map.

Tested on HTC One S and Samsung Galaxy ACE.


My super-duper-crazy idea of solving it: each time the opening animation starts, take a screenshot of the MapFragment (it should be possible with SurfaceView) and lay it over for the duration of the animation. But I really don't know how to do it... Taking screenshot of a map isn't possible, but maybe someone will get inspired by this.

Or maybe disable redrawing the map some other way, I don't know.


UPDATE:

Found this.

It appears that SurfaceView can be changed to TextureView to remove those limitations. But since MapFragment is based on SurfaceView, I don't know if it can be achieved.

Another update It appears that this issue has been resolved on devices 4.1+. They just used TextureView. but on lower versions we still have to use workarounds.

Community
  • 1
  • 1
Michał Klimczak
  • 12,674
  • 8
  • 66
  • 99
  • 2
    The map will continue to process commands and animate as it is sliding. This is a good UX. Taking a screen shot to cover the map while it slides will detract from that UX and AFAIK, it's not possible, see http://stackoverflow.com/questions/13773658/capture-screen-shot-of-googlemap-android-api-v2 – qubz Jan 20 '13 at 10:29
  • http://stackoverflow.com/a/13910364/1121889 Replace the viewpager with your map v2 – praveenLal Mar 21 '13 at 06:14
  • I don't have a `ViewPager`. Also, I've already read the answer you cited (even commented on it). – Michał Klimczak Mar 21 '13 at 09:33
  • I have the same problem with a surfaceview. I've modified the surfaceView to a textureView. I don't know if it's possible with the mapfragment. – Martijn Mellens Sep 02 '13 at 11:05
  • I think its acceptable. I also think it will eventually be repaired. If there was an issue to click on at google I'd star it for you. – danny117 Sep 18 '13 at 13:50
  • The issue is appearing again with play services version 6.7.74 – Renjith Feb 20 '15 at 17:49
  • How am I getting upvotes on this after 9 years?! – Michał Klimczak Feb 02 '22 at 20:44

16 Answers16

14

Of course the proper solution will be for Google to fix the problem (see Android Maps V2 issue 4639: http://code.google.com/p/gmaps-api-issues/issues/detail?id=4639).

However, one of my coworkers suggested simply extending the map beyond its container. If we extend the map fragment beyond the visible region of its container, like so:

android:layout_marginLeft="-40dp"
android:layout_marginRight="-40dp"

we can reduce/eliminate the flickering. Newer devices (e.g. Galaxy Nexus) show no flickering after this hack, and older devices (e.g. LG Optimus V) show reduced flickering. We have to extend margins on both sides so that info windows are centered when they're selected.


Update: This issue was fixed by Google on Aug. 28, but I'm guessing it still has to be rolled into a release.

Daniel Schuler
  • 3,130
  • 2
  • 30
  • 28
4

Here's a crazy idea, don't move the MapView ;-)

That is, create a MapView full screen width behind your app in a FrameLayout. Then lay your views on top, and punch a hole through to the MapView in the position you need to display it. When moving you'll need to update the map position to stay in sync so the user keeps seeing the same section of the map, but that should update better than moving a surface view around.

Ryan
  • 2,240
  • 17
  • 17
  • 6
    That's interesting but would be terrible to implement, especially in a viewpager or something where you need multiple MapFragments and each displays different map. And `SlidingMenu` moves the whole "above" view aside, so that would mean I'd have to entirely change the way the library works. Otherwise, very interesting, so +1 :) – Michał Klimczak Jan 22 '13 at 08:21
  • Completely agree it would be hard / not wise to implement as a generic solution, but could just work for a specific one off use case. Hopefully, it may help nudge someone more intelligent to consider a better solution ;-) – Ryan Jan 22 '13 at 12:46
  • Here's hoping for a much simpler solution. I imagine using a map with a sliding menu will grow in demand and become more than just a one off use case. – qubz Jan 23 '13 at 00:52
  • I had a mapview inside a viewpager inside a dialog. Solved by putting the map behind the view pager and making the viewpager's first item a transparent placeholder (map gestures are disabled). – James Wald Jun 04 '13 at 00:47
4

add android:hardwareAccelerated="true" in your manifest file.

eg: <application android:name=".Sample" android:hardwareAccelerated="true" />

3

Well, Quoting Dianne Hackborn (an Android framework engineer)

The surface view is actually BEHIND your window, and a hole punched in the window for you to see it. You thus can put things on top of it in your window, but nothing in your window can appear behind it. Original post

This architecture is part of why you see these flickers.

Some flickering sometimes appear due to double buffers: Android coding blog explains further.

You can also try and subclass the SurfaceView to try and draw the background differently. I haven't seen an example of this that doesn't change the z-index to be that of the top. Check out this SO post

Otherwise, I recommend only getting the SurfaceHolder after your view is focused (try and make a temporary view until then) and removing the SurfaceView whilst not in focus and on screen.

Community
  • 1
  • 1
Ben Max Rubinstein
  • 1,803
  • 1
  • 13
  • 15
  • 1
    Strange thing is, when you `setZOrderOnTop(true)`, the flicker doesn't disappear. It's not black anymore, it becomes white. But it's still there. There surely is some problem with performance or synchronization, because the black background doesn't remain there, it only flickers for a fraction of second. If I could synchronize the frames of SlidingMenu animation with refreshing of SurfaceView, it would probably disappear... – Michał Klimczak Jan 28 '13 at 22:07
  • It's true, but that actually only allows you to avoid the high visibility of the issue, by making sure the SurfaceView's background color is the same as the color of it's container's background. I don't think that you should try and synchronise frame rates or other possibly complicated properties, as such a behaviour even exists in the slowest movements controlled by your hand. As I mentioned, you could draw programatically inside your canvas, by subclassing the SurfaceView, but that majorly degrades it's performance. – Ben Max Rubinstein Jan 28 '13 at 22:12
  • I don't think I follow. What would "drawing inside canvas" change? I thought that MapFragment is drawing inside canvas, but it's not fast enough. If you mean changing the background so it matches the foreground map (black flicker appears in the place where map should be, not in the place of the SlidingMenu), then I doubt it's possible, because the map isn't a solid background, I would have to take a screenshot or something like this. – Michał Klimczak Jan 28 '13 at 22:20
2

In my scenario, I had an initial Fragment, which on a button click swapped in a second fragment with a MapFragment inside it. I noticed that this flicker only occurred the first time you would swap the fragment - popping the back stack, and swapping in another fragment of the same type did not cause a flicker.

So figuring it had something to do with adding an initial SurfaceView to the window, I tried adding an empty instance of a SurfaceView to my initially loaded fragment, behind it's main view. And bingo, no more flicker when animating in the next fragment!

I know it's not the cleanest solution, but it works. The Google MapFragment (or SupportMapFragment in my case) still works fine, as the unused SurfaceView is in the previous fragment.

I should add I am using the v2 API.

Update @VenomVendor

I add the empty SurfaceView to the first fragment to be shown, at index 0 - behind the main view. In my case, the Home fragment is the previous fragment to one that contains the Google Maps fragment, not sure if that matters:

import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class HomeFragment extends FragmentBase {
private SurfaceView sview;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_home, container, false);

    //this fixes google maps surface view black screen animation issue
    sview = new SurfaceView(getActivity());
    sview.setZOrderOnTop(true);    // necessary
    SurfaceHolder holder = sview.getHolder();
    holder.setFormat(PixelFormat.TRANSPARENT);

    container.addView(sview, 0);
    ...
}
Breeno
  • 3,007
  • 2
  • 31
  • 30
2

Taking screenshot of a map isn't possible, but maybe someone will get inspired by this.

It's possible with the snapshot interface from GoogleMap. Now you can display a snapshot of the map while scrolling the page. With this I solved my problem with the ViewPager, maybe you can change the code to fit your SlidingMenu.

Here is my code to setting the snapshot (it's in the same fragment as map, called FragmentMap.java):

public void setSnapshot(int visibility) {
    switch(visibility) {
    case View.GONE:
        if(mapFragment.getView().getVisibility() == View.VISIBLE) {
            getMap().snapshot(new SnapshotReadyCallback() {
                @Override
                public void onSnapshotReady(Bitmap arg0) {
                    iv.setImageBitmap(arg0);
                }
            });
            iv.setVisibility(View.VISIBLE);
            mapFragment.getView().setVisibility(View.GONE);
        }
        break;
    case View.VISIBLE:
        if(mapFragment.getView().getVisibility() == View.GONE) {
            mapFragment.getView().setVisibility(View.VISIBLE);
            iv.setVisibility(View.GONE);
        }
        break;
    }
}

Where "mapFragment" is my SupportedMapFragment and "iv" is an ImageView (make it match_parent).

And here I am controlling the scroll:

pager.setOnPageChangeListener(new OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            if(position == 0 && positionOffsetPixels > 0 || position == 1 && positionOffsetPixels > 0) {
                ((FragmentMap)adapter.getRegisteredFragment(1)).setSnapshot(View.GONE);
            } else if(position == 1 && positionOffsetPixels == 0) {
                ((FragmentMap)adapter.getRegisteredFragment(1)).setSnapshot(View.VISIBLE);
            }
        }
        @Override
        public void onPageScrollStateChanged(int arg0) {}
        @Override
        public void onPageSelected(int arg0) {}
    });

My fragment with map (FragmentMap) is on position 1, so I need to controll the scroll from position 0 to 1 and from position 1 to 2 (the first if-clause). "getRegisteredFragment()" is a function in my custom FragmentPagerAdapter, in which I have a SparseArray(Fragment) called "registeredFragments".

So, whenever you scroll to or from your map, you always see a snapshot of it. This works very well for me.

Namenlos
  • 1,615
  • 2
  • 18
  • 24
2

This worked for me... Add map:zOrderOnTop="true" to your fragment as this...

<fragment
                android:id="@+id/map"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:name="com.google.android.gms.maps.SupportMapFragment""
                map:zOrderOnTop="true"/>

Remember to add this to your parent ScrollView

xmlns:map="http://schemas.android.com/apk/res-auto"
dianakarenms
  • 2,609
  • 1
  • 22
  • 22
1

To prevent the MapView(it is actually a SurfaceView) creating the black holes, I add a empty view with transparent background color to cover the whole MapView, such that it can prevent MapView from generating a black "hole". It may work for your situation.

Acheese
  • 135
  • 1
  • 11
  • It works to prevent the "black hole" problem, but not the problem I describe. See this part of my question: "This problem can be partially solved by specifying a transparent background on the SurfaceView or even placing a transparent View over it..." – Michał Klimczak Mar 07 '13 at 07:15
  • Oh~ sry that i didn't notice that 8-) – Acheese Mar 08 '13 at 03:00
1

I got same problem with a webview and SlidingMenu and I solved by modifying SlidingMenu source code as explained here by main developer.

Comment out the hardware acceleration in the manageLayers method of SlidingMenu. This seems to be the only solution since SurfaceViews don't work.

sourcerebels
  • 5,140
  • 1
  • 32
  • 52
1

Another alternative solution is to use the new GoogleMap.snapshot() function, and using a screenshot of the map instead; as described here.

Community
  • 1
  • 1
joecks
  • 4,539
  • 37
  • 48
1

try adding..

getHolder().setFormat(PixelFormat.TRANSLUCENT);

to your SurfaceView's constructor. The TextureView should also work, but will require you to drop support for sub api 14 devices.

Lee
  • 21
  • 2
0

I had same issue of black screen on scrolling of list view, i was using map fragment with list view in same screen i have resolved this issue with use of the magical property in xml where i am talking list view just we have to put android:scrollingCache="false".my issue is fixed try this property to stop lagging and flickering in your maps.

0

Guys the best solution I found was to instantiate your support map through this. Does wonders

SupportMapFragment mapFragment = SupportMapFragment.newInstance(new GoogleMapOptions().zOrderOnTop(true));
SoH
  • 2,180
  • 2
  • 24
  • 53
0

Change Your FrameLayout to RelativeLayout if Possible

Reza
  • 151
  • 1
  • 9
0

I had the same problem and used a workaround based on changing the "Visibility".

private GoogleMap mMap;
private SupportMapFragment mMapFragment;

mMapFragment = ((SupportMapFragment)getSupportFragmentManager().findFragmentById(R.id.mapFragment));
mMap = mMapFragment.getMap();

//Change the visibility to 'INVISIBLE' before the animation to go to another fragment.
mMapFragment.getView().setVisibility(View.INVISIBLE);

//Change the visibility to 'VISIBLE' after coming back to the original fragment that has map fragment.
mMapFragment.getView().setVisibility(View.VISIBLE);
kalan nawarathne
  • 1,944
  • 27
  • 26
-1

Here's a very simple workaround I used to get rid of the flashing in a ViewPager. I would imagine the same will hold true for any Fragment or other dynamically added MapView.

The problem doesn't really seem to be the MapView itself, but the resources needed to run the MapView. These resources are only loaded the first time you fire up a MapView in your activity and re-used for each subsequent MapView, as you would expect. This loading of resources is what causes the screen to flash.

So to remove the flashing, I just included another MapView in the layout of my Activity (location in the activity is irrelevant). As soon as the Activity has loaded, I just set the Visibility of the extra MapView to GONE. This means all the resources needed for your MapView are ready for when you fire up any of your Fragments using MapViews with no lag, no flashing, all happiness.

This is of course a workaround and not a real 'solution' to the underlying problem but it will resolve the side effects.

So to cover off the code used for completeness:

Randomly (but cleanly) placed in my Activity Layout:

<com.google.android.gms.maps.MapView
        android:id="@+id/flash_map"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

Then in my Activity Class

private MapView flashMap;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

            // Code omitted

    try {
        MapsInitializer.initialize(this);
    } catch (GooglePlayServicesNotAvailableException e) {
        e.printStackTrace();
    }
    flashMap = (MapView)findViewById(R.id.flash_map);
    flashMap.onCreate(savedInstanceState);
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
        flashMap.onSaveInstanceState(outState);
}

@Override
public void onResume() {
    super.onResume();
    flashMap.onResume();
    flashMap.setVisibility(View.GONE); // Just like it was never there...
}

@Override
public void onPause() {
    flashMap.onPause();
    super.onPause();
}

@Override
public void onDestroy() {
    flashMap.onDestroy();
    super.onDestroy();
}

@Override
public void onLowMemory() {
    super.onLowMemory();
    flashMap.onLowMemory();
}
Scott
  • 2,593
  • 1
  • 22
  • 23
  • 1
    But you're using the deprecated Map API v1 and it has nothing to do with the `SurfaceView` and my question. I know what you're talking about, I had the same problem when I tried to instantiate multiple MapViews, but it's not an answer to my question, unfortunately. – Michał Klimczak Jan 30 '13 at 09:47
  • This is using Map API v2. "com.google.android.gms.maps.MapView". The GMS bit is v2. – Scott Jan 31 '13 at 03:23
  • Maybe I'll explain a little more. I think the flicker you are experiencing is not because of the movement of the Map. I have the map on a ViewPager and it's more than happy to slide around. There is a black edge when it moves quickly but not the flicker you are talking about. In my testing the flicker was due to resource loading which I found could be avoided by effectively pre-caching the Maps data when loading the activity. – Scott Jan 31 '13 at 03:32
  • "There is a black edge when it moves quickly but not the flicker you are talking about" - isn't it the same thing? Did you see the video in the question? How it differs in your ViewPager? Sorry for misreading your answer and that "v1" comment. – Michał Klimczak Jan 31 '13 at 12:18
  • Ah right, so you are just moving the MapView so fast that it appears to flash, the same effect as the screen flash you get from late loading the MapView on a fragment. Yeah you are right, I'm addressing a slightly different but similar looking problem. – Scott Feb 01 '13 at 05:31
  • 4
    This is not a solution to the problem at hand. – qubz Feb 03 '13 at 10:18