【问题标题】:Custom ViewPager to allow child GoogleMap control to scroll horizontally自定义 ViewPager 以允许子 GoogleMap 控件水平滚动
【发布时间】:2014-03-21 19:11:17
【问题描述】:

我在 ViewPager 的 Fragment 内使用 GoogleMap (v2 api)。我遇到的最初问题是 ViewPager 正在捕获所有水平滚动手势,因此只有在用户从垂直或对角方向开始时才能拖动地图。 ViewPager 捕获了所有水平拖动。我发现解决方案是创建一个派生的ViewPager 类并覆盖canScroll 方法。被拖动的 View 会传递给该方法,因此您可以根据它的 View 类型做出决定。

在我的例子中,我的 Fragment 是一个 SupportMapFragment(来自 com.google.android.gms.maps 包),它给了我一个 GoogleMap 对象来与之交互。但是,我在 ViewPager 的canScroll 方法中收到的实际控制是maps.j.b 类型。我已经用我的自定义 ViewPager 类中的以下代码解决了这个问题。但是,当我对此一无所知时,我对检查这个类名感到不舒服。什么是 maps.j.b?这是在运行时动态创建的类吗?在框架的未来更新中,类型是否可能发生变化?仅给定 View 对象,是否有更可靠的方法来检查拖动是否由地图控件启动?

@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);
}

【问题讨论】:

    标签: java android android-viewpager google-maps-android-api-2


    【解决方案1】:

    这是在运行时动态创建的类吗?

    这可能是 ProGuard 重命名的输出。

    在框架的未来更新中,类型是否可能发生变化?

    AFAIK,是的。我不知道 ProGuard 保证名称在构建之间保持不变。

    请注意,这将取决于库项目。当您使用新版本的库项目时,类名可能会更改。但是,由于这与您自己的应用程序相关联,因此在紧要关头,您可以坚持使用名称游戏,并且只需要在使用新版本的库项目时修改名称。

    话虽如此,我同意基于名称的匹配是 icky。

    仅给定 View 对象,是否有更可靠的方法来检查拖动是否由地图控件启动?

    好吧,既然这不是MapView,我们无法知道maps.j.b 到底是什么。我们知道它不是MapView,因为它没有被命名为MapView,Google 只保留了这个名称(大概是通过ProGuard 配置中的-keep 指令),因此我们可以在我们的应用程序中使用它。

    你可以尝试用你自己的MapView 创建你自己的MapFragment,看看你是否得到MapView 而不是maps.j.b(这可能是MapView 使用的一些内部子类SupportMapFragment)。如果你通过了实际的MapView,那么你可以直接进行相等性检查,看看传入的对象是否是你的MapFragmentMapView

    或者,您可以查看instanceof 是否表示maps.j.bMapView

    随着时间的推移,这些都不能保证可靠,因为可以想象,谷歌可以改变事情,例如 MapView 被包裹在某些东西中。但是,我的猜测是它比生成的类名更可能是稳定的。

    maps.j.b 继承自 SurfaceView。因此,半可靠地检测View 是否传递到canScroll() 的一种方法是测试(v instanceof SurfaceView) 是否传递。如果 Maps V2 稍后执行其他操作(例如,在 API 级别 11+ 上扩展 TextureView)或您的 ViewPager 页面有另一个 SurfaceView(例如,VideoView),则会失败。

    我已提交an issue on this 以尝试获得一些稳定的方法来将此确定添加到 Maps V2 API。

    【讨论】:

    • 您很可能是正确的,因为它是 ProGuard 输出,因为运行时实例字段也是单字符名称。我在运行时获得的类型在编译时对我来说是不可用的,所以我不知道我是否能够用 instanceof 做任何事情。考虑到我今天使用的类型名称取决于我使用的库的版本,我想我可以将它包装在一个单元测试中以检查更改。我会在运行时继续检查对象和它的类,看看我是否能找到更稳定的东西。一如既往,感谢您的帮助和意见。
    • @Rich:“我在运行时获得的类型在编译时对我不可用”——不,但instanceof 仍然可以工作。您只需要确定它是MapView 还是某个子类,instanceof 会遍历继承链。
    • 有没有办法让我在运行时查看继承链(例如,在 Eclipse 中的“表达式”窗口中)?另外,我想到了一种更简单的方法,因为我的地图填满了所有可用空间。由于提供给 ViewPager 的适配器明确说明了哪个片段进入哪个位置,我也可以只检查位置和 vCheck 布尔值,这应该意味着我在视图层次结构的叶子(没有更多的孩子要检查)。我刚刚测试了该方法,到目前为止它有效。
    • @Rich:“有没有办法让我在运行时查看继承链”——instanceofgetClass()
    • @Rich:FWIW,我对此进行了更多研究——请参阅更新的答案。
    【解决方案2】:

    首先,@Rich 感谢您分享您的解决方案 - 这是我一直在寻找的东西。我有一个视图寻呼机,其片段扩展 SupportMapfragment 是其项目之一。在我的解决方案中,我有一个自定义 XML 布局文件,因为我需要向片段添加一些额外的按钮。这是我的解决方案:

    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);
    }  }
    

    片段布局文件:

    <?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>
    

    片段本身:

    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));
        }
    }
    

    由于原始 SupportMapFragment 的布局在我的自定义 RelativeLyout 内,因此您可以使用 instanceof 运算符而不直接引用 maps.j.b 类...

    【讨论】:

      【解决方案3】:

      希望这对一年后有所帮助... 就我而言,我通过忽略 View 对象解决了这个问题。您可以做的是通知 CustomViewPager 以便当前页面包含地图。

      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);
          }
      }
      

      在我的情况下 containsMap() 只需检查当前页面是否对应于预定义的页面索引:

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

      但它可能更复杂:添加一个名为 setCanScrollIndex(int index, boolean canScroll) 的方法来保留对包含地图的那些页面的引用。在适当的时候从你的活动中调用它。

      【讨论】:

      • 这就是我最终要做的 - 根据地图所在的预定义索引检查 getCurrentItem 的位置。我喜欢你关于更好地公开功能的想法,因为它是一种更可重用的设计。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多