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