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