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