ViewDataBinding.java revision 447971abca811b11b8c1d8e7bfaa294856d03c16
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.databinding; 18 19import com.android.databinding.library.R; 20 21import android.annotation.TargetApi; 22import android.databinding.CallbackRegistry.NotifierCallback; 23import android.os.Build.VERSION; 24import android.os.Build.VERSION_CODES; 25import android.text.TextUtils; 26import android.util.SparseIntArray; 27import android.view.View; 28import android.view.View.OnAttachStateChangeListener; 29import android.view.ViewGroup; 30 31import java.lang.ref.WeakReference; 32 33public abstract class ViewDataBinding { 34 35 /** 36 * Instead of directly accessing Build.VERSION.SDK_INT, generated code uses this value so that 37 * we can test API dependent behavior. 38 */ 39 static int SDK_INT = VERSION.SDK_INT; 40 41 private static final int REBIND = 1; 42 private static final int HALTED = 2; 43 private static final int REBOUND = 3; 44 45 /** 46 * Prefix for android:tag on Views with binding. The root View and include tags will not have 47 * android:tag attributes and will use ids instead. 48 */ 49 public static final String BINDING_TAG_PREFIX = "binding_"; 50 51 // The length of BINDING_TAG_PREFIX prevents calling length repeatedly. 52 private static final int BINDING_NUMBER_START = BINDING_TAG_PREFIX.length(); 53 54 // ICS (v 14) fixes a leak when using setTag(int, Object) 55 private static final boolean USE_TAG_ID = DataBinderMapper.TARGET_MIN_SDK >= 14; 56 57 /** 58 * Method object extracted out to attach a listener to a bound Observable object. 59 */ 60 private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() { 61 @Override 62 public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) { 63 return new WeakPropertyListener(viewDataBinding, localFieldId); 64 } 65 }; 66 67 /** 68 * Method object extracted out to attach a listener to a bound ObservableList object. 69 */ 70 private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() { 71 @Override 72 public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) { 73 return new WeakListListener(viewDataBinding, localFieldId); 74 } 75 }; 76 77 /** 78 * Method object extracted out to attach a listener to a bound ObservableMap object. 79 */ 80 private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() { 81 @Override 82 public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) { 83 return new WeakMapListener(viewDataBinding, localFieldId); 84 } 85 }; 86 87 private static final CallbackRegistry.NotifierCallback<OnRebindCallback, ViewDataBinding, Void> 88 REBIND_NOTIFIER = new NotifierCallback<OnRebindCallback, ViewDataBinding, Void>() { 89 @Override 90 public void onNotifyCallback(OnRebindCallback callback, ViewDataBinding sender, int mode, 91 Void arg2) { 92 switch (mode) { 93 case REBIND: 94 if (!callback.onPreBind(sender)) { 95 sender.mRebindHalted = true; 96 } 97 break; 98 case HALTED: 99 callback.onCanceled(sender); 100 break; 101 case REBOUND: 102 callback.onBound(sender); 103 break; 104 } 105 } 106 }; 107 108 private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER; 109 110 static { 111 if (VERSION.SDK_INT < VERSION_CODES.KITKAT) { 112 ROOT_REATTACHED_LISTENER = null; 113 } else { 114 ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() { 115 @TargetApi(VERSION_CODES.KITKAT) 116 @Override 117 public void onViewAttachedToWindow(View v) { 118 // execute the pending bindings. 119 final ViewDataBinding binding; 120 if (USE_TAG_ID) { 121 binding = (ViewDataBinding) v.getTag(R.id.dataBinding); 122 } else { 123 binding = (ViewDataBinding) v.getTag(); 124 } 125 v.post(binding.mRebindRunnable); 126 v.removeOnAttachStateChangeListener(this); 127 } 128 129 @Override 130 public void onViewDetachedFromWindow(View v) { 131 } 132 }; 133 } 134 } 135 136 /** 137 * Runnable executed on animation heartbeat to rebind the dirty Views. 138 */ 139 private Runnable mRebindRunnable = new Runnable() { 140 @Override 141 public void run() { 142 synchronized (this) { 143 mPendingRebind = false; 144 } 145 if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { 146 // Nested so that we don't get a lint warning in IntelliJ 147 if (!mRoot.isAttachedToWindow()) { 148 // Don't execute the pending bindings until the View 149 // is attached again. 150 mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER); 151 mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER); 152 return; 153 } 154 } 155 executePendingBindings(); 156 } 157 }; 158 159 /** 160 * Flag indicates that there are pending bindings that need to be reevaluated. 161 */ 162 private boolean mPendingRebind = false; 163 164 /** 165 * Indicates that a onPreBind has stopped the executePendingBindings call. 166 */ 167 private boolean mRebindHalted = false; 168 169 /** 170 * The observed expressions. 171 */ 172 private WeakListener[] mLocalFieldObservers; 173 174 /** 175 * The root View that this Binding is associated with. 176 */ 177 private final View mRoot; 178 179 /** 180 * The collection of OnRebindCallbacks. 181 */ 182 private CallbackRegistry<OnRebindCallback, ViewDataBinding, Void> mRebindCallbacks; 183 184 /** 185 * Flag to prevent reentrant executePendingBinding calls. 186 */ 187 private boolean mIsExecutingPendingBindings; 188 189 protected ViewDataBinding(View root, int localFieldCount) { 190 mLocalFieldObservers = new WeakListener[localFieldCount]; 191 this.mRoot = root; 192 } 193 194 protected void setRootTag(View view) { 195 if (USE_TAG_ID) { 196 view.setTag(R.id.dataBinding, this); 197 } else { 198 view.setTag(this); 199 } 200 } 201 202 protected void setRootTag(View[] views) { 203 if (USE_TAG_ID) { 204 for (View view : views) { 205 view.setTag(R.id.dataBinding, this); 206 } 207 } else { 208 for (View view : views) { 209 view.setTag(this); 210 } 211 } 212 } 213 214 public static int getBuildSdkInt() { 215 return SDK_INT; 216 } 217 218 /** 219 * Called when an observed object changes. Sets the appropriate dirty flag if applicable. 220 * @param localFieldId The index into mLocalFieldObservers that this Object resides in. 221 * @param object The object that has changed. 222 * @param fieldId The BR ID of the field being changed or _all if 223 * no specific field is being notified. 224 * @return true if this change should cause a change to the UI. 225 */ 226 protected abstract boolean onFieldChange(int localFieldId, Object object, int fieldId); 227 228 /** 229 * Set a value value in the Binding class. 230 * <p> 231 * Typically, the developer will be able to call the subclass's set method directly. For 232 * example, if there is a variable <code>x</code> in the Binding, a <code>setX</code> method 233 * will be generated. However, there are times when the specific subclass of ViewDataBinding 234 * is unknown, so the generated method cannot be discovered without reflection. The 235 * setVariable call allows the values of variables to be set without reflection. 236 * 237 * @param variableId the BR id of the variable to be set. For example, if the variable is 238 * <code>x</code>, then variableId will be <code>BR.x</code>. 239 * @param value The new value of the variable to be set. 240 * @return <code>true</code> if the variable exists in the binding or <code>false</code> 241 * otherwise. 242 */ 243 public abstract boolean setVariable(int variableId, Object value); 244 245 /** 246 * Add a listener to be called when reevaluating dirty fields. This also allows automatic 247 * updates to be halted, but does not stop explicit calls to {@link #executePendingBindings()}. 248 * 249 * @param listener The listener to add. 250 */ 251 public void addOnRebindCallback(OnRebindCallback listener) { 252 if (mRebindCallbacks == null) { 253 mRebindCallbacks = new CallbackRegistry<OnRebindCallback, ViewDataBinding, Void>(REBIND_NOTIFIER); 254 } 255 mRebindCallbacks.add(listener); 256 } 257 258 /** 259 * Removes a listener that was added in {@link #addOnRebindCallback(OnRebindCallback)}. 260 * 261 * @param listener The listener to remove. 262 */ 263 public void removeOnRebindCallback(OnRebindCallback listener) { 264 if (mRebindCallbacks != null) { 265 mRebindCallbacks.remove(listener); 266 } 267 } 268 269 /** 270 * Evaluates the pending bindings, updating any Views that have expressions bound to 271 * modified variables. This <b>must</b> be run on the UI thread. 272 */ 273 public void executePendingBindings() { 274 if (mIsExecutingPendingBindings) { 275 requestRebind(); 276 return; 277 } 278 if (!hasPendingBindings()) { 279 return; 280 } 281 mIsExecutingPendingBindings = true; 282 mRebindHalted = false; 283 if (mRebindCallbacks != null) { 284 mRebindCallbacks.notifyCallbacks(this, REBIND, null); 285 286 // The onRebindListeners will change mPendingHalted 287 if (mRebindHalted) { 288 mRebindCallbacks.notifyCallbacks(this, HALTED, null); 289 } 290 } 291 if (!mRebindHalted) { 292 executeBindings(); 293 if (mRebindCallbacks != null) { 294 mRebindCallbacks.notifyCallbacks(this, REBOUND, null); 295 } 296 } 297 mIsExecutingPendingBindings = false; 298 } 299 300 void forceExecuteBindings() { 301 executeBindings(); 302 } 303 304 protected abstract void executeBindings(); 305 306 /** 307 * Used internally to invalidate flags of included layouts. 308 * @hide 309 */ 310 public abstract void invalidateAll(); 311 312 /** 313 * @return true if any field has changed and the binding should be evaluated. 314 * @hide 315 */ 316 public abstract boolean hasPendingBindings(); 317 318 /** 319 * Removes binding listeners to expression variables. 320 */ 321 public void unbind() { 322 for (WeakListener weakListener : mLocalFieldObservers) { 323 if (weakListener != null) { 324 weakListener.unregister(); 325 } 326 } 327 } 328 329 @Override 330 protected void finalize() throws Throwable { 331 unbind(); 332 } 333 334 static ViewDataBinding getBinding(View v) { 335 if (USE_TAG_ID) { 336 return (ViewDataBinding) v.getTag(R.id.dataBinding); 337 } else { 338 final Object tag = v.getTag(); 339 if (tag instanceof ViewDataBinding) { 340 return (ViewDataBinding) tag; 341 } 342 } 343 return null; 344 } 345 346 /** 347 * Returns the outermost View in the layout file associated with the Binding. 348 * @return the outermost View in the layout file associated with the Binding. 349 */ 350 public View getRoot() { 351 return mRoot; 352 } 353 354 private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) { 355 boolean result = onFieldChange(mLocalFieldId, object, fieldId); 356 if (result) { 357 requestRebind(); 358 } 359 } 360 361 protected boolean unregisterFrom(int localFieldId) { 362 WeakListener listener = mLocalFieldObservers[localFieldId]; 363 if (listener != null) { 364 return listener.unregister(); 365 } 366 return false; 367 } 368 369 protected void requestRebind() { 370 synchronized (this) { 371 if (mPendingRebind) { 372 return; 373 } 374 mPendingRebind = true; 375 } 376 if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 377 mRoot.postOnAnimation(mRebindRunnable); 378 } else { 379 mRoot.post(mRebindRunnable); 380 } 381 } 382 383 protected Object getObservedField(int localFieldId) { 384 WeakListener listener = mLocalFieldObservers[localFieldId]; 385 if (listener == null) { 386 return null; 387 } 388 return listener.getTarget(); 389 } 390 391 private boolean updateRegistration(int localFieldId, Object observable, 392 CreateWeakListener listenerCreator) { 393 if (observable == null) { 394 return unregisterFrom(localFieldId); 395 } 396 WeakListener listener = mLocalFieldObservers[localFieldId]; 397 if (listener == null) { 398 registerTo(localFieldId, observable, listenerCreator); 399 return true; 400 } 401 if (listener.getTarget() == observable) { 402 return false;//nothing to do, same object 403 } 404 unregisterFrom(localFieldId); 405 registerTo(localFieldId, observable, listenerCreator); 406 return true; 407 } 408 409 protected boolean updateRegistration(int localFieldId, Observable observable) { 410 return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER); 411 } 412 413 protected boolean updateRegistration(int localFieldId, ObservableList observable) { 414 return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER); 415 } 416 417 protected boolean updateRegistration(int localFieldId, ObservableMap observable) { 418 return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER); 419 } 420 421 protected void registerTo(int localFieldId, Object observable, 422 CreateWeakListener listenerCreator) { 423 if (observable == null) { 424 return; 425 } 426 WeakListener listener = mLocalFieldObservers[localFieldId]; 427 if (listener == null) { 428 listener = listenerCreator.create(this, localFieldId); 429 mLocalFieldObservers[localFieldId] = listener; 430 } 431 listener.setTarget(observable); 432 } 433 434 protected static ViewDataBinding bind(View view, int layoutId) { 435 return DataBindingUtil.bind(view, layoutId); 436 } 437 438 /** 439 * Walks the view hierarchy under root and pulls out tagged Views, includes, and views with 440 * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find 441 * all bound and ID'd views. 442 * 443 * @param root The root of the view hierarchy to walk. 444 * @param numBindings The total number of ID'd views, views with expressions, and includes 445 * @param includes The include layout information, indexed by their container's index. 446 * @param viewsWithIds Indexes of views that don't have tags, but have IDs. 447 * @return An array of size numBindings containing all Views in the hierarchy that have IDs 448 * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for 449 * included layouts. 450 */ 451 protected static Object[] mapBindings(View root, int numBindings, 452 IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) { 453 Object[] bindings = new Object[numBindings]; 454 mapBindings(root, bindings, includes, viewsWithIds, true); 455 return bindings; 456 } 457 458 /** 459 * Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with 460 * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find 461 * all bound and ID'd views. 462 * 463 * @param roots The root Views of the view hierarchy to walk. This is used with merge tags. 464 * @param numBindings The total number of ID'd views, views with expressions, and includes 465 * @param includes The include layout information, indexed by their container's index. 466 * @param viewsWithIds Indexes of views that don't have tags, but have IDs. 467 * @return An array of size numBindings containing all Views in the hierarchy that have IDs 468 * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for 469 * included layouts. 470 */ 471 protected static Object[] mapBindings(View[] roots, int numBindings, 472 IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) { 473 Object[] bindings = new Object[numBindings]; 474 for (int i = 0; i < roots.length; i++) { 475 mapBindings(roots[i], bindings, includes, viewsWithIds, true); 476 } 477 return bindings; 478 } 479 480 private static void mapBindings(View view, Object[] bindings, 481 IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds, boolean isRoot) { 482 final IncludedLayoutIndex[] includedLayoutIndexes; 483 final String tag = (String) view.getTag(); 484 boolean isBound = false; 485 if (isRoot && tag != null && tag.startsWith("layout")) { 486 final int underscoreIndex = tag.lastIndexOf('_'); 487 if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) { 488 final int index = parseTagInt(tag, underscoreIndex + 1); 489 bindings[index] = view; 490 includedLayoutIndexes = includes == null ? null : includes[index]; 491 isBound = true; 492 } else { 493 includedLayoutIndexes = null; 494 } 495 } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) { 496 int tagIndex = parseTagInt(tag, BINDING_NUMBER_START); 497 bindings[tagIndex] = view; 498 isBound = true; 499 includedLayoutIndexes = includes == null ? null : includes[tagIndex]; 500 } else { 501 // Not a bound view 502 includedLayoutIndexes = null; 503 } 504 if (!isBound) { 505 final int id = view.getId(); 506 if (id > 0) { 507 int index; 508 if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0) { 509 bindings[index] = view; 510 } 511 } 512 } 513 514 if (view instanceof ViewGroup) { 515 final ViewGroup viewGroup = (ViewGroup) view; 516 final int count = viewGroup.getChildCount(); 517 int minInclude = 0; 518 for (int i = 0; i < count; i++) { 519 final View child = viewGroup.getChildAt(i); 520 boolean isInclude = false; 521 if (includedLayoutIndexes != null) { 522 String childTag = (String) child.getTag(); 523 if (childTag != null && childTag.endsWith("_0") && 524 childTag.startsWith("layout") && childTag.indexOf('/') > 0) { 525 // This *could* be an include. Test against the expected includes. 526 int includeIndex = findIncludeIndex(childTag, minInclude, 527 includedLayoutIndexes); 528 if (includeIndex >= 0) { 529 isInclude = true; 530 minInclude = includeIndex + 1; 531 IncludedLayoutIndex include = includedLayoutIndexes[includeIndex]; 532 int lastMatchingIndex = findLastMatching(viewGroup, i); 533 if (lastMatchingIndex == i) { 534 bindings[include.index] = DataBindingUtil.bind(child, 535 include.layoutId); 536 } else { 537 final int includeCount = lastMatchingIndex - i + 1; 538 final View[] included = new View[includeCount]; 539 for (int j = 0; j < includeCount; j++) { 540 included[j] = viewGroup.getChildAt(i + j); 541 } 542 bindings[include.index] = DataBindingUtil.bind(included, 543 include.layoutId); 544 i += includeCount - 1; 545 } 546 } 547 } 548 } 549 if (!isInclude) { 550 mapBindings(child, bindings, includes, viewsWithIds, false); 551 } 552 } 553 } 554 } 555 556 private static int findIncludeIndex(String tag, int minInclude, 557 IncludedLayoutIndex[] layoutIndexes) { 558 final int slashIndex = tag.indexOf('/'); 559 final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2); 560 561 final int length = layoutIndexes.length; 562 for (int i = minInclude; i < length; i++) { 563 final IncludedLayoutIndex layoutIndex = layoutIndexes[i]; 564 if (TextUtils.equals(layoutName, layoutIndex.layout)) { 565 return i; 566 } 567 } 568 return -1; 569 } 570 571 private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) { 572 final View firstView = viewGroup.getChildAt(firstIncludedIndex); 573 final String firstViewTag = (String) firstView.getTag(); 574 final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0" 575 final int tagSequenceIndex = tagBase.length(); 576 577 final int count = viewGroup.getChildCount(); 578 int max = firstIncludedIndex; 579 for (int i = firstIncludedIndex + 1; i < count; i++) { 580 final View view = viewGroup.getChildAt(i); 581 final String tag = (String) view.getTag(); 582 if (tag != null && tag.startsWith(tagBase)) { 583 if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') { 584 return max; // Found another instance of the include 585 } 586 if (isNumeric(tag, tagSequenceIndex)) { 587 max = i; 588 } 589 } 590 } 591 return max; 592 } 593 594 private static boolean isNumeric(String tag, int startIndex) { 595 int length = tag.length(); 596 if (length == startIndex) { 597 return false; // no numerals 598 } 599 for (int i = startIndex; i < length; i++) { 600 if (!Character.isDigit(tag.charAt(i))) { 601 return false; 602 } 603 } 604 return true; 605 } 606 607 /** 608 * Parse the tag without creating a new String object. This is fast and assumes the 609 * tag is in the correct format. 610 * @param str The tag string. 611 * @return The binding tag number parsed from the tag string. 612 */ 613 private static int parseTagInt(String str, int startIndex) { 614 final int end = str.length(); 615 int val = 0; 616 for (int i = startIndex; i < end; i++) { 617 val *= 10; 618 char c = str.charAt(i); 619 val += (c - '0'); 620 } 621 return val; 622 } 623 624 private static abstract class WeakListener<T> { 625 private final WeakReference<ViewDataBinding> mBinder; 626 protected final int mLocalFieldId; 627 private T mTarget; 628 629 public WeakListener(ViewDataBinding binder, int localFieldId) { 630 mBinder = new WeakReference<ViewDataBinding>(binder); 631 mLocalFieldId = localFieldId; 632 } 633 634 public void setTarget(T object) { 635 unregister(); 636 mTarget = object; 637 if (mTarget != null) { 638 addListener(mTarget); 639 } 640 } 641 642 public boolean unregister() { 643 boolean unregistered = false; 644 if (mTarget != null) { 645 removeListener(mTarget); 646 unregistered = true; 647 } 648 mTarget = null; 649 return unregistered; 650 } 651 652 public T getTarget() { 653 return mTarget; 654 } 655 656 protected ViewDataBinding getBinder() { 657 ViewDataBinding binder = mBinder.get(); 658 if (binder == null) { 659 unregister(); // The binder is dead 660 } 661 return binder; 662 } 663 664 protected abstract void addListener(T target); 665 protected abstract void removeListener(T target); 666 } 667 668 private static class WeakPropertyListener extends WeakListener<Observable> 669 implements OnPropertyChangedListener { 670 public WeakPropertyListener(ViewDataBinding binder, int localFieldId) { 671 super(binder, localFieldId); 672 } 673 674 @Override 675 protected void addListener(Observable target) { 676 target.addOnPropertyChangedListener(this); 677 } 678 679 @Override 680 protected void removeListener(Observable target) { 681 target.removeOnPropertyChangedListener(this); 682 } 683 684 @Override 685 public void onPropertyChanged(Observable sender, int fieldId) { 686 ViewDataBinding binder = getBinder(); 687 if (binder == null) { 688 return; 689 } 690 Observable obj = getTarget(); 691 if (obj != sender) { 692 return; // notification from the wrong object? 693 } 694 binder.handleFieldChange(mLocalFieldId, sender, fieldId); 695 } 696 } 697 698 private static class WeakListListener extends WeakListener<ObservableList> 699 implements OnListChangedListener { 700 701 public WeakListListener(ViewDataBinding binder, int localFieldId) { 702 super(binder, localFieldId); 703 } 704 705 @Override 706 public void onChanged() { 707 ViewDataBinding binder = getBinder(); 708 if (binder == null) { 709 return; 710 } 711 ObservableList target = getTarget(); 712 if (target == null) { 713 return; // We don't expect any notifications from null targets 714 } 715 binder.handleFieldChange(mLocalFieldId, target, 0); 716 } 717 718 @Override 719 public void onItemRangeChanged(int positionStart, int itemCount) { 720 onChanged(); 721 } 722 723 @Override 724 public void onItemRangeInserted(int positionStart, int itemCount) { 725 onChanged(); 726 } 727 728 @Override 729 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 730 onChanged(); 731 } 732 733 @Override 734 public void onItemRangeRemoved(int positionStart, int itemCount) { 735 onChanged(); 736 } 737 738 @Override 739 protected void addListener(ObservableList target) { 740 target.addOnListChangedListener(this); 741 } 742 743 @Override 744 protected void removeListener(ObservableList target) { 745 target.removeOnListChangedListener(this); 746 } 747 } 748 749 private static class WeakMapListener extends WeakListener<ObservableMap> 750 implements OnMapChangedListener { 751 public WeakMapListener(ViewDataBinding binder, int localFieldId) { 752 super(binder, localFieldId); 753 } 754 755 @Override 756 protected void addListener(ObservableMap target) { 757 target.addOnMapChangedListener(this); 758 } 759 760 @Override 761 protected void removeListener(ObservableMap target) { 762 target.removeOnMapChangedListener(this); 763 } 764 765 @Override 766 public void onMapChanged(ObservableMap sender, Object key) { 767 ViewDataBinding binder = getBinder(); 768 if (binder == null || sender != getTarget()) { 769 return; 770 } 771 binder.handleFieldChange(mLocalFieldId, sender, 0); 772 } 773 } 774 775 private interface CreateWeakListener { 776 WeakListener create(ViewDataBinding viewDataBinding, int localFieldId); 777 } 778 779 protected static class IncludedLayoutIndex { 780 public final String layout; 781 public final int index; 782 public final int layoutId; 783 784 public IncludedLayoutIndex(String layout, int index, int layoutId) { 785 this.layout = layout; 786 this.index = index; 787 this.layoutId = layoutId; 788 } 789 } 790} 791