ViewDataBinding.java revision 95d1b38adeb5963ec5337e7dd6177b4bb5a03619
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 android.annotation.TargetApi; 20import android.os.Build.VERSION; 21import android.os.Build.VERSION_CODES; 22import android.util.SparseIntArray; 23import android.view.View; 24import android.view.View.OnAttachStateChangeListener; 25import android.view.ViewGroup; 26 27import java.lang.ref.WeakReference; 28 29public abstract class ViewDataBinding { 30 31 /** 32 * Instead of directly accessing Build.VERSION.SDK_INT, generated code uses this value so that 33 * we can test API dependent behavior. 34 */ 35 static int SDK_INT = VERSION.SDK_INT; 36 37 /** 38 * Prefix for android:tag on Views with binding. The root View and include tags will not have 39 * android:tag attributes and will use ids instead. 40 */ 41 public static final String BINDING_TAG_PREFIX = "bindingTag"; 42 43 // The length of BINDING_TAG_PREFIX prevents calling length repeatedly. 44 private static final int BINDING_NUMBER_START = BINDING_TAG_PREFIX.length(); 45 46 /** 47 * Method object extracted out to attach a listener to a bound Observable object. 48 */ 49 private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() { 50 @Override 51 public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) { 52 return new WeakPropertyListener(viewDataBinding, localFieldId); 53 } 54 }; 55 56 /** 57 * Method object extracted out to attach a listener to a bound ObservableList object. 58 */ 59 private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() { 60 @Override 61 public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) { 62 return new WeakListListener(viewDataBinding, localFieldId); 63 } 64 }; 65 66 /** 67 * Method object extracted out to attach a listener to a bound ObservableMap object. 68 */ 69 private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() { 70 @Override 71 public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) { 72 return new WeakMapListener(viewDataBinding, localFieldId); 73 } 74 }; 75 76 private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER; 77 78 static { 79 if (VERSION.SDK_INT < VERSION_CODES.KITKAT) { 80 ROOT_REATTACHED_LISTENER = null; 81 } else { 82 ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() { 83 @TargetApi(VERSION_CODES.KITKAT) 84 @Override 85 public void onViewAttachedToWindow(View v) { 86 // execute the pending bindings. 87 ViewDataBinding binding = (ViewDataBinding) v.getTag(); 88 v.post(binding.mRebindRunnable); 89 v.removeOnAttachStateChangeListener(this); 90 } 91 92 @Override 93 public void onViewDetachedFromWindow(View v) { 94 } 95 }; 96 } 97 } 98 99 /** 100 * Runnable executed on animation heartbeat to rebind the dirty Views. 101 */ 102 private Runnable mRebindRunnable = new Runnable() { 103 @Override 104 public void run() { 105 if (mPendingRebind) { 106 boolean rebind = true; 107 if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { 108 rebind = mRoot.isAttachedToWindow(); 109 if (!rebind) { 110 // Don't execute the pending bindings until the View 111 // is attached again. 112 mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER); 113 } 114 } 115 if (rebind) { 116 mPendingRebind = false; 117 executePendingBindings(); 118 } 119 } 120 } 121 }; 122 123 /** 124 * Flag indicates that there are pending bindings that need to be reevaluated. 125 */ 126 private boolean mPendingRebind = false; 127 128 /** 129 * The observed expressions. 130 */ 131 private WeakListener[] mLocalFieldObservers; 132 133 /** 134 * The root View that this Binding is associated with. 135 */ 136 private final View mRoot; 137 138 protected ViewDataBinding(View root, int localFieldCount) { 139 mLocalFieldObservers = new WeakListener[localFieldCount]; 140 this.mRoot = root; 141 // TODO: When targeting ICS and above, use setTag(id, this) instead 142 this.mRoot.setTag(this); 143 } 144 145 public static int getBuildSdkInt() { 146 return SDK_INT; 147 } 148 149 /** 150 * Called when an observed object changes. Sets the appropriate dirty flag if applicable. 151 * @param localFieldId The index into mLocalFieldObservers that this Object resides in. 152 * @param object The object that has changed. 153 * @param fieldId The BR ID of the field being changed or _all if 154 * no specific field is being notified. 155 * @return true if this change should cause a change to the UI. 156 */ 157 protected abstract boolean onFieldChange(int localFieldId, Object object, int fieldId); 158 159 public abstract boolean setVariable(int variableId, Object variable); 160 161 /** 162 * Evaluates the pending bindings, updating any Views that have expressions bound to 163 * modified variables. This <b>must</b> be run on the UI thread. 164 */ 165 public abstract void executePendingBindings(); 166 167 /** 168 * Used internally to invalidate flags of included layouts. 169 */ 170 public abstract void invalidateAll(); 171 172 /** 173 * Removes binding listeners to expression variables. 174 */ 175 public void unbind() { 176 for (WeakListener weakListener : mLocalFieldObservers) { 177 if (weakListener != null) { 178 weakListener.unregister(); 179 } 180 } 181 } 182 183 @Override 184 protected void finalize() throws Throwable { 185 unbind(); 186 } 187 188 /** 189 * Returns the outermost View in the layout file associated with the Binding. 190 * @return the outermost View in the layout file associated with the Binding. 191 */ 192 public View getRoot() { 193 return mRoot; 194 } 195 196 private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) { 197 boolean result = onFieldChange(mLocalFieldId, object, fieldId); 198 if (result) { 199 requestRebind(); 200 } 201 } 202 203 protected boolean unregisterFrom(int localFieldId) { 204 WeakListener listener = mLocalFieldObservers[localFieldId]; 205 if (listener != null) { 206 return listener.unregister(); 207 } 208 return false; 209 } 210 211 protected void requestRebind() { 212 if (mPendingRebind) { 213 return; 214 } 215 mPendingRebind = true; 216 if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 217 mRoot.postOnAnimation(mRebindRunnable); 218 } else { 219 mRoot.post(mRebindRunnable); 220 } 221 } 222 223 protected Object getObservedField(int localFieldId) { 224 WeakListener listener = mLocalFieldObservers[localFieldId]; 225 if (listener == null) { 226 return null; 227 } 228 return listener.getTarget(); 229 } 230 231 private boolean updateRegistration(int localFieldId, Object observable, 232 CreateWeakListener listenerCreator) { 233 if (observable == null) { 234 return unregisterFrom(localFieldId); 235 } 236 WeakListener listener = mLocalFieldObservers[localFieldId]; 237 if (listener == null) { 238 registerTo(localFieldId, observable, listenerCreator); 239 return true; 240 } 241 if (listener.getTarget() == observable) { 242 return false;//nothing to do, same object 243 } 244 unregisterFrom(localFieldId); 245 registerTo(localFieldId, observable, listenerCreator); 246 return true; 247 } 248 249 protected boolean updateRegistration(int localFieldId, Observable observable) { 250 return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER); 251 } 252 253 protected boolean updateRegistration(int localFieldId, ObservableList observable) { 254 return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER); 255 } 256 257 protected boolean updateRegistration(int localFieldId, ObservableMap observable) { 258 return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER); 259 } 260 261 protected void registerTo(int localFieldId, Object observable, 262 CreateWeakListener listenerCreator) { 263 if (observable == null) { 264 return; 265 } 266 WeakListener listener = mLocalFieldObservers[localFieldId]; 267 if (listener == null) { 268 listener = listenerCreator.create(this, localFieldId); 269 mLocalFieldObservers[localFieldId] = listener; 270 } 271 listener.setTarget(observable); 272 } 273 274 /** 275 * Walk all children of root and assign tagged Views to associated indices in views. 276 * 277 * @param root The base of the View hierarchy to walk. 278 * @param views An array of all Views with binding expressions and all Views with IDs. This 279 * will start empty and will contain the found Views when this method completes. 280 * @param includes A mapping of include tag IDs to the index into the views array. 281 * @param viewsWithIds A mapping of views with IDs but without expressions to the index 282 * into the views array. 283 */ 284 private static void mapTaggedChildViews(View root, View[] views, SparseIntArray includes, 285 SparseIntArray viewsWithIds) { 286 if (root instanceof ViewGroup) { 287 ViewGroup viewGroup = (ViewGroup) root; 288 for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) { 289 mapTaggedViews(viewGroup.getChildAt(i), views, includes, viewsWithIds); 290 } 291 } 292 } 293 294 private static void mapTaggedViews(View view, View[] views, SparseIntArray includes, 295 SparseIntArray viewsWithIds) { 296 boolean visitChildren = true; 297 String tag = (String) view.getTag(); 298 if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) { 299 int tagIndex = parseTagInt(tag); 300 views[tagIndex] = view; 301 } else { 302 visitChildren = addViewWithId(view, views, includes, viewsWithIds); 303 } 304 if (visitChildren) { 305 mapTaggedChildViews(view, views, includes, viewsWithIds); 306 } 307 } 308 309 /** 310 * Walks the view hierarchy under root and pulls out tagged Views, includes, and views with 311 * IDs into a View[] that is returned. This is used to walk the view hierarchy once to find 312 * all bound and ID'd views. 313 * 314 * @param root The root of the view hierarchy to walk. 315 * @param numViews The total number of ID'd views and views with expressions. 316 * @param includes Views that are considered includes and should be treated as separate 317 * binders. 318 * @param viewsWithIds Views that don't have tags, but have IDs. 319 * @return An array of size numViews containing all Views in the hierarchy that have IDs 320 * (with elements in viewsWithIds) or are tagged containing expressions. 321 */ 322 protected static View[] mapChildViews(View root, int numViews, SparseIntArray includes, 323 SparseIntArray viewsWithIds) { 324 View[] views = new View[numViews]; 325 boolean visitChildren = addViewWithId(root, views, includes, viewsWithIds); 326 if (visitChildren) { 327 mapTaggedChildViews(root, views, includes, viewsWithIds); 328 } 329 return views; 330 } 331 332 private static boolean addViewWithId(View view, View[] views, SparseIntArray includes, 333 SparseIntArray viewsWithIds) { 334 final int id = view.getId(); 335 boolean visitChildren = true; 336 if (id > 0) { 337 int index; 338 if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0) { 339 views[index] = view; 340 } else if (includes != null && (index = includes.get(id, -1)) >= 0) { 341 views[index] = view; 342 visitChildren = false; 343 } 344 } 345 return visitChildren; 346 } 347 348 /** 349 * Parse the tag without creating a new String object. This is fast and assumes the 350 * tag is in the correct format. 351 * @param str The tag string. 352 * @return The binding tag number parsed from the tag string. 353 */ 354 private static int parseTagInt(String str) { 355 final int end = str.length(); 356 int val = 0; 357 for (int i = BINDING_NUMBER_START; i < end; i++) { 358 val *= 10; 359 char c = str.charAt(i); 360 val += (c - '0'); 361 } 362 return val; 363 } 364 365 private static abstract class WeakListener<T> { 366 private final WeakReference<ViewDataBinding> mBinder; 367 protected final int mLocalFieldId; 368 private T mTarget; 369 370 public WeakListener(ViewDataBinding binder, int localFieldId) { 371 mBinder = new WeakReference<ViewDataBinding>(binder); 372 mLocalFieldId = localFieldId; 373 } 374 375 public void setTarget(T object) { 376 unregister(); 377 mTarget = object; 378 if (mTarget != null) { 379 addListener(mTarget); 380 } 381 } 382 383 public boolean unregister() { 384 boolean unregistered = false; 385 if (mTarget != null) { 386 removeListener(mTarget); 387 unregistered = true; 388 } 389 mTarget = null; 390 return unregistered; 391 } 392 393 public T getTarget() { 394 return mTarget; 395 } 396 397 protected ViewDataBinding getBinder() { 398 ViewDataBinding binder = mBinder.get(); 399 if (binder == null) { 400 unregister(); // The binder is dead 401 } 402 return binder; 403 } 404 405 protected abstract void addListener(T target); 406 protected abstract void removeListener(T target); 407 } 408 409 private static class WeakPropertyListener extends WeakListener<Observable> 410 implements OnPropertyChangedListener { 411 public WeakPropertyListener(ViewDataBinding binder, int localFieldId) { 412 super(binder, localFieldId); 413 } 414 415 @Override 416 protected void addListener(Observable target) { 417 target.addOnPropertyChangedListener(this); 418 } 419 420 @Override 421 protected void removeListener(Observable target) { 422 target.removeOnPropertyChangedListener(this); 423 } 424 425 @Override 426 public void onPropertyChanged(Observable sender, int fieldId) { 427 ViewDataBinding binder = getBinder(); 428 if (binder == null) { 429 return; 430 } 431 Observable obj = getTarget(); 432 if (obj != sender) { 433 return; // notification from the wrong object? 434 } 435 binder.handleFieldChange(mLocalFieldId, sender, fieldId); 436 } 437 } 438 439 private static class WeakListListener extends WeakListener<ObservableList> 440 implements OnListChangedListener { 441 442 public WeakListListener(ViewDataBinding binder, int localFieldId) { 443 super(binder, localFieldId); 444 } 445 446 @Override 447 public void onChanged() { 448 ViewDataBinding binder = getBinder(); 449 if (binder == null) { 450 return; 451 } 452 ObservableList target = getTarget(); 453 if (target == null) { 454 return; // We don't expect any notifications from null targets 455 } 456 binder.handleFieldChange(mLocalFieldId, target, 0); 457 } 458 459 @Override 460 public void onItemRangeChanged(int positionStart, int itemCount) { 461 onChanged(); 462 } 463 464 @Override 465 public void onItemRangeInserted(int positionStart, int itemCount) { 466 onChanged(); 467 } 468 469 @Override 470 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 471 onChanged(); 472 } 473 474 @Override 475 public void onItemRangeRemoved(int positionStart, int itemCount) { 476 onChanged(); 477 } 478 479 @Override 480 protected void addListener(ObservableList target) { 481 target.addOnListChangedListener(this); 482 } 483 484 @Override 485 protected void removeListener(ObservableList target) { 486 target.removeOnListChangedListener(this); 487 } 488 } 489 490 private static class WeakMapListener extends WeakListener<ObservableMap> 491 implements OnMapChangedListener { 492 public WeakMapListener(ViewDataBinding binder, int localFieldId) { 493 super(binder, localFieldId); 494 } 495 496 @Override 497 protected void addListener(ObservableMap target) { 498 target.addOnMapChangedListener(this); 499 } 500 501 @Override 502 protected void removeListener(ObservableMap target) { 503 target.removeOnMapChangedListener(this); 504 } 505 506 @Override 507 public void onMapChanged(ObservableMap sender, Object key) { 508 ViewDataBinding binder = getBinder(); 509 if (binder == null || sender != getTarget()) { 510 return; 511 } 512 binder.handleFieldChange(mLocalFieldId, sender, 0); 513 } 514 } 515 516 private interface CreateWeakListener { 517 WeakListener create(ViewDataBinding viewDataBinding, int localFieldId); 518 } 519} 520