📜 ⬆️ ⬇️

Google Maps v2 for Android: Pop-up window with full redraw and input event support

image

With the announcement of the Google Maps v1 library for Android, the issuing of keys has ceased. Developers were offered a new version - faster, higher, stronger, better, more comfortable than the old one. What was it worth trying to display with its use a few points on the map and a simple pop-up window with an image, a small description and a button - read under the cut.

Google Maps v2 is much more convenient than the old version. That only costs MapFragment , without which embedding maps into an application built on fragments is impossible. Or, for example, such a trifle as the ability to specify a key in a single place in AndroidManifest.xml , and not in each MapView , as in the first version of the library. New chips, like Fluent Interface. Beauty.

But we stop to admire and get down to business. Adding markets to the map is a trifle, but when you try to display a pop-up window, you are ambushed. Out of the box you can add only the title and subtitle. There is another InfoWindowAdapter , but it ... uh ... is somewhat limited. Documentation tells us:
Note: This is a live view. The view is rendered as an image (using View. Draw (Canvas)) at the time it is returned. This is a reflection on the map. To update the info window later (for example, after an image has loaded), call showInfoWindow ().

That is, the window must be redrawn manually by calling showInfoWindow() . This is enough to, for example, display an image downloaded from the network, but what about, for example, a ProgressBar 'which needs to be redrawn constantly? How to draw states (pressed, focused, etc.) View in a window?
')
From the same place:
Furthermore, it is typical for a normal view, such as touch or gesture events. However, you can’t read it in this section below.

So no input events. All the interactivity that we have is a reaction to pressing the whole window, which can be OnInfoWindowClickListener using OnInfoWindowClickListener .

Finding solutions reveals a question on stack overflow . The author of the accepted answer offers to wrap MapView and intercept input events by manually switching states. According to him, this is extremely dreary, and it is not possible to make it all work normally. It would seem, on this you can give up.

But there is a way out. What if we add another layer above the map, in which we will draw a pop-up window? When scrolling the map, we will accordingly change the position of the pop-up window in order to “track” the marker on the map. In order to change the position of some View in the container, you can think of a bike, but, fortunately, there is no need - we remember that we have an outdated but not completely cut AbsoluteLayout (we don’t incinerate me for using it). ).

As a result, we obtain approximately the following markup (hereinafter, for clarity, a number of details are omitted):
 <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> 


The code looks like this:
 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(); } } } 


Finally, the most important and interesting part is “tracking” the marker on the map. The first thought that came to mind was capturing input events in onTouchEvent() and changing the position of the window in the same place. As it turned out, the method is completely useless - due to the fact that input events are grouped before it gets into onTouchEvent() , it is called with an arbitrary periodicity, so the pop-up window moves in spurts. Looks disgusting.

Another thought appeared - why not update the window position at regular intervals of time? Handler and Runnable to help:

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


What came out of it - look at the video below.


Unfortunately, simultaneously recording video and the emulator pulls badly, so the video does not give an idea of ​​performance. Tests on Nexus 7 2013 with 4.4.2 showed that when displaying a pop-up window, the smoothness of scrolling deteriorates a bit, but not too much. In general, it is usable even on an old Nexus S with 4.4.2.

The code is posted on GitHub: github.com/deville/info-window-demo .

Source: https://habr.com/ru/post/213415/


All Articles