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