问题
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