ViewDataBinding.java revision 610fd535ee15629671acb847f926b7a1f74eecae
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        mIsExecutingPendingBindings = true;
279        mRebindHalted = false;
280        if (mRebindCallbacks != null) {
281            mRebindCallbacks.notifyCallbacks(this, REBIND, null);
282
283            // The onRebindListeners will change mPendingHalted
284            if (mRebindHalted) {
285                mRebindCallbacks.notifyCallbacks(this, HALTED, null);
286            }
287        }
288        if (!mRebindHalted) {
289            executeBindings();
290            if (mRebindCallbacks != null) {
291                mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
292            }
293        }
294        mIsExecutingPendingBindings = false;
295    }
296
297    void forceExecuteBindings() {
298        executeBindings();
299    }
300
301    protected abstract void executeBindings();
302
303    /**
304     * Used internally to invalidate flags of included layouts.
305     */
306    public abstract void invalidateAll();
307
308    /**
309     * Removes binding listeners to expression variables.
310     */
311    public void unbind() {
312        for (WeakListener weakListener : mLocalFieldObservers) {
313            if (weakListener != null) {
314                weakListener.unregister();
315            }
316        }
317    }
318
319    @Override
320    protected void finalize() throws Throwable {
321        unbind();
322    }
323
324    static ViewDataBinding getBinding(View v) {
325        if (USE_TAG_ID) {
326            return (ViewDataBinding) v.getTag(R.id.dataBinding);
327        } else {
328            final Object tag = v.getTag();
329            if (tag instanceof ViewDataBinding) {
330                return (ViewDataBinding) tag;
331            }
332        }
333        return null;
334    }
335
336    /**
337     * Returns the outermost View in the layout file associated with the Binding.
338     * @return the outermost View in the layout file associated with the Binding.
339     */
340    public View getRoot() {
341        return mRoot;
342    }
343
344    private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
345        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
346        if (result) {
347            requestRebind();
348        }
349    }
350
351    protected boolean unregisterFrom(int localFieldId) {
352        WeakListener listener = mLocalFieldObservers[localFieldId];
353        if (listener != null) {
354            return listener.unregister();
355        }
356        return false;
357    }
358
359    protected void requestRebind() {
360        synchronized (this) {
361            if (mPendingRebind) {
362                return;
363            }
364            mPendingRebind = true;
365        }
366        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
367            mRoot.postOnAnimation(mRebindRunnable);
368        } else {
369            mRoot.post(mRebindRunnable);
370        }
371    }
372
373    protected Object getObservedField(int localFieldId) {
374        WeakListener listener = mLocalFieldObservers[localFieldId];
375        if (listener == null) {
376            return null;
377        }
378        return listener.getTarget();
379    }
380
381    private boolean updateRegistration(int localFieldId, Object observable,
382            CreateWeakListener listenerCreator) {
383        if (observable == null) {
384            return unregisterFrom(localFieldId);
385        }
386        WeakListener listener = mLocalFieldObservers[localFieldId];
387        if (listener == null) {
388            registerTo(localFieldId, observable, listenerCreator);
389            return true;
390        }
391        if (listener.getTarget() == observable) {
392            return false;//nothing to do, same object
393        }
394        unregisterFrom(localFieldId);
395        registerTo(localFieldId, observable, listenerCreator);
396        return true;
397    }
398
399    protected boolean updateRegistration(int localFieldId, Observable observable) {
400        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
401    }
402
403    protected boolean updateRegistration(int localFieldId, ObservableList observable) {
404        return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
405    }
406
407    protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
408        return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
409    }
410
411    protected void registerTo(int localFieldId, Object observable,
412            CreateWeakListener listenerCreator) {
413        if (observable == null) {
414            return;
415        }
416        WeakListener listener = mLocalFieldObservers[localFieldId];
417        if (listener == null) {
418            listener = listenerCreator.create(this, localFieldId);
419            mLocalFieldObservers[localFieldId] = listener;
420        }
421        listener.setTarget(observable);
422    }
423
424    protected static ViewDataBinding bind(View view, int layoutId) {
425        return DataBindingUtil.bind(view, layoutId);
426    }
427
428    /**
429     * Walks the view hierarchy under root and pulls out tagged Views, includes, and views with
430     * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
431     * all bound and ID'd views.
432     *
433     * @param root The root of the view hierarchy to walk.
434     * @param numBindings The total number of ID'd views, views with expressions, and includes
435     * @param includes The include layout information, indexed by their container's index.
436     * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
437     * @return An array of size numBindings containing all Views in the hierarchy that have IDs
438     * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
439     * included layouts.
440     */
441    protected static Object[] mapBindings(View root, int numBindings,
442            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) {
443        Object[] bindings = new Object[numBindings];
444        mapBindings(root, bindings, includes, viewsWithIds, true);
445        return bindings;
446    }
447
448    /**
449     * Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with
450     * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
451     * all bound and ID'd views.
452     *
453     * @param roots The root Views of the view hierarchy to walk. This is used with merge tags.
454     * @param numBindings The total number of ID'd views, views with expressions, and includes
455     * @param includes The include layout information, indexed by their container's index.
456     * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
457     * @return An array of size numBindings containing all Views in the hierarchy that have IDs
458     * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
459     * included layouts.
460     */
461    protected static Object[] mapBindings(View[] roots, int numBindings,
462            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) {
463        Object[] bindings = new Object[numBindings];
464        for (int i = 0; i < roots.length; i++) {
465            mapBindings(roots[i], bindings, includes, viewsWithIds, true);
466        }
467        return bindings;
468    }
469
470    private static void mapBindings(View view, Object[] bindings,
471            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds, boolean isRoot) {
472        final IncludedLayoutIndex[] includedLayoutIndexes;
473        final String tag = (String) view.getTag();
474        boolean isBound = false;
475        if (isRoot && tag != null && tag.startsWith("layout")) {
476            final int underscoreIndex = tag.lastIndexOf('_');
477            if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
478                final int index = parseTagInt(tag, underscoreIndex + 1);
479                bindings[index] = view;
480                includedLayoutIndexes = includes == null ? null : includes[index];
481                isBound = true;
482            } else {
483                includedLayoutIndexes = null;
484            }
485        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
486            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
487            bindings[tagIndex] = view;
488            isBound = true;
489            includedLayoutIndexes = includes == null ? null : includes[tagIndex];
490        } else {
491            // Not a bound view
492            includedLayoutIndexes = null;
493        }
494        if (!isBound) {
495            final int id = view.getId();
496            if (id > 0) {
497                int index;
498                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0) {
499                    bindings[index] = view;
500                }
501            }
502        }
503
504        if (view instanceof  ViewGroup) {
505            final ViewGroup viewGroup = (ViewGroup) view;
506            final int count = viewGroup.getChildCount();
507            int minInclude = 0;
508            for (int i = 0; i < count; i++) {
509                final View child = viewGroup.getChildAt(i);
510                boolean isInclude = false;
511                if (includedLayoutIndexes != null) {
512                    String childTag = (String) child.getTag();
513                    if (childTag != null && childTag.endsWith("_0") &&
514                            childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
515                        // This *could* be an include. Test against the expected includes.
516                        int includeIndex = findIncludeIndex(childTag, minInclude,
517                                includedLayoutIndexes);
518                        if (includeIndex >= 0) {
519                            isInclude = true;
520                            minInclude = includeIndex + 1;
521                            IncludedLayoutIndex include = includedLayoutIndexes[includeIndex];
522                            int lastMatchingIndex = findLastMatching(viewGroup, i);
523                            if (lastMatchingIndex == i) {
524                                bindings[include.index] = DataBindingUtil.bind(child,
525                                        include.layoutId);
526                            } else {
527                                final int includeCount =  lastMatchingIndex - i + 1;
528                                final View[] included = new View[includeCount];
529                                for (int j = 0; j < includeCount; j++) {
530                                    included[j] = viewGroup.getChildAt(i + j);
531                                }
532                                bindings[include.index] = DataBindingUtil.bind(included,
533                                        include.layoutId);
534                                i += includeCount - 1;
535                            }
536                        }
537                    }
538                }
539                if (!isInclude) {
540                    mapBindings(child, bindings, includes, viewsWithIds, false);
541                }
542            }
543        }
544    }
545
546    private static int findIncludeIndex(String tag, int minInclude,
547            IncludedLayoutIndex[] layoutIndexes) {
548        final int slashIndex = tag.indexOf('/');
549        final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2);
550
551        final int length = layoutIndexes.length;
552        for (int i = minInclude; i < length; i++) {
553            final IncludedLayoutIndex layoutIndex = layoutIndexes[i];
554            if (TextUtils.equals(layoutName, layoutIndex.layout)) {
555                return i;
556            }
557        }
558        return -1;
559    }
560
561    private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) {
562        final View firstView = viewGroup.getChildAt(firstIncludedIndex);
563        final String firstViewTag = (String) firstView.getTag();
564        final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0"
565        final int tagSequenceIndex = tagBase.length();
566
567        final int count = viewGroup.getChildCount();
568        int max = firstIncludedIndex;
569        for (int i = firstIncludedIndex + 1; i < count; i++) {
570            final View view = viewGroup.getChildAt(i);
571            final String tag = (String) view.getTag();
572            if (tag != null && tag.startsWith(tagBase)) {
573                if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') {
574                    return max; // Found another instance of the include
575                }
576                if (isNumeric(tag, tagSequenceIndex)) {
577                    max = i;
578                }
579            }
580        }
581        return max;
582    }
583
584    private static boolean isNumeric(String tag, int startIndex) {
585        int length = tag.length();
586        if (length == startIndex) {
587            return false; // no numerals
588        }
589        for (int i = startIndex; i < length; i++) {
590            if (!Character.isDigit(tag.charAt(i))) {
591                return false;
592            }
593        }
594        return true;
595    }
596
597    /**
598     * Parse the tag without creating a new String object. This is fast and assumes the
599     * tag is in the correct format.
600     * @param str The tag string.
601     * @return The binding tag number parsed from the tag string.
602     */
603    private static int parseTagInt(String str, int startIndex) {
604        final int end = str.length();
605        int val = 0;
606        for (int i = startIndex; i < end; i++) {
607            val *= 10;
608            char c = str.charAt(i);
609            val += (c - '0');
610        }
611        return val;
612    }
613
614    private static abstract class WeakListener<T> {
615        private final WeakReference<ViewDataBinding> mBinder;
616        protected final int mLocalFieldId;
617        private T mTarget;
618
619        public WeakListener(ViewDataBinding binder, int localFieldId) {
620            mBinder = new WeakReference<ViewDataBinding>(binder);
621            mLocalFieldId = localFieldId;
622        }
623
624        public void setTarget(T object) {
625            unregister();
626            mTarget = object;
627            if (mTarget != null) {
628                addListener(mTarget);
629            }
630        }
631
632        public boolean unregister() {
633            boolean unregistered = false;
634            if (mTarget != null) {
635                removeListener(mTarget);
636                unregistered = true;
637            }
638            mTarget = null;
639            return unregistered;
640        }
641
642        public T getTarget() {
643            return mTarget;
644        }
645
646        protected ViewDataBinding getBinder() {
647            ViewDataBinding binder = mBinder.get();
648            if (binder == null) {
649                unregister(); // The binder is dead
650            }
651            return binder;
652        }
653
654        protected abstract void addListener(T target);
655        protected abstract void removeListener(T target);
656    }
657
658    private static class WeakPropertyListener extends WeakListener<Observable>
659            implements OnPropertyChangedListener {
660        public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
661            super(binder, localFieldId);
662        }
663
664        @Override
665        protected void addListener(Observable target) {
666            target.addOnPropertyChangedListener(this);
667        }
668
669        @Override
670        protected void removeListener(Observable target) {
671            target.removeOnPropertyChangedListener(this);
672        }
673
674        @Override
675        public void onPropertyChanged(Observable sender, int fieldId) {
676            ViewDataBinding binder = getBinder();
677            if (binder == null) {
678                return;
679            }
680            Observable obj = getTarget();
681            if (obj != sender) {
682                return; // notification from the wrong object?
683            }
684            binder.handleFieldChange(mLocalFieldId, sender, fieldId);
685        }
686    }
687
688    private static class WeakListListener extends WeakListener<ObservableList>
689            implements OnListChangedListener {
690
691        public WeakListListener(ViewDataBinding binder, int localFieldId) {
692            super(binder, localFieldId);
693        }
694
695        @Override
696        public void onChanged() {
697            ViewDataBinding binder = getBinder();
698            if (binder == null) {
699                return;
700            }
701            ObservableList target = getTarget();
702            if (target == null) {
703                return; // We don't expect any notifications from null targets
704            }
705            binder.handleFieldChange(mLocalFieldId, target, 0);
706        }
707
708        @Override
709        public void onItemRangeChanged(int positionStart, int itemCount) {
710            onChanged();
711        }
712
713        @Override
714        public void onItemRangeInserted(int positionStart, int itemCount) {
715            onChanged();
716        }
717
718        @Override
719        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
720            onChanged();
721        }
722
723        @Override
724        public void onItemRangeRemoved(int positionStart, int itemCount) {
725            onChanged();
726        }
727
728        @Override
729        protected void addListener(ObservableList target) {
730            target.addOnListChangedListener(this);
731        }
732
733        @Override
734        protected void removeListener(ObservableList target) {
735            target.removeOnListChangedListener(this);
736        }
737    }
738
739    private static class WeakMapListener extends WeakListener<ObservableMap>
740            implements OnMapChangedListener {
741        public WeakMapListener(ViewDataBinding binder, int localFieldId) {
742            super(binder, localFieldId);
743        }
744
745        @Override
746        protected void addListener(ObservableMap target) {
747            target.addOnMapChangedListener(this);
748        }
749
750        @Override
751        protected void removeListener(ObservableMap target) {
752            target.removeOnMapChangedListener(this);
753        }
754
755        @Override
756        public void onMapChanged(ObservableMap sender, Object key) {
757            ViewDataBinding binder = getBinder();
758            if (binder == null || sender != getTarget()) {
759                return;
760            }
761            binder.handleFieldChange(mLocalFieldId, sender, 0);
762        }
763    }
764
765    private interface CreateWeakListener {
766        WeakListener create(ViewDataBinding viewDataBinding, int localFieldId);
767    }
768
769    protected static class IncludedLayoutIndex {
770        public final String layout;
771        public final int index;
772        public final int layoutId;
773
774        public IncludedLayoutIndex(String layout, int index, int layoutId) {
775            this.layout = layout;
776            this.index = index;
777            this.layoutId = layoutId;
778        }
779    }
780}
781