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