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