summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java
blob: d9fc9e62ae36b6be3c53f91722e0b0ebea679fbb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
package com.mapbox.mapboxsdk.annotations;

import android.graphics.PointF;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.Pools;
import android.view.View;

import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Projection;
import com.mapbox.mapboxsdk.utils.AnimatorUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Interface for interacting with ViewMarkers objects inside of a MapView.
 * <p>
 * This class is responsible for managing a {@link MarkerView} item.
 * </p>
 */
public class MarkerViewManager {

    private Map<MarkerView, View> mMarkerViewMap;
    private MapboxMap mapboxMap;
    private MapView mapView;
    private List<MapboxMap.MarkerViewAdapter> markerViewAdapters;
    private long mViewMarkerBoundsUpdateTime;
    private MapboxMap.OnMarkerViewClickListener onMarkerViewClickListener;

    /**
     * Creates an instance of MarkerViewManager.
     *
     * @param mapboxMap the MapboxMap associated with the MarkerViewManager
     * @param mapView   the MapView associated with the MarkerViewManager
     */
    public MarkerViewManager(@NonNull MapboxMap mapboxMap, @NonNull MapView mapView) {
        this.mapboxMap = mapboxMap;
        this.markerViewAdapters = new ArrayList<>();
        this.mapView = mapView;
        mMarkerViewMap = new HashMap<>();
    }

    /**
     * Animate a MarkerView to a given rotation.
     * <p>
     * The {@link MarkerView} will be rotated from its current rotation to the given rotation.
     * </p>
     *
     * @param marker   the MarkerView to rotate
     * @param rotation the rotation value
     */
    public void animateRotation(@NonNull MarkerView marker, float rotation) {
        View convertView = mMarkerViewMap.get(marker);
        if (convertView != null) {
            AnimatorUtils.rotate(convertView, rotation);
        }
    }

    /**
     * Animate a MarkerView to a given alpha value.
     * <p>
     * The {@link MarkerView} will be transformed from its current alpha value to the given value.
     * </p>
     *
     * @param marker the MarkerView to change its alpha value
     * @param alpha  the alpha value
     */
    public void animateAlpha(@NonNull MarkerView marker, float alpha) {
        View convertView = mMarkerViewMap.get(marker);
        if (convertView != null) {
            AnimatorUtils.alpha(convertView, alpha);
        }
    }

    /**
     * Animate a MarkerVIew to be visible or invisible
     * <p>
     * The {@link MarkerView} will be made {@link View#VISIBLE} or {@link View#GONE}.
     * </p>
     *
     * @param marker  the MarkerView to change its visibility
     * @param visible the flag indicating if MarkerView is visible
     */
    public void animateVisible(@NonNull MarkerView marker, boolean visible) {
        View convertView = mMarkerViewMap.get(marker);
        if (convertView != null) {
            convertView.setVisibility(visible ? View.VISIBLE : View.GONE);
        }
    }

    /**
     * Updates the position of MarkerViews currently found in the viewport.
     * <p>
     * The collection of {@link MarkerView} will be iterated and each item position will be updated.
     * If an item is View state is not visible and its related flag is set to visible,
     * The {@link MarkerView} will be animated to visible using alpha animation.
     * </p>
     */
    public void update() {
        View convertView;
        for (MarkerView marker : mMarkerViewMap.keySet()) {
            convertView = mMarkerViewMap.get(marker);
            if (convertView != null) {
                PointF point = mapboxMap.getProjection().toScreenLocation(marker.getPosition());
                int x = (int) (marker.getAnchorU() * convertView.getMeasuredWidth());
                int y = (int) (marker.getAnchorV() * convertView.getMeasuredHeight());

                marker.setOffsetX(x);
                marker.setOffsetY(y);

                convertView.setX(point.x - x);
                convertView.setY(point.y - y);

                if (marker.isVisible() && convertView.getVisibility() == View.GONE) {
                    convertView.animate().cancel();
                    convertView.setAlpha(0);
                    AnimatorUtils.alpha(convertView, 1);
                }
            }
        }
    }

    /**
     * Set tilt on every non flat MarkerView currently shown in the Viewport.
     *
     * @param tilt the tilt value
     */
    public void setTilt(float tilt) {
        View convertView;
        for (MarkerView markerView : mMarkerViewMap.keySet()) {
            if (markerView.isFlat()) {
                convertView = mMarkerViewMap.get(markerView);
                if (convertView != null) {
                    markerView.setTilt(tilt);
                    convertView.setRotationX(tilt);
                }
            }
        }
    }

    /**
     * Animate a MarkerView to a deselected state.
     * <p>
     * The {@link MarkerView#getDeselectAnimRes()} will be called to get the related animation.
     * If non are provided, no animation will be started.
     * </p>
     *
     * @param marker the MarkerView to deselect
     */
    public void deselect(@NonNull MarkerView marker) {
        final View convertView = mMarkerViewMap.get(marker);
        if (convertView != null) {
            int deselectAnimatorRes = marker.getDeselectAnimRes();
            if (deselectAnimatorRes != 0) {
                AnimatorUtils.animate(convertView, deselectAnimatorRes);
            }
        }
    }

    /**
     * Remove a MarkerView from a map.
     * <p>
     * The {@link MarkerView} will be removed using an alpha animation and related {@link View}
     * will be released to the {@link android.support.v4.util.Pools.SimplePool} from the related
     * {@link com.mapbox.mapboxsdk.maps.MapboxMap.MarkerViewAdapter}. It's possible to remove
     * the {@link MarkerView} from the underlying collection if needed.
     * </p>
     *
     * @param marker        the MarkerView to remove
     * @param removeFromMap flag indicating if a MarkerView will be removed from the collection.
     */
    public void removeMarkerView(MarkerView marker, boolean removeFromMap) {
        final View viewHolder = mMarkerViewMap.get(marker);
        if (viewHolder != null && marker != null) {
            for (final MapboxMap.MarkerViewAdapter<?> adapter : markerViewAdapters) {
                if (adapter.getMarkerClass() == marker.getClass()) {

                    // get pool of Views associated to an adapter
                    final Pools.SimplePool<View> viewPool = adapter.getViewReusePool();

                    // cancel ongoing animations
                    viewHolder.animate().cancel();
                    viewHolder.setAlpha(1);
                    AnimatorUtils.alpha(viewHolder, 0, new AnimatorUtils.OnAnimationEndListener() {
                        @Override
                        public void onAnimationEnd() {
                            viewHolder.setVisibility(View.GONE);
                            viewPool.release(viewHolder);
                        }
                    });
                }
            }
        }
        if (removeFromMap) {
            mMarkerViewMap.remove(marker);
        }
    }

    /**
     * Add a MarkerViewAdapter.
     *
     * @param markerViewAdapter the MarkerViewAdapter to add
     */
    public void addMarkerViewAdapter(@Nullable MapboxMap.MarkerViewAdapter markerViewAdapter) {
        if (!markerViewAdapters.contains(markerViewAdapter)) {
            markerViewAdapters.add(markerViewAdapter);
            invalidateViewMarkersInBounds();
        }
    }

    /**
     * Get all MarkerViewAdapters associated with this MarkerViewManager.
     *
     * @return a List of MarkerViewAdapters
     */
    public List<MapboxMap.MarkerViewAdapter> getMarkerViewAdapters() {
        return markerViewAdapters;
    }


    /**
     * Register a callback to be invoked when this view is clicked.
     *
     * @param listener the callback to be invoked
     */
    public void setOnMarkerViewClickListener(@Nullable MapboxMap.OnMarkerViewClickListener listener) {
        onMarkerViewClickListener = listener;
    }

    /**
     * Schedule that ViewMarkers found in the viewport are invalidated.
     * <p>
     * This method is rate limited, and {@link #invalidateViewMarkersInBounds} will only be called
     * once each 250 ms.
     * </p>
     */
    public void scheduleViewMarkerInvalidation() {
        if (!markerViewAdapters.isEmpty()) {
            long currentTime = SystemClock.elapsedRealtime();
            if (currentTime < mViewMarkerBoundsUpdateTime) {
                return;
            }
            invalidateViewMarkersInBounds();
            mViewMarkerBoundsUpdateTime = currentTime + 250;
        }
    }

    /**
     * Invalidate the ViewMarkers found in the viewport.
     * <p>
     * This method will remove any markers that aren't in the viewport any more and will add new
     * ones for each found Marker in the changed viewport.
     * </p>
     */
    public void invalidateViewMarkersInBounds() {
        Projection projection = mapboxMap.getProjection();
        List<MarkerView> markers = mapView.getMarkerViewsInBounds(projection.getVisibleRegion().latLngBounds);
        View convertView;

        // remove old markers
        Iterator<MarkerView> iterator = mMarkerViewMap.keySet().iterator();
        while (iterator.hasNext()) {
            MarkerView m = iterator.next();
            if (!markers.contains(m)) {
                // remove marker
                convertView = mMarkerViewMap.get(m);
                int deselectAnimRes = m.getDeselectAnimRes();
                if (deselectAnimRes != 0) {
                    AnimatorUtils.animate(convertView, deselectAnimRes, 0);
                }
                removeMarkerView(m, false);
                iterator.remove();
            }
        }

        // introduce new markers
        for (final MarkerView marker : markers) {
            if (!mMarkerViewMap.containsKey(marker)) {
                for (final MapboxMap.MarkerViewAdapter adapter : markerViewAdapters) {
                    if (adapter.getMarkerClass() == marker.getClass()) {
                        convertView = (View) adapter.getViewReusePool().acquire();
                        final View adaptedView = adapter.getView(marker, convertView, mapView);
                        if (adaptedView != null) {

                            // tilt
                            adaptedView.setRotationX(marker.getTilt());

                            // rotation
                            adaptedView.setRotation(marker.getRotation());

                            // alpha
                            adaptedView.setAlpha(marker.getAlpha());

                            // visible
                            adaptedView.setVisibility(marker.isVisible() ? View.VISIBLE : View.GONE);

                            if (mapboxMap.getSelectedMarkers().contains(marker)) {
                                // if a marker to be shown was selected
                                // replay that animation with duration 0
                                int selectAnimRes = marker.getSelectAnimRes();
                                if (selectAnimRes != 0) {
                                    AnimatorUtils.animate(convertView, selectAnimRes, 0);
                                }
                            }

                            final int animSelectRes = marker.getSelectAnimRes();
                            adaptedView.setOnClickListener(new View.OnClickListener() {
                                @Override
                                public void onClick(final View v) {
                                    boolean clickHandled = false;
                                    if (onMarkerViewClickListener != null) {
                                        clickHandled = onMarkerViewClickListener.onMarkerClick(marker, v, adapter);
                                    }

                                    if (!clickHandled) {
                                        // InfoWindow offset
                                        int infoWindowOffsetX = (int) ((adaptedView.getWidth() * marker.getInfoWindowAnchorU()) - marker.getOffsetX());
                                        int infoWindowOffsetY = (int) ((adaptedView.getHeight() * marker.getInfoWindowAnchorV()) - marker.getOffsetY());
                                        marker.setTopOffsetPixels(infoWindowOffsetY);
                                        marker.setRightOffsetPixels(infoWindowOffsetX);

                                        if (animSelectRes != 0) {
                                            AnimatorUtils.animate(v, animSelectRes, new AnimatorUtils.OnAnimationEndListener() {
                                                @Override
                                                public void onAnimationEnd() {
                                                    mapboxMap.selectMarker(marker);
                                                }
                                            });
                                        } else {
                                            mapboxMap.selectMarker(marker);
                                        }
                                    }
                                }
                            });

                            mMarkerViewMap.put(marker, adaptedView);
                            if (convertView == null) {
                                mapView.addView(adaptedView);
                            }
                        }
                    }
                }
            }
        }
    }
}