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