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