ViewDataBinding.java revision 9bdb2415487832e88a05c7bd19391b05440b468e
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 mIsExecutingPendingBindings = true; 279 mRebindHalted = false; 280 if (mRebindCallbacks != null) { 281 mRebindCallbacks.notifyCallbacks(this, REBIND, null); 282 283 // The onRebindListeners will change mPendingHalted 284 if (mRebindHalted) { 285 mRebindCallbacks.notifyCallbacks(this, HALTED, null); 286 } 287 } 288 if (!mRebindHalted) { 289 executeBindings(); 290 if (mRebindCallbacks != null) { 291 mRebindCallbacks.notifyCallbacks(this, REBOUND, null); 292 } 293 } 294 mIsExecutingPendingBindings = false; 295 } 296 297 void forceExecuteBindings() { 298 executeBindings(); 299 } 300 301 protected abstract void executeBindings(); 302 303 /** 304 * Used internally to invalidate flags of included layouts. 305 */ 306 public abstract void invalidateAll(); 307 308 /** 309 * Removes binding listeners to expression variables. 310 */ 311 public void unbind() { 312 for (WeakListener weakListener : mLocalFieldObservers) { 313 if (weakListener != null) { 314 weakListener.unregister(); 315 } 316 } 317 } 318 319 @Override 320 protected void finalize() throws Throwable { 321 unbind(); 322 } 323 324 /** 325 * Returns the outermost View in the layout file associated with the Binding. 326 * @return the outermost View in the layout file associated with the Binding. 327 */ 328 public View getRoot() { 329 return mRoot; 330 } 331 332 private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) { 333 boolean result = onFieldChange(mLocalFieldId, object, fieldId); 334 if (result) { 335 requestRebind(); 336 } 337 } 338 339 protected boolean unregisterFrom(int localFieldId) { 340 WeakListener listener = mLocalFieldObservers[localFieldId]; 341 if (listener != null) { 342 return listener.unregister(); 343 } 344 return false; 345 } 346 347 protected void requestRebind() { 348 synchronized (this) { 349 if (mPendingRebind) { 350 return; 351 } 352 mPendingRebind = true; 353 } 354 if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 355 mRoot.postOnAnimation(mRebindRunnable); 356 } else { 357 mRoot.post(mRebindRunnable); 358 } 359 } 360 361 protected Object getObservedField(int localFieldId) { 362 WeakListener listener = mLocalFieldObservers[localFieldId]; 363 if (listener == null) { 364 return null; 365 } 366 return listener.getTarget(); 367 } 368 369 private boolean updateRegistration(int localFieldId, Object observable, 370 CreateWeakListener listenerCreator) { 371 if (observable == null) { 372 return unregisterFrom(localFieldId); 373 } 374 WeakListener listener = mLocalFieldObservers[localFieldId]; 375 if (listener == null) { 376 registerTo(localFieldId, observable, listenerCreator); 377 return true; 378 } 379 if (listener.getTarget() == observable) { 380 return false;//nothing to do, same object 381 } 382 unregisterFrom(localFieldId); 383 registerTo(localFieldId, observable, listenerCreator); 384 return true; 385 } 386 387 protected boolean updateRegistration(int localFieldId, Observable observable) { 388 return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER); 389 } 390 391 protected boolean updateRegistration(int localFieldId, ObservableList observable) { 392 return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER); 393 } 394 395 protected boolean updateRegistration(int localFieldId, ObservableMap observable) { 396 return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER); 397 } 398 399 protected void registerTo(int localFieldId, Object observable, 400 CreateWeakListener listenerCreator) { 401 if (observable == null) { 402 return; 403 } 404 WeakListener listener = mLocalFieldObservers[localFieldId]; 405 if (listener == null) { 406 listener = listenerCreator.create(this, localFieldId); 407 mLocalFieldObservers[localFieldId] = listener; 408 } 409 listener.setTarget(observable); 410 } 411 412 /** 413 * Walks the view hierarchy under root and pulls out tagged Views, includes, and views with 414 * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find 415 * all bound and ID'd views. 416 * 417 * @param root The root of the view hierarchy to walk. 418 * @param numBindings The total number of ID'd views, views with expressions, and includes 419 * @param includes The include layout information, indexed by their container's index. 420 * @param viewsWithIds Indexes of views that don't have tags, but have IDs. 421 * @return An array of size numBindings containing all Views in the hierarchy that have IDs 422 * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for 423 * included layouts. 424 */ 425 protected static Object[] mapBindings(View root, int numBindings, 426 IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) { 427 Object[] bindings = new Object[numBindings]; 428 mapBindings(root, bindings, includes, viewsWithIds, true); 429 return bindings; 430 } 431 432 /** 433 * Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with 434 * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find 435 * all bound and ID'd views. 436 * 437 * @param roots The root Views of the view hierarchy to walk. This is used with merge tags. 438 * @param numBindings The total number of ID'd views, views with expressions, and includes 439 * @param includes The include layout information, indexed by their container's index. 440 * @param viewsWithIds Indexes of views that don't have tags, but have IDs. 441 * @return An array of size numBindings containing all Views in the hierarchy that have IDs 442 * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for 443 * included layouts. 444 */ 445 protected static Object[] mapBindings(View[] roots, int numBindings, 446 IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) { 447 Object[] bindings = new Object[numBindings]; 448 for (int i = 0; i < roots.length; i++) { 449 mapBindings(roots[i], bindings, includes, viewsWithIds, true); 450 } 451 return bindings; 452 } 453 454 private static void mapBindings(View view, Object[] bindings, 455 IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds, boolean isRoot) { 456 final IncludedLayoutIndex[] includedLayoutIndexes; 457 final String tag = (String) view.getTag(); 458 boolean isBound = false; 459 if (isRoot && tag != null && tag.startsWith("layout")) { 460 final int underscoreIndex = tag.lastIndexOf('_'); 461 if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) { 462 final int index = parseTagInt(tag, underscoreIndex + 1); 463 bindings[index] = view; 464 includedLayoutIndexes = includes == null ? null : includes[index]; 465 isBound = true; 466 } else { 467 includedLayoutIndexes = null; 468 } 469 } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) { 470 int tagIndex = parseTagInt(tag, BINDING_NUMBER_START); 471 bindings[tagIndex] = view; 472 isBound = true; 473 includedLayoutIndexes = includes == null ? null : includes[tagIndex]; 474 } else { 475 // Not a bound view 476 includedLayoutIndexes = null; 477 } 478 if (!isBound) { 479 final int id = view.getId(); 480 if (id > 0) { 481 int index; 482 if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0) { 483 bindings[index] = view; 484 } 485 } 486 } 487 488 if (view instanceof ViewGroup) { 489 final ViewGroup viewGroup = (ViewGroup) view; 490 final int count = viewGroup.getChildCount(); 491 int minInclude = 0; 492 for (int i = 0; i < count; i++) { 493 final View child = viewGroup.getChildAt(i); 494 boolean isInclude = false; 495 if (includedLayoutIndexes != null) { 496 String childTag = (String) child.getTag(); 497 if (childTag != null && childTag.endsWith("_0") && 498 childTag.startsWith("layout") && childTag.indexOf('/') > 0) { 499 // This *could* be an include. Test against the expected includes. 500 int includeIndex = findIncludeIndex(childTag, minInclude, 501 includedLayoutIndexes); 502 if (includeIndex >= 0) { 503 isInclude = true; 504 minInclude = includeIndex + 1; 505 IncludedLayoutIndex include = includedLayoutIndexes[includeIndex]; 506 int lastMatchingIndex = findLastMatching(viewGroup, i); 507 if (lastMatchingIndex == i) { 508 bindings[include.index] = DataBindingUtil.bind(child, 509 include.layoutId); 510 } else { 511 final int includeCount = lastMatchingIndex - i + 1; 512 final View[] included = new View[includeCount]; 513 for (int j = 0; j < includeCount; j++) { 514 included[j] = viewGroup.getChildAt(i + j); 515 } 516 bindings[include.index] = DataBindingUtil.bind(included, 517 include.layoutId); 518 i += includeCount - 1; 519 } 520 } 521 } 522 } 523 if (!isInclude) { 524 mapBindings(child, bindings, includes, viewsWithIds, false); 525 } 526 } 527 } 528 } 529 530 private static int findIncludeIndex(String tag, int minInclude, 531 IncludedLayoutIndex[] layoutIndexes) { 532 final int slashIndex = tag.indexOf('/'); 533 final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2); 534 535 final int length = layoutIndexes.length; 536 for (int i = minInclude; i < length; i++) { 537 final IncludedLayoutIndex layoutIndex = layoutIndexes[i]; 538 if (TextUtils.equals(layoutName, layoutIndex.layout)) { 539 return i; 540 } 541 } 542 return -1; 543 } 544 545 private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) { 546 final View firstView = viewGroup.getChildAt(firstIncludedIndex); 547 final String firstViewTag = (String) firstView.getTag(); 548 final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0" 549 final int tagSequenceIndex = tagBase.length(); 550 551 final int count = viewGroup.getChildCount(); 552 int max = firstIncludedIndex; 553 for (int i = firstIncludedIndex + 1; i < count; i++) { 554 final View view = viewGroup.getChildAt(i); 555 final String tag = (String) view.getTag(); 556 if (tag != null && tag.startsWith(tagBase)) { 557 if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') { 558 return max; // Found another instance of the include 559 } 560 if (isNumeric(tag, tagSequenceIndex)) { 561 max = i; 562 } 563 } 564 } 565 return max; 566 } 567 568 private static boolean isNumeric(String tag, int startIndex) { 569 int length = tag.length(); 570 if (length == startIndex) { 571 return false; // no numerals 572 } 573 for (int i = startIndex; i < length; i++) { 574 if (!Character.isDigit(tag.charAt(i))) { 575 return false; 576 } 577 } 578 return true; 579 } 580 581 /** 582 * Parse the tag without creating a new String object. This is fast and assumes the 583 * tag is in the correct format. 584 * @param str The tag string. 585 * @return The binding tag number parsed from the tag string. 586 */ 587 private static int parseTagInt(String str, int startIndex) { 588 final int end = str.length(); 589 int val = 0; 590 for (int i = startIndex; i < end; i++) { 591 val *= 10; 592 char c = str.charAt(i); 593 val += (c - '0'); 594 } 595 return val; 596 } 597 598 private static abstract class WeakListener<T> { 599 private final WeakReference<ViewDataBinding> mBinder; 600 protected final int mLocalFieldId; 601 private T mTarget; 602 603 public WeakListener(ViewDataBinding binder, int localFieldId) { 604 mBinder = new WeakReference<ViewDataBinding>(binder); 605 mLocalFieldId = localFieldId; 606 } 607 608 public void setTarget(T object) { 609 unregister(); 610 mTarget = object; 611 if (mTarget != null) { 612 addListener(mTarget); 613 } 614 } 615 616 public boolean unregister() { 617 boolean unregistered = false; 618 if (mTarget != null) { 619 removeListener(mTarget); 620 unregistered = true; 621 } 622 mTarget = null; 623 return unregistered; 624 } 625 626 public T getTarget() { 627 return mTarget; 628 } 629 630 protected ViewDataBinding getBinder() { 631 ViewDataBinding binder = mBinder.get(); 632 if (binder == null) { 633 unregister(); // The binder is dead 634 } 635 return binder; 636 } 637 638 protected abstract void addListener(T target); 639 protected abstract void removeListener(T target); 640 } 641 642 private static class WeakPropertyListener extends WeakListener<Observable> 643 implements OnPropertyChangedListener { 644 public WeakPropertyListener(ViewDataBinding binder, int localFieldId) { 645 super(binder, localFieldId); 646 } 647 648 @Override 649 protected void addListener(Observable target) { 650 target.addOnPropertyChangedListener(this); 651 } 652 653 @Override 654 protected void removeListener(Observable target) { 655 target.removeOnPropertyChangedListener(this); 656 } 657 658 @Override 659 public void onPropertyChanged(Observable sender, int fieldId) { 660 ViewDataBinding binder = getBinder(); 661 if (binder == null) { 662 return; 663 } 664 Observable obj = getTarget(); 665 if (obj != sender) { 666 return; // notification from the wrong object? 667 } 668 binder.handleFieldChange(mLocalFieldId, sender, fieldId); 669 } 670 } 671 672 private static class WeakListListener extends WeakListener<ObservableList> 673 implements OnListChangedListener { 674 675 public WeakListListener(ViewDataBinding binder, int localFieldId) { 676 super(binder, localFieldId); 677 } 678 679 @Override 680 public void onChanged() { 681 ViewDataBinding binder = getBinder(); 682 if (binder == null) { 683 return; 684 } 685 ObservableList target = getTarget(); 686 if (target == null) { 687 return; // We don't expect any notifications from null targets 688 } 689 binder.handleFieldChange(mLocalFieldId, target, 0); 690 } 691 692 @Override 693 public void onItemRangeChanged(int positionStart, int itemCount) { 694 onChanged(); 695 } 696 697 @Override 698 public void onItemRangeInserted(int positionStart, int itemCount) { 699 onChanged(); 700 } 701 702 @Override 703 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 704 onChanged(); 705 } 706 707 @Override 708 public void onItemRangeRemoved(int positionStart, int itemCount) { 709 onChanged(); 710 } 711 712 @Override 713 protected void addListener(ObservableList target) { 714 target.addOnListChangedListener(this); 715 } 716 717 @Override 718 protected void removeListener(ObservableList target) { 719 target.removeOnListChangedListener(this); 720 } 721 } 722 723 private static class WeakMapListener extends WeakListener<ObservableMap> 724 implements OnMapChangedListener { 725 public WeakMapListener(ViewDataBinding binder, int localFieldId) { 726 super(binder, localFieldId); 727 } 728 729 @Override 730 protected void addListener(ObservableMap target) { 731 target.addOnMapChangedListener(this); 732 } 733 734 @Override 735 protected void removeListener(ObservableMap target) { 736 target.removeOnMapChangedListener(this); 737 } 738 739 @Override 740 public void onMapChanged(ObservableMap sender, Object key) { 741 ViewDataBinding binder = getBinder(); 742 if (binder == null || sender != getTarget()) { 743 return; 744 } 745 binder.handleFieldChange(mLocalFieldId, sender, 0); 746 } 747 } 748 749 private interface CreateWeakListener { 750 WeakListener create(ViewDataBinding viewDataBinding, int localFieldId); 751 } 752 753 protected static class IncludedLayoutIndex { 754 public final String layout; 755 public final int index; 756 public final int layoutId; 757 758 public IncludedLayoutIndex(String layout, int index, int layoutId) { 759 this.layout = layout; 760 this.index = index; 761 this.layoutId = layoutId; 762 } 763 } 764} 765