ViewDataBinding.java revision 7abd815495130b6853ae6dbcb169213dab97a1b9
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 com.android.databinding.library.R;
20
21import android.annotation.TargetApi;
22import android.databinding.CallbackRegistry.NotifierCallback;
23import android.os.Build.VERSION;
24import android.os.Build.VERSION_CODES;
25import android.text.TextUtils;
26import android.util.SparseIntArray;
27import android.view.View;
28import android.view.View.OnAttachStateChangeListener;
29import android.view.ViewGroup;
30
31import java.lang.ref.WeakReference;
32
33public abstract class ViewDataBinding {
34
35    /**
36     * Instead of directly accessing Build.VERSION.SDK_INT, generated code uses this value so that
37     * we can test API dependent behavior.
38     */
39    static int SDK_INT = VERSION.SDK_INT;
40
41    private static final int REBIND = 1;
42    private static final int HALTED = 2;
43    private static final int REBOUND = 3;
44
45    /**
46     * Prefix for android:tag on Views with binding. The root View and include tags will not have
47     * android:tag attributes and will use ids instead.
48     */
49    public static final String BINDING_TAG_PREFIX = "binding_";
50
51    // The length of BINDING_TAG_PREFIX prevents calling length repeatedly.
52    private static final int BINDING_NUMBER_START = BINDING_TAG_PREFIX.length();
53
54    // ICS (v 14) fixes a leak when using setTag(int, Object)
55    private static final boolean USE_TAG_ID = DataBinderMapper.TARGET_MIN_SDK >= 14;
56
57    /**
58     * Method object extracted out to attach a listener to a bound Observable object.
59     */
60    private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
61        @Override
62        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
63            return new WeakPropertyListener(viewDataBinding, localFieldId);
64        }
65    };
66
67    /**
68     * Method object extracted out to attach a listener to a bound ObservableList object.
69     */
70    private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() {
71        @Override
72        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
73            return new WeakListListener(viewDataBinding, localFieldId);
74        }
75    };
76
77    /**
78     * Method object extracted out to attach a listener to a bound ObservableMap object.
79     */
80    private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() {
81        @Override
82        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
83            return new WeakMapListener(viewDataBinding, localFieldId);
84        }
85    };
86
87    private static final CallbackRegistry.NotifierCallback<OnRebindCallback, ViewDataBinding, Void>
88        REBIND_NOTIFIER = new NotifierCallback<OnRebindCallback, ViewDataBinding, Void>() {
89        @Override
90        public void onNotifyCallback(OnRebindCallback callback, ViewDataBinding sender, int mode,
91                Void arg2) {
92            switch (mode) {
93                case REBIND:
94                    if (!callback.onPreBind(sender)) {
95                        sender.mRebindHalted = true;
96                    }
97                    break;
98                case HALTED:
99                    callback.onCanceled(sender);
100                    break;
101                case REBOUND:
102                    callback.onBound(sender);
103                    break;
104            }
105        }
106    };
107
108    private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER;
109
110    static {
111        if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
112            ROOT_REATTACHED_LISTENER = null;
113        } else {
114            ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
115                @TargetApi(VERSION_CODES.KITKAT)
116                @Override
117                public void onViewAttachedToWindow(View v) {
118                    // execute the pending bindings.
119                    final ViewDataBinding binding;
120                    if (USE_TAG_ID) {
121                        binding = (ViewDataBinding) v.getTag(R.id.dataBinding);
122                    } else {
123                        binding = (ViewDataBinding) v.getTag();
124                    }
125                    v.post(binding.mRebindRunnable);
126                    v.removeOnAttachStateChangeListener(this);
127                }
128
129                @Override
130                public void onViewDetachedFromWindow(View v) {
131                }
132            };
133        }
134    }
135
136    /**
137     * Runnable executed on animation heartbeat to rebind the dirty Views.
138     */
139    private Runnable mRebindRunnable = new Runnable() {
140        @Override
141        public void run() {
142            synchronized (this) {
143                mPendingRebind = false;
144            }
145            if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
146                // Nested so that we don't get a lint warning in IntelliJ
147                if (!mRoot.isAttachedToWindow()) {
148                    // Don't execute the pending bindings until the View
149                    // is attached again.
150                    mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
151                    mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
152                    return;
153                }
154            }
155            executePendingBindings();
156        }
157    };
158
159    /**
160     * Flag indicates that there are pending bindings that need to be reevaluated.
161     */
162    private boolean mPendingRebind = false;
163
164    /**
165     * Indicates that a onPreBind has stopped the executePendingBindings call.
166     */
167    private boolean mRebindHalted = false;
168
169    /**
170     * The observed expressions.
171     */
172    private WeakListener[] mLocalFieldObservers;
173
174    /**
175     * The root View that this Binding is associated with.
176     */
177    private final View mRoot;
178
179    /**
180     * The collection of OnRebindCallbacks.
181     */
182    private CallbackRegistry<OnRebindCallback, ViewDataBinding, Void> mRebindCallbacks;
183
184    /**
185     * Flag to prevent reentrant executePendingBinding calls.
186     */
187    private boolean mIsExecutingPendingBindings;
188
189    protected ViewDataBinding(View root, int localFieldCount) {
190        mLocalFieldObservers = new WeakListener[localFieldCount];
191        this.mRoot = root;
192    }
193
194    protected void setRootTag(View view) {
195        if (USE_TAG_ID) {
196            view.setTag(R.id.dataBinding, this);
197        } else {
198            view.setTag(this);
199        }
200    }
201
202    protected void setRootTag(View[] views) {
203        if (USE_TAG_ID) {
204            for (View view : views) {
205                view.setTag(R.id.dataBinding, this);
206            }
207        } else {
208            for (View view : views) {
209                view.setTag(this);
210            }
211        }
212    }
213
214    public static int getBuildSdkInt() {
215        return SDK_INT;
216    }
217
218    /**
219     * Called when an observed object changes. Sets the appropriate dirty flag if applicable.
220     * @param localFieldId The index into mLocalFieldObservers that this Object resides in.
221     * @param object The object that has changed.
222     * @param fieldId The BR ID of the field being changed or _all if
223     *                no specific field is being notified.
224     * @return true if this change should cause a change to the UI.
225     */
226    protected abstract boolean onFieldChange(int localFieldId, Object object, int fieldId);
227
228    /**
229     * Set a value value in the Binding class.
230     * <p>
231     * Typically, the developer will be able to call the subclass's set method directly. For
232     * example, if there is a variable <code>x</code> in the Binding, a <code>setX</code> method
233     * will be generated. However, there are times when the specific subclass of ViewDataBinding
234     * is unknown, so the generated method cannot be discovered without reflection. The
235     * setVariable call allows the values of variables to be set without reflection.
236     *
237     * @param variableId the BR id of the variable to be set. For example, if the variable is
238     *                   <code>x</code>, then variableId will be <code>BR.x</code>.
239     * @param value The new value of the variable to be set.
240     * @return <code>true</code> if the variable exists in the binding or <code>false</code>
241     * otherwise.
242     */
243    public abstract boolean setVariable(int variableId, Object value);
244
245    /**
246     * Add a listener to be called when reevaluating dirty fields. This also allows automatic
247     * updates to be halted, but does not stop explicit calls to {@link #executePendingBindings()}.
248     *
249     * @param listener The listener to add.
250     */
251    public void addOnRebindCallback(OnRebindCallback listener) {
252        if (mRebindCallbacks == null) {
253            mRebindCallbacks = new CallbackRegistry<OnRebindCallback, ViewDataBinding, Void>(REBIND_NOTIFIER);
254        }
255        mRebindCallbacks.add(listener);
256    }
257
258    /**
259     * Removes a listener that was added in {@link #addOnRebindCallback(OnRebindCallback)}.
260     *
261     * @param listener The listener to remove.
262     */
263    public void removeOnRebindCallback(OnRebindCallback listener) {
264        if (mRebindCallbacks != null) {
265            mRebindCallbacks.remove(listener);
266        }
267    }
268
269    /**
270     * Evaluates the pending bindings, updating any Views that have expressions bound to
271     * modified variables. This <b>must</b> be run on the UI thread.
272     */
273    public void executePendingBindings() {
274        if (mIsExecutingPendingBindings) {
275            requestRebind();
276            return;
277        }
278        if (!hasPendingBindings()) {
279            return;
280        }
281        mIsExecutingPendingBindings = true;
282        mRebindHalted = false;
283        if (mRebindCallbacks != null) {
284            mRebindCallbacks.notifyCallbacks(this, REBIND, null);
285
286            // The onRebindListeners will change mPendingHalted
287            if (mRebindHalted) {
288                mRebindCallbacks.notifyCallbacks(this, HALTED, null);
289            }
290        }
291        if (!mRebindHalted) {
292            executeBindings();
293            if (mRebindCallbacks != null) {
294                mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
295            }
296        }
297        mIsExecutingPendingBindings = false;
298    }
299
300    void forceExecuteBindings() {
301        executeBindings();
302    }
303
304    protected abstract void executeBindings();
305
306    /**
307     * Used internally to invalidate flags of included layouts.
308     * @hide
309     */
310    public abstract void invalidateAll();
311
312    /**
313     * @return true if any field has changed and the binding should be evaluated.
314     * @hide
315     */
316    public abstract boolean hasPendingBindings();
317
318    /**
319     * Removes binding listeners to expression variables.
320     */
321    public void unbind() {
322        for (WeakListener weakListener : mLocalFieldObservers) {
323            if (weakListener != null) {
324                weakListener.unregister();
325            }
326        }
327    }
328
329    @Override
330    protected void finalize() throws Throwable {
331        unbind();
332    }
333
334    static ViewDataBinding getBinding(View v) {
335        if (USE_TAG_ID) {
336            return (ViewDataBinding) v.getTag(R.id.dataBinding);
337        } else {
338            final Object tag = v.getTag();
339            if (tag instanceof ViewDataBinding) {
340                return (ViewDataBinding) tag;
341            }
342        }
343        return null;
344    }
345
346    /**
347     * Returns the outermost View in the layout file associated with the Binding.
348     * @return the outermost View in the layout file associated with the Binding.
349     */
350    public View getRoot() {
351        return mRoot;
352    }
353
354    private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
355        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
356        if (result) {
357            requestRebind();
358        }
359    }
360
361    protected boolean unregisterFrom(int localFieldId) {
362        WeakListener listener = mLocalFieldObservers[localFieldId];
363        if (listener != null) {
364            return listener.unregister();
365        }
366        return false;
367    }
368
369    protected void requestRebind() {
370        synchronized (this) {
371            if (mPendingRebind) {
372                return;
373            }
374            mPendingRebind = true;
375        }
376        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
377            mRoot.postOnAnimation(mRebindRunnable);
378        } else {
379            mRoot.post(mRebindRunnable);
380        }
381    }
382
383    protected Object getObservedField(int localFieldId) {
384        WeakListener listener = mLocalFieldObservers[localFieldId];
385        if (listener == null) {
386            return null;
387        }
388        return listener.getTarget();
389    }
390
391    private boolean updateRegistration(int localFieldId, Object observable,
392            CreateWeakListener listenerCreator) {
393        if (observable == null) {
394            return unregisterFrom(localFieldId);
395        }
396        WeakListener listener = mLocalFieldObservers[localFieldId];
397        if (listener == null) {
398            registerTo(localFieldId, observable, listenerCreator);
399            return true;
400        }
401        if (listener.getTarget() == observable) {
402            return false;//nothing to do, same object
403        }
404        unregisterFrom(localFieldId);
405        registerTo(localFieldId, observable, listenerCreator);
406        return true;
407    }
408
409    protected boolean updateRegistration(int localFieldId, Observable observable) {
410        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
411    }
412
413    protected boolean updateRegistration(int localFieldId, ObservableList observable) {
414        return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
415    }
416
417    protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
418        return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
419    }
420
421    protected void registerTo(int localFieldId, Object observable,
422            CreateWeakListener listenerCreator) {
423        if (observable == null) {
424            return;
425        }
426        WeakListener listener = mLocalFieldObservers[localFieldId];
427        if (listener == null) {
428            listener = listenerCreator.create(this, localFieldId);
429            mLocalFieldObservers[localFieldId] = listener;
430        }
431        listener.setTarget(observable);
432    }
433
434    protected static ViewDataBinding bind(View view, int layoutId) {
435        return DataBindingUtil.bind(view, layoutId);
436    }
437
438    /**
439     * Walks the view hierarchy under root and pulls out tagged Views, includes, and views with
440     * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
441     * all bound and ID'd views.
442     *
443     * @param root The root of the view hierarchy to walk.
444     * @param numBindings The total number of ID'd views, views with expressions, and includes
445     * @param includes The include layout information, indexed by their container's index.
446     * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
447     * @return An array of size numBindings containing all Views in the hierarchy that have IDs
448     * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
449     * included layouts.
450     */
451    protected static Object[] mapBindings(View root, int numBindings,
452            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) {
453        Object[] bindings = new Object[numBindings];
454        mapBindings(root, bindings, includes, viewsWithIds, true);
455        return bindings;
456    }
457
458    /**
459     * Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with
460     * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
461     * all bound and ID'd views.
462     *
463     * @param roots The root Views of the view hierarchy to walk. This is used with merge tags.
464     * @param numBindings The total number of ID'd views, views with expressions, and includes
465     * @param includes The include layout information, indexed by their container's index.
466     * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
467     * @return An array of size numBindings containing all Views in the hierarchy that have IDs
468     * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
469     * included layouts.
470     */
471    protected static Object[] mapBindings(View[] roots, int numBindings,
472            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) {
473        Object[] bindings = new Object[numBindings];
474        for (int i = 0; i < roots.length; i++) {
475            mapBindings(roots[i], bindings, includes, viewsWithIds, true);
476        }
477        return bindings;
478    }
479
480    private static void mapBindings(View view, Object[] bindings,
481            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds, boolean isRoot) {
482        final IncludedLayoutIndex[] includedLayoutIndexes;
483        final String tag = (String) view.getTag();
484        boolean isBound = false;
485        if (isRoot && tag != null && tag.startsWith("layout")) {
486            final int underscoreIndex = tag.lastIndexOf('_');
487            if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
488                final int index = parseTagInt(tag, underscoreIndex + 1);
489                bindings[index] = view;
490                includedLayoutIndexes = includes == null ? null : includes[index];
491                isBound = true;
492            } else {
493                includedLayoutIndexes = null;
494            }
495        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
496            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
497            bindings[tagIndex] = view;
498            isBound = true;
499            includedLayoutIndexes = includes == null ? null : includes[tagIndex];
500        } else {
501            // Not a bound view
502            includedLayoutIndexes = null;
503        }
504        if (!isBound) {
505            final int id = view.getId();
506            if (id > 0) {
507                int index;
508                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0) {
509                    bindings[index] = view;
510                }
511            }
512        }
513
514        if (view instanceof  ViewGroup) {
515            final ViewGroup viewGroup = (ViewGroup) view;
516            final int count = viewGroup.getChildCount();
517            int minInclude = 0;
518            for (int i = 0; i < count; i++) {
519                final View child = viewGroup.getChildAt(i);
520                boolean isInclude = false;
521                if (includedLayoutIndexes != null) {
522                    String childTag = (String) child.getTag();
523                    if (childTag != null && childTag.endsWith("_0") &&
524                            childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
525                        // This *could* be an include. Test against the expected includes.
526                        int includeIndex = findIncludeIndex(childTag, minInclude,
527                                includedLayoutIndexes);
528                        if (includeIndex >= 0) {
529                            isInclude = true;
530                            minInclude = includeIndex + 1;
531                            IncludedLayoutIndex include = includedLayoutIndexes[includeIndex];
532                            int lastMatchingIndex = findLastMatching(viewGroup, i);
533                            if (lastMatchingIndex == i) {
534                                bindings[include.index] = DataBindingUtil.bind(child,
535                                        include.layoutId);
536                            } else {
537                                final int includeCount =  lastMatchingIndex - i + 1;
538                                final View[] included = new View[includeCount];
539                                for (int j = 0; j < includeCount; j++) {
540                                    included[j] = viewGroup.getChildAt(i + j);
541                                }
542                                bindings[include.index] = DataBindingUtil.bind(included,
543                                        include.layoutId);
544                                i += includeCount - 1;
545                            }
546                        }
547                    }
548                }
549                if (!isInclude) {
550                    mapBindings(child, bindings, includes, viewsWithIds, false);
551                }
552            }
553        }
554    }
555
556    private static int findIncludeIndex(String tag, int minInclude,
557            IncludedLayoutIndex[] layoutIndexes) {
558        final int slashIndex = tag.indexOf('/');
559        final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2);
560
561        final int length = layoutIndexes.length;
562        for (int i = minInclude; i < length; i++) {
563            final IncludedLayoutIndex layoutIndex = layoutIndexes[i];
564            if (TextUtils.equals(layoutName, layoutIndex.layout)) {
565                return i;
566            }
567        }
568        return -1;
569    }
570
571    private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) {
572        final View firstView = viewGroup.getChildAt(firstIncludedIndex);
573        final String firstViewTag = (String) firstView.getTag();
574        final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0"
575        final int tagSequenceIndex = tagBase.length();
576
577        final int count = viewGroup.getChildCount();
578        int max = firstIncludedIndex;
579        for (int i = firstIncludedIndex + 1; i < count; i++) {
580            final View view = viewGroup.getChildAt(i);
581            final String tag = (String) view.getTag();
582            if (tag != null && tag.startsWith(tagBase)) {
583                if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') {
584                    return max; // Found another instance of the include
585                }
586                if (isNumeric(tag, tagSequenceIndex)) {
587                    max = i;
588                }
589            }
590        }
591        return max;
592    }
593
594    private static boolean isNumeric(String tag, int startIndex) {
595        int length = tag.length();
596        if (length == startIndex) {
597            return false; // no numerals
598        }
599        for (int i = startIndex; i < length; i++) {
600            if (!Character.isDigit(tag.charAt(i))) {
601                return false;
602            }
603        }
604        return true;
605    }
606
607    /**
608     * Parse the tag without creating a new String object. This is fast and assumes the
609     * tag is in the correct format.
610     * @param str The tag string.
611     * @return The binding tag number parsed from the tag string.
612     */
613    private static int parseTagInt(String str, int startIndex) {
614        final int end = str.length();
615        int val = 0;
616        for (int i = startIndex; i < end; i++) {
617            val *= 10;
618            char c = str.charAt(i);
619            val += (c - '0');
620        }
621        return val;
622    }
623
624    private static abstract class WeakListener<T> {
625        private final WeakReference<ViewDataBinding> mBinder;
626        protected final int mLocalFieldId;
627        private T mTarget;
628
629        public WeakListener(ViewDataBinding binder, int localFieldId) {
630            mBinder = new WeakReference<ViewDataBinding>(binder);
631            mLocalFieldId = localFieldId;
632        }
633
634        public void setTarget(T object) {
635            unregister();
636            mTarget = object;
637            if (mTarget != null) {
638                addListener(mTarget);
639            }
640        }
641
642        public boolean unregister() {
643            boolean unregistered = false;
644            if (mTarget != null) {
645                removeListener(mTarget);
646                unregistered = true;
647            }
648            mTarget = null;
649            return unregistered;
650        }
651
652        public T getTarget() {
653            return mTarget;
654        }
655
656        protected ViewDataBinding getBinder() {
657            ViewDataBinding binder = mBinder.get();
658            if (binder == null) {
659                unregister(); // The binder is dead
660            }
661            return binder;
662        }
663
664        protected abstract void addListener(T target);
665        protected abstract void removeListener(T target);
666    }
667
668    private static class WeakPropertyListener extends WeakListener<Observable>
669            implements OnPropertyChangedListener {
670        public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
671            super(binder, localFieldId);
672        }
673
674        @Override
675        protected void addListener(Observable target) {
676            target.addOnPropertyChangedListener(this);
677        }
678
679        @Override
680        protected void removeListener(Observable target) {
681            target.removeOnPropertyChangedListener(this);
682        }
683
684        @Override
685        public void onPropertyChanged(Observable sender, int fieldId) {
686            ViewDataBinding binder = getBinder();
687            if (binder == null) {
688                return;
689            }
690            Observable obj = getTarget();
691            if (obj != sender) {
692                return; // notification from the wrong object?
693            }
694            binder.handleFieldChange(mLocalFieldId, sender, fieldId);
695        }
696    }
697
698    private static class WeakListListener extends WeakListener<ObservableList>
699            implements OnListChangedListener {
700
701        public WeakListListener(ViewDataBinding binder, int localFieldId) {
702            super(binder, localFieldId);
703        }
704
705        @Override
706        public void onChanged() {
707            ViewDataBinding binder = getBinder();
708            if (binder == null) {
709                return;
710            }
711            ObservableList target = getTarget();
712            if (target == null) {
713                return; // We don't expect any notifications from null targets
714            }
715            binder.handleFieldChange(mLocalFieldId, target, 0);
716        }
717
718        @Override
719        public void onItemRangeChanged(int positionStart, int itemCount) {
720            onChanged();
721        }
722
723        @Override
724        public void onItemRangeInserted(int positionStart, int itemCount) {
725            onChanged();
726        }
727
728        @Override
729        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
730            onChanged();
731        }
732
733        @Override
734        public void onItemRangeRemoved(int positionStart, int itemCount) {
735            onChanged();
736        }
737
738        @Override
739        protected void addListener(ObservableList target) {
740            target.addOnListChangedListener(this);
741        }
742
743        @Override
744        protected void removeListener(ObservableList target) {
745            target.removeOnListChangedListener(this);
746        }
747    }
748
749    private static class WeakMapListener extends WeakListener<ObservableMap>
750            implements OnMapChangedListener {
751        public WeakMapListener(ViewDataBinding binder, int localFieldId) {
752            super(binder, localFieldId);
753        }
754
755        @Override
756        protected void addListener(ObservableMap target) {
757            target.addOnMapChangedListener(this);
758        }
759
760        @Override
761        protected void removeListener(ObservableMap target) {
762            target.removeOnMapChangedListener(this);
763        }
764
765        @Override
766        public void onMapChanged(ObservableMap sender, Object key) {
767            ViewDataBinding binder = getBinder();
768            if (binder == null || sender != getTarget()) {
769                return;
770            }
771            binder.handleFieldChange(mLocalFieldId, sender, 0);
772        }
773    }
774
775    private interface CreateWeakListener {
776        WeakListener create(ViewDataBinding viewDataBinding, int localFieldId);
777    }
778
779    protected static class IncludedLayoutIndex {
780        public final String layout;
781        public final int index;
782        public final int layoutId;
783
784        public IncludedLayoutIndex(String layout, int index, int layoutId) {
785            this.layout = layout;
786            this.index = index;
787            this.layoutId = layoutId;
788        }
789    }
790}
791