ViewDataBinding.java revision 716ba89e7f459f49ea85070d4710c1d79d715298
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 = getBinding(v); 125 binding.mRebindRunnable.run(); 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 final 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 // null api < 16 190 private Choreographer mChoreographer; 191 192 private final Choreographer.FrameCallback mFrameCallback; 193 194 // null api >= 16 195 private Handler mUIThreadHandler; 196 197 protected ViewDataBinding(View root, int localFieldCount) { 198 mLocalFieldObservers = new WeakListener[localFieldCount]; 199 this.mRoot = root; 200 if (Looper.myLooper() == null) { 201 throw new IllegalStateException("DataBinding must be created in view's UI Thread"); 202 } 203 if (USE_CHOREOGRAPHER) { 204 mChoreographer = Choreographer.getInstance(); 205 mFrameCallback = new Choreographer.FrameCallback() { 206 @Override 207 public void doFrame(long frameTimeNanos) { 208 mRebindRunnable.run(); 209 } 210 }; 211 } else { 212 mFrameCallback = null; 213 mUIThreadHandler = new Handler(Looper.myLooper()); 214 } 215 } 216 217 protected void setRootTag(View view) { 218 if (USE_TAG_ID) { 219 view.setTag(R.id.dataBinding, this); 220 } else { 221 view.setTag(this); 222 } 223 } 224 225 protected void setRootTag(View[] views) { 226 if (USE_TAG_ID) { 227 for (View view : views) { 228 view.setTag(R.id.dataBinding, this); 229 } 230 } else { 231 for (View view : views) { 232 view.setTag(this); 233 } 234 } 235 } 236 237 public static int getBuildSdkInt() { 238 return SDK_INT; 239 } 240 241 /** 242 * Called when an observed object changes. Sets the appropriate dirty flag if applicable. 243 * @param localFieldId The index into mLocalFieldObservers that this Object resides in. 244 * @param object The object that has changed. 245 * @param fieldId The BR ID of the field being changed or _all if 246 * no specific field is being notified. 247 * @return true if this change should cause a change to the UI. 248 */ 249 protected abstract boolean onFieldChange(int localFieldId, Object object, int fieldId); 250 251 /** 252 * Set a value value in the Binding class. 253 * <p> 254 * Typically, the developer will be able to call the subclass's set method directly. For 255 * example, if there is a variable <code>x</code> in the Binding, a <code>setX</code> method 256 * will be generated. However, there are times when the specific subclass of ViewDataBinding 257 * is unknown, so the generated method cannot be discovered without reflection. The 258 * setVariable call allows the values of variables to be set without reflection. 259 * 260 * @param variableId the BR id of the variable to be set. For example, if the variable is 261 * <code>x</code>, then variableId will be <code>BR.x</code>. 262 * @param value The new value of the variable to be set. 263 * @return <code>true</code> if the variable exists in the binding or <code>false</code> 264 * otherwise. 265 */ 266 public abstract boolean setVariable(int variableId, Object value); 267 268 /** 269 * Add a listener to be called when reevaluating dirty fields. This also allows automatic 270 * updates to be halted, but does not stop explicit calls to {@link #executePendingBindings()}. 271 * 272 * @param listener The listener to add. 273 */ 274 public void addOnRebindCallback(OnRebindCallback listener) { 275 if (mRebindCallbacks == null) { 276 mRebindCallbacks = new CallbackRegistry<OnRebindCallback, ViewDataBinding, Void>(REBIND_NOTIFIER); 277 } 278 mRebindCallbacks.add(listener); 279 } 280 281 /** 282 * Removes a listener that was added in {@link #addOnRebindCallback(OnRebindCallback)}. 283 * 284 * @param listener The listener to remove. 285 */ 286 public void removeOnRebindCallback(OnRebindCallback listener) { 287 if (mRebindCallbacks != null) { 288 mRebindCallbacks.remove(listener); 289 } 290 } 291 292 /** 293 * Evaluates the pending bindings, updating any Views that have expressions bound to 294 * modified variables. This <b>must</b> be run on the UI thread. 295 */ 296 public void executePendingBindings() { 297 if (mIsExecutingPendingBindings) { 298 requestRebind(); 299 return; 300 } 301 if (!hasPendingBindings()) { 302 return; 303 } 304 mIsExecutingPendingBindings = true; 305 mRebindHalted = false; 306 if (mRebindCallbacks != null) { 307 mRebindCallbacks.notifyCallbacks(this, REBIND, null); 308 309 // The onRebindListeners will change mPendingHalted 310 if (mRebindHalted) { 311 mRebindCallbacks.notifyCallbacks(this, HALTED, null); 312 } 313 } 314 if (!mRebindHalted) { 315 executeBindings(); 316 if (mRebindCallbacks != null) { 317 mRebindCallbacks.notifyCallbacks(this, REBOUND, null); 318 } 319 } 320 mIsExecutingPendingBindings = false; 321 } 322 323 void forceExecuteBindings() { 324 executeBindings(); 325 } 326 327 protected abstract void executeBindings(); 328 329 /** 330 * Invalidates all binding expressions and requests a new rebind to refresh UI. 331 */ 332 public abstract void invalidateAll(); 333 334 /** 335 * Returns whether the UI needs to be refresh to represent the current data. 336 * 337 * @return true if any field has changed and the binding should be evaluated. 338 */ 339 public abstract boolean hasPendingBindings(); 340 341 /** 342 * Removes binding listeners to expression variables. 343 */ 344 public void unbind() { 345 for (WeakListener weakListener : mLocalFieldObservers) { 346 if (weakListener != null) { 347 weakListener.unregister(); 348 } 349 } 350 } 351 352 @Override 353 protected void finalize() throws Throwable { 354 unbind(); 355 } 356 357 static ViewDataBinding getBinding(View v) { 358 if (v != null) { 359 if (USE_TAG_ID) { 360 return (ViewDataBinding) v.getTag(R.id.dataBinding); 361 } else { 362 final Object tag = v.getTag(); 363 if (tag instanceof ViewDataBinding) { 364 return (ViewDataBinding) tag; 365 } 366 } 367 } 368 return null; 369 } 370 371 /** 372 * Returns the outermost View in the layout file associated with the Binding. 373 * @return the outermost View in the layout file associated with the Binding. 374 */ 375 public View getRoot() { 376 return mRoot; 377 } 378 379 private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) { 380 boolean result = onFieldChange(mLocalFieldId, object, fieldId); 381 if (result) { 382 requestRebind(); 383 } 384 } 385 386 protected boolean unregisterFrom(int localFieldId) { 387 WeakListener listener = mLocalFieldObservers[localFieldId]; 388 if (listener != null) { 389 return listener.unregister(); 390 } 391 return false; 392 } 393 394 protected void requestRebind() { 395 synchronized (this) { 396 if (mPendingRebind) { 397 return; 398 } 399 mPendingRebind = true; 400 } 401 if (USE_CHOREOGRAPHER) { 402 mChoreographer.postFrameCallback(mFrameCallback); 403 } else { 404 mUIThreadHandler.post(mRebindRunnable); 405 } 406 407 } 408 409 protected Object getObservedField(int localFieldId) { 410 WeakListener listener = mLocalFieldObservers[localFieldId]; 411 if (listener == null) { 412 return null; 413 } 414 return listener.getTarget(); 415 } 416 417 private boolean updateRegistration(int localFieldId, Object observable, 418 CreateWeakListener listenerCreator) { 419 if (observable == null) { 420 return unregisterFrom(localFieldId); 421 } 422 WeakListener listener = mLocalFieldObservers[localFieldId]; 423 if (listener == null) { 424 registerTo(localFieldId, observable, listenerCreator); 425 return true; 426 } 427 if (listener.getTarget() == observable) { 428 return false;//nothing to do, same object 429 } 430 unregisterFrom(localFieldId); 431 registerTo(localFieldId, observable, listenerCreator); 432 return true; 433 } 434 435 protected boolean updateRegistration(int localFieldId, Observable observable) { 436 return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER); 437 } 438 439 protected boolean updateRegistration(int localFieldId, ObservableList observable) { 440 return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER); 441 } 442 443 protected boolean updateRegistration(int localFieldId, ObservableMap observable) { 444 return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER); 445 } 446 447 protected void registerTo(int localFieldId, Object observable, 448 CreateWeakListener listenerCreator) { 449 if (observable == null) { 450 return; 451 } 452 WeakListener listener = mLocalFieldObservers[localFieldId]; 453 if (listener == null) { 454 listener = listenerCreator.create(this, localFieldId); 455 mLocalFieldObservers[localFieldId] = listener; 456 } 457 listener.setTarget(observable); 458 } 459 460 protected static ViewDataBinding bind(View view, int layoutId) { 461 return DataBindingUtil.bind(view, layoutId); 462 } 463 464 /** 465 * Walks the view hierarchy under root and pulls out tagged Views, includes, and views with 466 * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find 467 * all bound and ID'd views. 468 * 469 * @param root The root of the view hierarchy to walk. 470 * @param numBindings The total number of ID'd views, views with expressions, and includes 471 * @param includes The include layout information, indexed by their container's index. 472 * @param viewsWithIds Indexes of views that don't have tags, but have IDs. 473 * @return An array of size numBindings containing all Views in the hierarchy that have IDs 474 * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for 475 * included layouts. 476 */ 477 protected static Object[] mapBindings(View root, int numBindings, 478 IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) { 479 Object[] bindings = new Object[numBindings]; 480 mapBindings(root, bindings, includes, viewsWithIds, true); 481 return bindings; 482 } 483 484 /** 485 * Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with 486 * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find 487 * all bound and ID'd views. 488 * 489 * @param roots The root Views of the view hierarchy to walk. This is used with merge tags. 490 * @param numBindings The total number of ID'd views, views with expressions, and includes 491 * @param includes The include layout information, indexed by their container's index. 492 * @param viewsWithIds Indexes of views that don't have tags, but have IDs. 493 * @return An array of size numBindings containing all Views in the hierarchy that have IDs 494 * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for 495 * included layouts. 496 */ 497 protected static Object[] mapBindings(View[] roots, int numBindings, 498 IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) { 499 Object[] bindings = new Object[numBindings]; 500 for (int i = 0; i < roots.length; i++) { 501 mapBindings(roots[i], bindings, includes, viewsWithIds, true); 502 } 503 return bindings; 504 } 505 506 private static void mapBindings(View view, Object[] bindings, 507 IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds, boolean isRoot) { 508 final IncludedLayoutIndex[] includedLayoutIndexes; 509 final ViewDataBinding existingBinding = getBinding(view); 510 if (existingBinding != null) { 511 return; 512 } 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 if (bindings[index] == null) { 520 bindings[index] = view; 521 } 522 includedLayoutIndexes = includes == null ? null : includes[index]; 523 isBound = true; 524 } else { 525 includedLayoutIndexes = null; 526 } 527 } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) { 528 int tagIndex = parseTagInt(tag, BINDING_NUMBER_START); 529 if (bindings[tagIndex] == null) { 530 bindings[tagIndex] = view; 531 } 532 isBound = true; 533 includedLayoutIndexes = includes == null ? null : includes[tagIndex]; 534 } else { 535 // Not a bound view 536 includedLayoutIndexes = null; 537 } 538 if (!isBound) { 539 final int id = view.getId(); 540 if (id > 0) { 541 int index; 542 if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 && 543 bindings[index] == null) { 544 bindings[index] = view; 545 } 546 } 547 } 548 549 if (view instanceof ViewGroup) { 550 final ViewGroup viewGroup = (ViewGroup) view; 551 final int count = viewGroup.getChildCount(); 552 int minInclude = 0; 553 for (int i = 0; i < count; i++) { 554 final View child = viewGroup.getChildAt(i); 555 boolean isInclude = false; 556 if (includedLayoutIndexes != null) { 557 String childTag = (String) child.getTag(); 558 if (childTag != null && childTag.endsWith("_0") && 559 childTag.startsWith("layout") && childTag.indexOf('/') > 0) { 560 // This *could* be an include. Test against the expected includes. 561 int includeIndex = findIncludeIndex(childTag, minInclude, 562 includedLayoutIndexes); 563 if (includeIndex >= 0) { 564 isInclude = true; 565 minInclude = includeIndex + 1; 566 IncludedLayoutIndex include = includedLayoutIndexes[includeIndex]; 567 int lastMatchingIndex = findLastMatching(viewGroup, i); 568 if (lastMatchingIndex == i) { 569 bindings[include.index] = DataBindingUtil.bind(child, 570 include.layoutId); 571 } else { 572 final int includeCount = lastMatchingIndex - i + 1; 573 final View[] included = new View[includeCount]; 574 for (int j = 0; j < includeCount; j++) { 575 included[j] = viewGroup.getChildAt(i + j); 576 } 577 bindings[include.index] = DataBindingUtil.bind(included, 578 include.layoutId); 579 i += includeCount - 1; 580 } 581 } 582 } 583 } 584 if (!isInclude) { 585 mapBindings(child, bindings, includes, viewsWithIds, false); 586 } 587 } 588 } 589 } 590 591 private static int findIncludeIndex(String tag, int minInclude, 592 IncludedLayoutIndex[] layoutIndexes) { 593 final int slashIndex = tag.indexOf('/'); 594 final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2); 595 596 final int length = layoutIndexes.length; 597 for (int i = minInclude; i < length; i++) { 598 final IncludedLayoutIndex layoutIndex = layoutIndexes[i]; 599 if (TextUtils.equals(layoutName, layoutIndex.layout)) { 600 return i; 601 } 602 } 603 return -1; 604 } 605 606 private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) { 607 final View firstView = viewGroup.getChildAt(firstIncludedIndex); 608 final String firstViewTag = (String) firstView.getTag(); 609 final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0" 610 final int tagSequenceIndex = tagBase.length(); 611 612 final int count = viewGroup.getChildCount(); 613 int max = firstIncludedIndex; 614 for (int i = firstIncludedIndex + 1; i < count; i++) { 615 final View view = viewGroup.getChildAt(i); 616 final String tag = (String) view.getTag(); 617 if (tag != null && tag.startsWith(tagBase)) { 618 if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') { 619 return max; // Found another instance of the include 620 } 621 if (isNumeric(tag, tagSequenceIndex)) { 622 max = i; 623 } 624 } 625 } 626 return max; 627 } 628 629 private static boolean isNumeric(String tag, int startIndex) { 630 int length = tag.length(); 631 if (length == startIndex) { 632 return false; // no numerals 633 } 634 for (int i = startIndex; i < length; i++) { 635 if (!Character.isDigit(tag.charAt(i))) { 636 return false; 637 } 638 } 639 return true; 640 } 641 642 /** 643 * Parse the tag without creating a new String object. This is fast and assumes the 644 * tag is in the correct format. 645 * @param str The tag string. 646 * @return The binding tag number parsed from the tag string. 647 */ 648 private static int parseTagInt(String str, int startIndex) { 649 final int end = str.length(); 650 int val = 0; 651 for (int i = startIndex; i < end; i++) { 652 val *= 10; 653 char c = str.charAt(i); 654 val += (c - '0'); 655 } 656 return val; 657 } 658 659 private interface ObservableReference<T> { 660 WeakListener<T> getListener(); 661 void addListener(T target); 662 void removeListener(T target); 663 } 664 665 private static class WeakListener<T> extends WeakReference<ViewDataBinding> { 666 private final ObservableReference<T> mObservable; 667 protected final int mLocalFieldId; 668 private T mTarget; 669 670 public WeakListener(ViewDataBinding binder, int localFieldId, 671 ObservableReference<T> observable) { 672 super(binder); 673 mLocalFieldId = localFieldId; 674 mObservable = observable; 675 } 676 677 public void setTarget(T object) { 678 unregister(); 679 mTarget = object; 680 if (mTarget != null) { 681 mObservable.addListener(mTarget); 682 } 683 } 684 685 public boolean unregister() { 686 boolean unregistered = false; 687 if (mTarget != null) { 688 mObservable.removeListener(mTarget); 689 unregistered = true; 690 } 691 mTarget = null; 692 return unregistered; 693 } 694 695 public T getTarget() { 696 return mTarget; 697 } 698 699 protected ViewDataBinding getBinder() { 700 ViewDataBinding binder = get(); 701 if (binder == null) { 702 unregister(); // The binder is dead 703 } 704 return binder; 705 } 706 } 707 708 private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback 709 implements ObservableReference<Observable> { 710 final WeakListener<Observable> mListener; 711 712 public WeakPropertyListener(ViewDataBinding binder, int localFieldId) { 713 mListener = new WeakListener<Observable>(binder, localFieldId, this); 714 } 715 716 @Override 717 public WeakListener<Observable> getListener() { 718 return mListener; 719 } 720 721 @Override 722 public void addListener(Observable target) { 723 target.addOnPropertyChangedCallback(this); 724 } 725 726 @Override 727 public void removeListener(Observable target) { 728 target.removeOnPropertyChangedCallback(this); 729 } 730 731 @Override 732 public void onPropertyChanged(Observable sender, int propertyId) { 733 ViewDataBinding binder = mListener.getBinder(); 734 if (binder == null) { 735 return; 736 } 737 Observable obj = mListener.getTarget(); 738 if (obj != sender) { 739 return; // notification from the wrong object? 740 } 741 binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId); 742 } 743 } 744 745 private static class WeakListListener extends ObservableList.OnListChangedCallback 746 implements ObservableReference<ObservableList> { 747 final WeakListener<ObservableList> mListener; 748 749 public WeakListListener(ViewDataBinding binder, int localFieldId) { 750 mListener = new WeakListener<ObservableList>(binder, localFieldId, this); 751 } 752 753 @Override 754 public WeakListener<ObservableList> getListener() { 755 return mListener; 756 } 757 758 @Override 759 public void addListener(ObservableList target) { 760 target.addOnListChangedCallback(this); 761 } 762 763 @Override 764 public void removeListener(ObservableList target) { 765 target.removeOnListChangedCallback(this); 766 } 767 768 @Override 769 public void onChanged(ObservableList sender) { 770 ViewDataBinding binder = mListener.getBinder(); 771 if (binder == null) { 772 return; 773 } 774 ObservableList target = mListener.getTarget(); 775 if (target != sender) { 776 return; // We expect notifications only from sender 777 } 778 binder.handleFieldChange(mListener.mLocalFieldId, target, 0); 779 } 780 781 @Override 782 public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) { 783 onChanged(sender); 784 } 785 786 @Override 787 public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) { 788 onChanged(sender); 789 } 790 791 @Override 792 public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, 793 int itemCount) { 794 onChanged(sender); 795 } 796 797 @Override 798 public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) { 799 onChanged(sender); 800 } 801 } 802 803 private static class WeakMapListener extends ObservableMap.OnMapChangedCallback 804 implements ObservableReference<ObservableMap> { 805 final WeakListener<ObservableMap> mListener; 806 807 public WeakMapListener(ViewDataBinding binder, int localFieldId) { 808 mListener = new WeakListener<ObservableMap>(binder, localFieldId, this); 809 } 810 811 @Override 812 public WeakListener<ObservableMap> getListener() { 813 return mListener; 814 } 815 816 @Override 817 public void addListener(ObservableMap target) { 818 target.addOnMapChangedCallback(this); 819 } 820 821 @Override 822 public void removeListener(ObservableMap target) { 823 target.removeOnMapChangedCallback(this); 824 } 825 826 @Override 827 public void onMapChanged(ObservableMap sender, Object key) { 828 ViewDataBinding binder = mListener.getBinder(); 829 if (binder == null || sender != mListener.getTarget()) { 830 return; 831 } 832 binder.handleFieldChange(mListener.mLocalFieldId, sender, 0); 833 } 834 } 835 836 private interface CreateWeakListener { 837 WeakListener create(ViewDataBinding viewDataBinding, int localFieldId); 838 } 839 840 protected static class IncludedLayoutIndex { 841 public final String layout; 842 public final int index; 843 public final int layoutId; 844 845 public IncludedLayoutIndex(String layout, int index, int layoutId) { 846 this.layout = layout; 847 this.index = index; 848 this.layoutId = layoutId; 849 } 850 } 851} 852