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