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