There are two different classes of data change events, item changes and structural 187 | * changes. Item changes are when a single item has its data updated but no positional 188 | * changes have occurred. Structural changes are when items are inserted, removed or moved 189 | * within the data set.
190 | * 191 | *This event does not specify what about the data set has changed, forcing 192 | * any observers to assume that all existing items and structure may no longer be valid. 193 | * LayoutManagers will be forced to fully rebind and relayout all visible views.
194 | * 195 | *RecyclerView will attempt to synthesize visible structural change events
196 | * for adapters that report that they have {@link #hasStableIds() stable IDs} when
197 | * this method is used. This can help for the purposes of animation and visual
198 | * object persistence but individual item views will still need to be rebound
199 | * and relaid out.
If you are writing an adapter it will always be more efficient to use the more
202 | * specific change events if you can. Rely on notifyDataSetChanged()
203 | * as a last resort.
position has changed.
226 | * Equivalent to calling notifyItemChanged(position, null);.
227 | *
228 | * This is an item change event, not a structural change event. It indicates that any
229 | * reflection of the data at position is out of date and should be updated.
230 | * The item at position retains the same identity.
position has changed with an
242 | * optional payload object.
243 | *
244 | * This is an item change event, not a structural change event. It indicates that any
245 | * reflection of the data at position is out of date and should be updated.
246 | * The item at position retains the same identity.
247 | *
250 | * Client can optionally pass a payload for partial change. These payloads will be merged
251 | * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the
252 | * item is already represented by a ViewHolder and it will be rebound to the same
253 | * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing
254 | * payloads on that item and prevent future payload until
255 | * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume
256 | * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not
257 | * attached, the payload will be simply dropped.
258 | *
259 | * @param position Position of the item that has changed
260 | * @param payload Optional parameter, use null to identify a "full" update
261 | *
262 | * @see #notifyItemRangeChanged(int, int)
263 | */
264 | fun notifyItemChanged(position: Int, payload: Any?) {
265 | observer.onItemRangeChanged(position, 1, payload)
266 | }
267 |
268 | /**
269 | * Notify any registered observers that the item reflected at position
270 | * has been newly inserted. The item previously at position is now at
271 | * position position + 1.
272 | *
273 | *
This is a structural change event. Representations of other existing items in the 274 | * data set are still considered up to date and will not be rebound, though their 275 | * positions may be altered.
276 | * 277 | * @param position Position of the newly inserted item in the data set 278 | * 279 | * @see #notifyItemRangeInserted(int, int) 280 | */ 281 | fun notifyItemInserted(position: Int) { 282 | observer.onItemRangeInserted(position, 1) 283 | } 284 | 285 | /** 286 | * Notify any registered observers that the item previously located atposition
287 | * has been removed from the data set. The items previously located at and after
288 | * position may now be found at oldPosition - 1.
289 | *
290 | * This is a structural change event. Representations of other existing items in the 291 | * data set are still considered up to date and will not be rebound, though their positions 292 | * may be altered.
293 | * 294 | * @param position Position of the item that has now been removed 295 | * 296 | * @see #notifyItemRangeRemoved(int, int) 297 | */ 298 | fun notifyItemRemoved(position: Int) { 299 | observer.onItemRangeRemoved(position, 1) 300 | } 301 | 302 | /** 303 | * Notify any registered observers that the currently reflecteditemCount
304 | * items starting at positionStart have been newly inserted. The items
305 | * previously located at positionStart and beyond can now be found starting
306 | * at positionStart positionStart + itemCount.
307 | *
308 | * This is a structural change event. Representations of other existing items in the 309 | * data set are still considered up to date and will not be rebound, though their positions 310 | * may be altered.
311 | * 312 | * @param positionStart Position of the first item that was inserted 313 | * @param itemCount Number of items inserted 314 | * 315 | * @see #notifyItemInserted(int) 316 | */ 317 | fun notifyItemRangeInserted(positionStart: Int, itemCount: Int) { 318 | for (position in positionStart until positionStart + itemCount) { 319 | notifyItemInserted(position) 320 | } 321 | } 322 | 323 | /** 324 | * Notify any registered observers that the item previously located atpositionStart
325 | * has been removed from the data set. The items previously located at and after
326 | * positionStart may now be found at oldPosition - 1.
327 | *
328 | * This is a structural change event. Representations of other existing items in the 329 | * data set are still considered up to date and will not be rebound, though their positions 330 | * may be altered.
331 | * 332 | * @param positionStart Position of the item that has now been removed 333 | * 334 | * @see #notifyItemRangeRemoved(int, int) 335 | */ 336 | fun notifyItemRangeRemoved(positionStart: Int, itemCount: Int) { 337 | for (position in positionStart + itemCount downTo positionStart) { 338 | notifyItemRemoved(position) 339 | } 340 | } 341 | 342 | 343 | /** 344 | * Notify any registered observers that the item atpositionStart has changed.
345 | * Equivalent to calling notifyItemChanged(positionStart, null);.
346 | *
347 | * This is an item change event, not a structural change event. It indicates that any
348 | * reflection of the data at positionStart is out of date and should be updated.
349 | * The item at positionStart retains the same identity.
positionStart has changed with an
361 | * optional payload object.
362 | *
363 | * This is an item change event, not a structural change event. It indicates that any
364 | * reflection of the data at positionStart is out of date and should be updated.
365 | * The item at positionStart retains the same identity.
366 | *
369 | * Client can optionally pass a payload for partial change. These payloads will be merged 370 | * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the 371 | * item is already represented by a ViewHolder and it will be rebound to the same 372 | * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing 373 | * payloads on that item and prevent future payload until 374 | * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume 375 | * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not 376 | * attached, the payload will be simply dropped. 377 | * 378 | * @param positionStart Position of the item that has changed 379 | * @param payload Optional parameter, use null to identify a "full" update 380 | * 381 | * @see #notifyItemRangeChanged(int, int) 382 | */ 383 | fun notifyItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) { 384 | for (position in positionStart until positionStart + itemCount) { 385 | notifyItemChanged(position, payload) 386 | } 387 | } 388 | 389 | /** 390 | * Notify any registered observers that the item reflected at `fromPosition` 391 | * has been moved to `toPosition`. 392 | 393 | * 394 | * This is a structural change event. Representations of other existing items in the 395 | * data set are still considered up to date and will not be rebound, though their 396 | * positions may be altered. 397 | 398 | * @param fromPosition Previous position of the item. 399 | * * 400 | * @param toPosition New position of the item. 401 | */ 402 | fun notifyItemMoved(fromPosition: Int, toPosition: Int) { 403 | observer.onItemRangeMoved(fromPosition, toPosition, 1) 404 | mapAdapterHelper.dispatch() 405 | } 406 | 407 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 408 | fun notifyAnnotatedMarkerClicked(marker: Any): Boolean { 409 | val clickListener = annotationClickListener ?: return false 410 | 411 | val annotation = annotations.find { it.annotatesObject(marker) } 412 | 413 | if (annotation != null) { 414 | return clickListener.onMapAnnotationClick(annotation) 415 | } else { 416 | Log.e("MapMeAdapter", "Unable to find an annotation that annotates the marker") 417 | } 418 | return false 419 | } 420 | 421 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 422 | fun notifyInfowWindowClicked(marker: Any): Boolean { 423 | val clickListener = infoWindowClickListener ?: return false 424 | 425 | val annotation = annotations.find { it.annotatesObject(marker) } 426 | 427 | if (annotation != null) { 428 | return clickListener.onInfoWindowClick(annotation) 429 | } else { 430 | Log.e("MapMeAdapter", "Unable to find an annotation that annotates the marker") 431 | } 432 | return false 433 | } 434 | 435 | /** 436 | * Marks an annotation as requiring an update (isDirty). The annotations will be updated 437 | * at the end of the 'layout pass' 438 | */ 439 | override fun markAnnotationsUpdated(positionStart: Int, itemCount: Int) { 440 | annotations.forEach { annotation -> 441 | val position = annotation.position 442 | if (position >= positionStart && position < positionStart + itemCount) { 443 | annotation.isDirty = true 444 | } 445 | } 446 | } 447 | 448 | override fun offsetPositionsForAdd(positionStart: Int, itemCount: Int) { 449 | annotations.forEach { annotation -> 450 | val position = annotation.position 451 | 452 | if (position >= positionStart) { 453 | offsetPosition(annotation, itemCount) 454 | } 455 | } 456 | } 457 | 458 | override fun offsetPositionsForRemove(positionStart: Int, itemCount: Int) { 459 | val positionEnd = positionStart + itemCount 460 | 461 | annotations.forEach { annotation -> 462 | if (annotation.position >= positionEnd) { 463 | offsetPosition(annotation, -itemCount) 464 | } else if (annotation.position >= positionStart) { 465 | offsetPosition(annotation, -itemCount) 466 | } 467 | } 468 | } 469 | 470 | private fun offsetPosition(annotation: MapAnnotation, offset: Int) { 471 | annotation.position += offset 472 | } 473 | 474 | override fun offsetPositionsForMove(from: Int, to: Int) { 475 | val start: Int 476 | val end: Int 477 | val inBetweenOffset: Int 478 | if (from < to) { 479 | start = from 480 | end = to 481 | inBetweenOffset = -1 482 | } else { 483 | start = to 484 | end = from 485 | inBetweenOffset = 1 486 | } 487 | 488 | annotations 489 | .filterNot { annotation -> 490 | annotation.position < start || annotation.position > end 491 | } 492 | .forEach { annotation -> 493 | val position = annotation.position 494 | if (position == from) { 495 | offsetPosition(annotation, to - from) 496 | } else { 497 | offsetPosition(annotation, inBetweenOffset) 498 | } 499 | } 500 | } 501 | 502 | internal val updateChildViewsRunnable: Runnable = Runnable { 503 | consumePendingUpdateOperations() 504 | } 505 | 506 | internal fun consumePendingUpdateOperations() { 507 | if (!mapAdapterHelper.hasPendingUpdates()) { 508 | return 509 | } 510 | 511 | mapAdapterHelper.dispatch() 512 | 513 | //after all updates have been dispatched, fill in any placeholder annotations 514 | //with new annotations 515 | applyUpdates() 516 | } 517 | 518 | @VisibleForTesting 519 | open internal fun triggerUpdateProcessor(runnable: Runnable) { 520 | mapView?.post(runnable) 521 | } 522 | 523 | inner class MapMeDataObserver : RecyclerView.AdapterDataObserver() { 524 | 525 | override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) { 526 | if (mapAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) { 527 | triggerUpdateProcessor(updateChildViewsRunnable) 528 | } 529 | 530 | registeredObservers.forEach { it.onItemRangeChanged(positionStart, itemCount, payload) } 531 | } 532 | 533 | override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { 534 | if (mapAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { 535 | triggerUpdateProcessor(updateChildViewsRunnable) 536 | } 537 | 538 | registeredObservers.forEach { it.onItemRangeInserted(positionStart, itemCount) } 539 | } 540 | 541 | override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { 542 | if (mapAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { 543 | triggerUpdateProcessor(updateChildViewsRunnable) 544 | } 545 | 546 | registeredObservers.forEach { it.onItemRangeRemoved(positionStart, itemCount) } 547 | } 548 | 549 | override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { 550 | if (mapAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) { 551 | triggerUpdateProcessor(updateChildViewsRunnable) 552 | } 553 | 554 | registeredObservers.forEach { it.onItemRangeMoved(fromPosition, toPosition, itemCount) } 555 | } 556 | } 557 | 558 | override fun dispatchUpdate(update: UpdateOp) { 559 | when (update.cmd) { 560 | UpdateOp.ADD -> onItemsInserted(update.positionStart, update.itemCount) 561 | UpdateOp.REMOVE -> onItemsRemoved(update.positionStart, update.itemCount) 562 | UpdateOp.UPDATE -> onItemsChanged(update.positionStart, update.itemCount, update.payload) 563 | UpdateOp.MOVE -> onItemsMoved(update.positionStart, update.itemCount, update.payload) 564 | } 565 | } 566 | } 567 | --------------------------------------------------------------------------------