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