ViewDataBinding.java revision 5914aa7da50a90a4c705b5be02a215499d0ad232
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 protected 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 * Parse the tag without creating a new String object. This is fast and assumes the 319 * tag is in the correct format. 320 * @param str The tag string. 321 * @return The binding tag number parsed from the tag string. 322 */ 323 private static int parseTagInt(String str) { 324 final int end = str.length(); 325 int val = 0; 326 for (int i = BINDING_NUMBER_START; i < end; i++) { 327 val *= 10; 328 char c = str.charAt(i); 329 val += (c - '0'); 330 } 331 return val; 332 } 333 334 private static abstract class WeakListener<T> { 335 private final WeakReference<ViewDataBinding> mBinder; 336 protected final int mLocalFieldId; 337 private T mTarget; 338 339 public WeakListener(ViewDataBinding binder, int localFieldId) { 340 mBinder = new WeakReference<ViewDataBinding>(binder); 341 mLocalFieldId = localFieldId; 342 } 343 344 public void setTarget(T object) { 345 unregister(); 346 mTarget = object; 347 if (mTarget != null) { 348 addListener(mTarget); 349 } 350 } 351 352 public boolean unregister() { 353 boolean unregistered = false; 354 if (mTarget != null) { 355 removeListener(mTarget); 356 unregistered = true; 357 } 358 mTarget = null; 359 return unregistered; 360 } 361 362 public T getTarget() { 363 return mTarget; 364 } 365 366 protected ViewDataBinding getBinder() { 367 ViewDataBinding binder = mBinder.get(); 368 if (binder == null) { 369 unregister(); // The binder is dead 370 } 371 return binder; 372 } 373 374 protected abstract void addListener(T target); 375 protected abstract void removeListener(T target); 376 } 377 378 private static class WeakPropertyListener extends WeakListener<Observable> 379 implements OnPropertyChangedListener { 380 public WeakPropertyListener(ViewDataBinding binder, int localFieldId) { 381 super(binder, localFieldId); 382 } 383 384 @Override 385 protected void addListener(Observable target) { 386 target.addOnPropertyChangedListener(this); 387 } 388 389 @Override 390 protected void removeListener(Observable target) { 391 target.removeOnPropertyChangedListener(this); 392 } 393 394 @Override 395 public void onPropertyChanged(Observable sender, int fieldId) { 396 ViewDataBinding binder = getBinder(); 397 if (binder == null) { 398 return; 399 } 400 Observable obj = getTarget(); 401 if (obj != sender) { 402 return; // notification from the wrong object? 403 } 404 binder.handleFieldChange(mLocalFieldId, sender, fieldId); 405 } 406 } 407 408 private static class WeakListListener extends WeakListener<ObservableList> 409 implements OnListChangedListener { 410 411 public WeakListListener(ViewDataBinding binder, int localFieldId) { 412 super(binder, localFieldId); 413 } 414 415 @Override 416 public void onChanged() { 417 ViewDataBinding binder = getBinder(); 418 if (binder == null) { 419 return; 420 } 421 ObservableList target = getTarget(); 422 if (target == null) { 423 return; // We don't expect any notifications from null targets 424 } 425 binder.handleFieldChange(mLocalFieldId, target, 0); 426 } 427 428 @Override 429 public void onItemRangeChanged(int positionStart, int itemCount) { 430 onChanged(); 431 } 432 433 @Override 434 public void onItemRangeInserted(int positionStart, int itemCount) { 435 onChanged(); 436 } 437 438 @Override 439 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 440 onChanged(); 441 } 442 443 @Override 444 public void onItemRangeRemoved(int positionStart, int itemCount) { 445 onChanged(); 446 } 447 448 @Override 449 protected void addListener(ObservableList target) { 450 target.addOnListChangedListener(this); 451 } 452 453 @Override 454 protected void removeListener(ObservableList target) { 455 target.removeOnListChangedListener(this); 456 } 457 } 458 459 private static class WeakMapListener extends WeakListener<ObservableMap> 460 implements OnMapChangedListener { 461 public WeakMapListener(ViewDataBinding binder, int localFieldId) { 462 super(binder, localFieldId); 463 } 464 465 @Override 466 protected void addListener(ObservableMap target) { 467 target.addOnMapChangedListener(this); 468 } 469 470 @Override 471 protected void removeListener(ObservableMap target) { 472 target.removeOnMapChangedListener(this); 473 } 474 475 @Override 476 public void onMapChanged(ObservableMap sender, Object key) { 477 ViewDataBinding binder = getBinder(); 478 if (binder == null || sender != getTarget()) { 479 return; 480 } 481 binder.handleFieldChange(mLocalFieldId, sender, 0); 482 } 483 } 484 485 private interface CreateWeakListener { 486 WeakListener create(ViewDataBinding viewDataBinding, int localFieldId); 487 } 488} 489