Android向けGoogleマップv2:完全な再描画と入力イベントをサポートするポップアップウィンドウ

画像



廃止されたAndroid用Google Maps v1ライブラリの発表により、キーの発行も終了しました。 開発者には新しいバージョンが提供されました。古いバージョンよりも速く、 高く、強く 、便利です。 地図上のいくつかのポイントと、画像、小さな説明、ボタンが付いたシンプルなポップアップウィンドウを使用して表示しようとしましたが、それはカットの下で読みました。



Google Maps v2は、多くの点で旧バージョンよりも便利です。 フラグメント上に構築されたアプリケーションにマップを埋め込むことが不可能なMapFragment



価値は何ですか。 または、たとえば、ライブラリの最初のバージョンのように、すべてのMapView



ではなく、 AndroidManifest.xml



単一の場所でキーを指定する機能などの些細なことです。 Fluent Interfaceなどの新機能。 美人



しかし、私たちは熱心に終わり、仕事に取りかかります。 カードにマーケットを追加するのは簡単ですが、ポップアップウィンドウを表示しようとすると待ち伏せされます。 箱から出して、タイトルとサブタイトルのみを追加できます。 InfoWindowAdapter



もありますが、それは...ええと...多少制限されています。 ドキュメントには次のことが記載されています。

注:描画される情報ウィンドウはライブビューではありません。 ビューは、返される時点で(View.draw(Canvas)を使用して)イメージとしてレンダリングされます。 これは、ビューに対するその後の変更がマップ上の情報ウィンドウに反映されないことを意味します。 情報ウィンドウを後で更新するには(たとえば、画像が読み込まれた後)、showInfoWindow()を呼び出します。


つまり、 showInfoWindow()



呼び出してウィンドウを手動で再描画する必要があります。 これは、たとえば、ネットワークからダウンロードした画像を表示するには十分ですが、たとえば、常に再描画する必要があるProgressBar



はどうでしょうか? ウィンドウ内のView



状態(押されている、フォーカスされているなど)を描画する方法



そこから:

さらに、情報ウィンドウは、タッチイベントやジェスチャイベントなどの通常のビューに典型的な対話性を尊重しません。 ただし、以下のセクションで説明するように、情報ウィンドウ全体で一般的なクリックイベントをリッスンできます。


したがって、入力イベントはありません。 私たちが持っているすべてのインタラクティブ機能は、ウィンドウ全体をクリックすることに対する反応であり、これはOnInfoWindowClickListener



を使用してOnInfoWindowClickListener



できます。



ソリューションを見つけると、Stack Overflowに関する質問が明らかになります。 受け入れられた回答の著者は、 MapView



をラップし、状態を手動で切り替えることで入力イベントをインターセプトすることを提案しています。 彼によると、これは非常に退屈であり、これをすべて正常に動作させることは不可能です。 これをあきらめることができるようです。



しかし、方法があります。 ポップアップウィンドウを描画するマップの上に別のレイヤーを追加するとどうなりますか? マップをスクロールするとき、マップ上のマーカーを「追跡」するために、それに応じてポップアップウィンドウの位置を変更します。 コンテナ内のView



の位置を座標によって変更するために、何らかの自転車を思い付くことができますが、幸いなことに、私たちは自由に時代遅れではあるが完全には見られないAbsoluteLayout



を使用していることを思い出します)



その結果、およそ次のマークアップが得られます(以降、明確にするために、いくつかの詳細は省略します)。

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <FrameLayout android:id="@+id/container_map" android:layout_width="wrap_content" android:layout_height="wrap_content" > <!--  --> </FrameLayout> <AbsoluteLayout android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:id="@+id/container_popup" android:layout_x="0dp" android:layout_y="0dp" android:layout_width="wrap_content" android:layout_height="wrap_content" > <!--   --> </LinearLayout> </AbsoluteLayout> </RelativeLayout>
      
      







コードは次のとおりです。

 public class MapFragment extends com.google.android.gms.maps.MapFragment { //    private static final int ANIMATION_DURATION = 500; //       private LatLng trackedPosition; //  ,       private int popupXOffset; private int popupYOffset; //  private int markerHeight; //,         private ViewTreeObserver.OnGlobalLayoutListener infoWindowLayoutListener; //   private View infoWindowContainer; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment, null); FrameLayout containerMap = (FrameLayout) rootView.findViewById(R.id.container_map); View mapView = super.onCreateView(inflater, container, savedInstanceState); containerMap.addView(mapView, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); infoWindowContainer= rootView.findViewById(R.id.container_popup); //      infoWindowLayoutListener = new InfoWindowLayoutListener(); infoWindowContainer.getViewTreeObserver().addOnGlobalLayoutListener(infoWindowLayoutListener ); return rootView; } @Override public void onDestroyView() { super.onDestroyView(); //   infoWindowContainer.getViewTreeObserver().removeGlobalOnLayoutListener(infoWindowLayoutListener ); } @Override public void onMapClick(LatLng latLng) { //          ,    infoWindowContainer.setVisibility(INVISIBLE); } @Override public boolean onMarkerClick(Marker marker) { //     //   GoogleMap map = getMap(); Projection projection = map.getProjection(); trackedPosition = marker.getPosition(); //  Point trackedPoint = projection.toScreenLocation(trackedPosition); trackedPoint.y -= popupYOffset / 2; LatLng newCameraLocation = projection.fromScreenLocation(trackedPoint); map.animateCamera(CameraUpdateFactory.newLatLng(newCameraLocation), ANIMATION_DURATION, null); //    //… infoWindowContainer.setVisibility(VISIBLE); return true; } private class InfoWindowLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener { @Override public void onGlobalLayout() { //  ,   popupXOffset = infoWindowContainer.getWidth() / 2; popupYOffset = infoWindowContainer.getHeight(); } } }
      
      







最後に、最も重要で興味深い部分は、マップ上のマーカーの「追跡」です。 最初に思いついたのは、 onTouchEvent()



で入力イベントをキャッチし、同じ場所でウィンドウの位置を変更することでした。 判明したように、このメソッドは完全に不適切です。入力イベントはonTouchEvent()



に入る前にグループ化されるため、任意の頻度で呼び出されるため、ポップアップウィンドウはぎくしゃく移動します。 それは嫌に見えます。



別の考えが浮上しました-ウィンドウの位置を定期的に更新してみませんか? Handler



Runnable



を支援します。



 public class MapFragment extends com.google.android.gms.maps.MapFragment { //    . //   60 fps,   1000 ms / 60 = 16 ms  . private static final int POPUP_POSITION_REFRESH_INTERVAL = 16; //Handler,       private Handler handler; //Runnable,     private Runnable positionUpdaterRunnable; @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); handler = new Handler(Looper.getMainLooper()); positionUpdaterRunnable = new PositionUpdaterRunnable(); //   handler.post(positionUpdaterRunnable); } @Override public void onDestroyView() { super.onDestroyView(); // handler.removeCallbacks(positionUpdaterRunnable); handler = null; } private class PositionUpdaterRunnable implements Runnable { private int lastXPosition = Integer.MIN_VALUE; private int lastYPosition = Integer.MIN_VALUE; @Override public void run() { //      handler.postDelayed(this, POPUP_POSITION_REFRESH_INTERVAL); //   ,    if (trackedPosition != null && infoWindowContainer.getVisibility() == VISIBLE) { Point targetPosition = getMap().getProjection().toScreenLocation(trackedPosition); //    ,    if (lastXPosition != targetPosition.x || lastYPosition != targetPosition.y) { //  overlayLayoutParams = (AbsoluteLayout.LayoutParams) infoWindowContainer.getLayoutParams(); overlayLayoutParams.x = targetPosition.x - popupXOffset; overlayLayoutParams.y = targetPosition.y - popupYOffset - markerHeight; infoWindowContainer.setLayoutParams(overlayLayoutParams); //   lastXPosition = targetPosition.x; lastYPosition = targetPosition.y; } } } } }
      
      







それの由来-下のビデオを見てください。





残念ながら、ビデオ録画とエミュレーターの両方のプルが不十分であるため、ビデオはパフォーマンスの概念を提供しません。 Nexus 7 2013で4.4.2を使用したテストでは、ポップアップウィンドウが表示されると、スクロールの滑らかさがわずかに悪化しますが、過大ではありませんでした。 一般に、4.4.2の古いNexus Sでも使用できます。



コードはGitHubに投稿されています: github.com/deville/info-window-demo



All Articles