Custom ViewPager to allow child GoogleMap control to scroll horizontally

瘦欲@ 提交于 2019-12-06 02:26:39

问题


I am using a GoogleMap (v2 api) inside of a Fragment inside of a ViewPager. The original issue I was having was that the ViewPager was capturing all horizontal scrolling gestures so the map could only be dragged if the user started in the vertical or diagonal directions. All horizontal dragging was captured by the ViewPager. I found that the solution is to create a derived ViewPager class and override the canScroll method. The View being dragged is passed to this method, so you can make a decision based on the type of View it is.

In my case, my Fragment is a SupportMapFragment (from the com.google.android.gms.maps package) which gives me a GoogleMap object to interact with. However, the actual control I receive in the ViewPager's canScroll method is of type maps.j.b. I have fixed the problem with the following code inside my custom ViewPager class. However, I don't feel comfortable checking for this class name when I don't know anything about it. What is maps.j.b? Is this a dynamically created class at runtime? Is it possible for the type to change in a future update of the framework? Given only the View object, is there a more robust way of checking that the drag was initiated by a map control?

@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
    if(v.getClass().getName().equals("maps.j.b")) {
        return true;
    }
    return super.canScroll(v, checkV, dx, x, y);
}

回答1:


Is this a dynamically created class at runtime?

It's probably the output of a ProGuard rename.

Is it possible for the type to change in a future update of the framework?

AFAIK, yes. I am not aware that ProGuard guarantees that the names will stay the same between builds.

Note that this will be dependent upon the library project. When you take on a new edition of the library project, the class name may change. But, since that's tied to your own app, in a pinch, you could stick with the name game, and just need to fix up the name when you take on a new edition of the library project.

That being said, I agree that matching based on name is icky.

Given only the View object, is there a more robust way of checking that the drag was initiated by a map control?

Well, since this isn't a MapView, we have no way of knowing what precisely a maps.j.b is. We know it is not a MapView, as it is not named MapView, and Google left that name alone (via -keep directives in the ProGuard configuration, presumably) so we can use it from our apps.

You could try creating your own MapFragment with your own MapView and see if there you get a MapView instead of a maps.j.b (which might be some internal subclass of MapView used by SupportMapFragment). If you get passed the actual MapView, then you can do direct equality checks to see if the passed-in object is your MapFragment's MapView.

Or, you could see if instanceof says that a maps.j.b is a MapView.

Neither of those are guaranteed reliable over time, either, as conceivably Google could change matters such that the MapView is wrapped in something. However, my guess is that it is more likely to be stable than the generated class name.

maps.j.b inherits from SurfaceView. Hence, one way to semi-reliably detect whether or not a View passed into canScroll() is to test if (v instanceof SurfaceView). This fails if Maps V2 does something else later (e.g., extends TextureView on API Level 11+) or if your ViewPager pages have another SurfaceView (e.g., a VideoView).

I have filed an issue on this to try to get some stable means of making this determination added to the Maps V2 API.




回答2:


First of all, @Rich thanks for sharing your solution - it was something I was looking for. I have a View Pager with a fragment extending SupportMapfragment being one of its items. In my solution I have a custom XML layout file since I needed to add some extra Buttons to the fragment. Here's my solution:

public class MapFragmentScrollOverrideViewPager extends ViewPager {

public MapFragmentScrollOverrideViewPager(Context context) {
    super(context);
}

public MapFragmentScrollOverrideViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
    if(v instanceof MapHoldingRelativeLayout) {
        return true;
    }
    return super.canScroll(v, checkV, dx, x, y);
} }

public class MapHoldingRelativeLayout extends RelativeLayout {

public MapHoldingRelativeLayout(Context context) {
    super(context);
}

public MapHoldingRelativeLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public MapHoldingRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}  }

Fragment layout file:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    xmlns:android="http://schemas.android.com/apk/res/android">

    <Button 
        android:id="@+id/switchNearbyOffersButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="XXXX"
        />

    <xxx.view.MapHoldingRelativeLayout
        android:id="@+id/mapLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/switchNearbyOffersButton"
         />

</RelativeLayout>

The fragment itself:

public final class NearbyOffersFragment extends SupportMapFragment {

    public static NearbyOffersFragment newInstance(String content) {
        NearbyOffersFragment fragment = new NearbyOffersFragment();
        return fragment;
    }

    private GoogleMap googleMap;

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

    private void setUpMapIfNeeded() {
        if (googleMap == null) {
            googleMap = getMap();
            if (googleMap != null) {
                setUpMap();
            }
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        final View rootView = inflater.inflate(R.layout.fragment_nearby_offers,
                container, false);
        final View mapFragmentLayout = super.onCreateView(inflater, container,
                savedInstanceState);
        MapHoldingRelativeLayout mapLayout = (MapHoldingRelativeLayout) rootView
                .findViewById(R.id.mapLayout);
        mapLayout.addView(mapFragmentLayout);
        return rootView;
    }
    @Override
    public void onResume() {
        super.onResume();
        setUpMapIfNeeded();
    }

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

    private void setUpMap() {
        googleMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
        googleMap.getUiSettings().setZoomControlsEnabled(true);
        googleMap.getUiSettings().setAllGesturesEnabled(true);
        CameraPosition cameraPosition = new CameraPosition.Builder()
                .target(new LatLng(41.474856, -88.056928))
                .zoom(14.5f)
                .build();
        googleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
    }
}

Since the original SupportMapFragment's layout is inside of my custom RelativeLyout you can use the instanceof operator and not reference the maps.j.b class directly...




回答3:


Hope this helps a year later... In my case I solved this problem by ignoring the View object. What you can do is notify the CustomViewPager so that the current page contains a map.

public class CustomViewPager extends ViewPager {
    @Override
    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
        if (this.containsMap()) {
            return true;
        }
        return super.canScroll(v, checkV, dx, x, y);
    }
}

In my case containsMap() just check the current page corresponds to a predefined page index:

public boolean containsMap(){
    return getCurrentItem() == MAP_PAGE;
}

But it could be more elaborate: add a method called setCanScrollIndex(int index, boolean canScroll) to keep a reference of those pages that contains a map. Call it from your activity when appropriate.



来源:https://stackoverflow.com/questions/14388494/custom-viewpager-to-allow-child-googlemap-control-to-scroll-horizontally

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!