• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 
17 package android.databinding;
18 
19 import android.app.Activity;
20 import android.support.annotation.Nullable;
21 import android.view.InflateException;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.view.ViewParent;
26 
27 /**
28  * Utility class to create {@link ViewDataBinding} from layouts.
29  */
30 public class DataBindingUtil {
31     private static DataBinderMapper sMapper = new DataBinderMapper();
32     private static DataBindingComponent sDefaultComponent = null;
33 
34     /**
35      * Prevent DataBindingUtil from being instantiated.
36      */
DataBindingUtil()37     private DataBindingUtil() {}
38 
39     /**
40      * Set the default {@link DataBindingComponent} to use for data binding.
41      * <p>
42      * <code>bindingComponent</code> may be passed as the first parameter of binding adapters.
43      * <p>
44      * When instance method BindingAdapters are used, the class instance for the binding adapter
45      * is retrieved from the DataBindingComponent.
46      */
setDefaultComponent(DataBindingComponent bindingComponent)47     public static void setDefaultComponent(DataBindingComponent bindingComponent) {
48         sDefaultComponent = bindingComponent;
49     }
50 
51     /**
52      * Returns the default {@link DataBindingComponent} used in data binding. This can be
53      * <code>null</code> if no default was set in
54      * {@link #setDefaultComponent(DataBindingComponent)}.
55      *
56      * @return the default {@link DataBindingComponent} used in data binding. This can be
57      * <code>null</code> if no default was set in
58      * {@link #setDefaultComponent(DataBindingComponent)}.
59      */
getDefaultComponent()60     public static DataBindingComponent getDefaultComponent() {
61         return sDefaultComponent;
62     }
63 
64     /**
65      * Inflates a binding layout and returns the newly-created binding for that layout.
66      * This uses the DataBindingComponent set in
67      * {@link #setDefaultComponent(DataBindingComponent)}.
68      * <p>
69      * Use this version only if <code>layoutId</code> is unknown in advance. Otherwise, use
70      * the generated Binding's inflate method to ensure type-safe inflation.
71      *
72      * @param inflater The LayoutInflater used to inflate the binding layout.
73      * @param layoutId The layout resource ID of the layout to inflate.
74      * @param parent Optional view to be the parent of the generated hierarchy
75      *               (if attachToParent is true), or else simply an object that provides
76      *               a set of LayoutParams values for root of the returned hierarchy
77      *               (if attachToParent is false.)
78      * @param attachToParent Whether the inflated hierarchy should be attached to the
79      *                       parent parameter. If false, parent is only used to create
80      *                       the correct subclass of LayoutParams for the root view in the XML.
81      * @return The newly-created binding for the inflated layout or <code>null</code> if
82      * the layoutId wasn't for a binding layout.
83      * @throws InflateException When a merge layout was used and attachToParent was false.
84      * @see #setDefaultComponent(DataBindingComponent)
85      */
inflate(LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent, boolean attachToParent)86     public static <T extends ViewDataBinding> T inflate(LayoutInflater inflater, int layoutId,
87             @Nullable ViewGroup parent, boolean attachToParent) {
88         return inflate(inflater, layoutId, parent, attachToParent, sDefaultComponent);
89     }
90 
91     /**
92      * Inflates a binding layout and returns the newly-created binding for that layout.
93      * <p>
94      * Use this version only if <code>layoutId</code> is unknown in advance. Otherwise, use
95      * the generated Binding's inflate method to ensure type-safe inflation.
96      *
97      * @param inflater The LayoutInflater used to inflate the binding layout.
98      * @param layoutId The layout resource ID of the layout to inflate.
99      * @param parent Optional view to be the parent of the generated hierarchy
100      *               (if attachToParent is true), or else simply an object that provides
101      *               a set of LayoutParams values for root of the returned hierarchy
102      *               (if attachToParent is false.)
103      * @param attachToParent Whether the inflated hierarchy should be attached to the
104      *                       parent parameter. If false, parent is only used to create
105      *                       the correct subclass of LayoutParams for the root view in the XML.
106      * @param bindingComponent The DataBindingComponent to use in the binding.
107      * @return The newly-created binding for the inflated layout or <code>null</code> if
108      * the layoutId wasn't for a binding layout.
109      * @throws InflateException When a merge layout was used and attachToParent was false.
110      */
inflate( LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent, boolean attachToParent, DataBindingComponent bindingComponent)111     public static <T extends ViewDataBinding> T inflate(
112             LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
113             boolean attachToParent, DataBindingComponent bindingComponent) {
114         final boolean useChildren = parent != null && attachToParent;
115         final int startChildren = useChildren ? parent.getChildCount() : 0;
116         final View view = inflater.inflate(layoutId, parent, attachToParent);
117         if (useChildren) {
118             return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);
119         } else {
120             return bind(bindingComponent, view, layoutId);
121         }
122     }
123 
124     /**
125      * Returns the binding for the given layout root or creates a binding if one
126      * does not exist. This uses the DataBindingComponent set in
127      * {@link #setDefaultComponent(DataBindingComponent)}.
128      * <p>
129      * Prefer using the generated Binding's <code>bind</code> method to ensure type-safe inflation
130      * when it is known that <code>root</code> has not yet been bound.
131      *
132      * @param root The root View of the inflated binding layout.
133      * @return A ViewDataBinding for the given root View. If one already exists, the
134      * existing one will be returned.
135      * @throws IllegalArgumentException when root is not from an inflated binding layout.
136      * @see #getBinding(View)
137      */
138     @SuppressWarnings("unchecked")
bind(View root)139     public static <T extends ViewDataBinding> T bind(View root) {
140         return bind(root, sDefaultComponent);
141     }
142 
143     /**
144      * Returns the binding for the given layout root or creates a binding if one
145      * does not exist.
146      * <p>
147      * Prefer using the generated Binding's <code>bind</code> method to ensure type-safe inflation
148      * when it is known that <code>root</code> has not yet been bound.
149      *
150      * @param root The root View of the inflated binding layout.
151      * @param bindingComponent The DataBindingComponent to use in data binding.
152      * @return A ViewDataBinding for the given root View. If one already exists, the
153      * existing one will be returned.
154      * @throws IllegalArgumentException when root is not from an inflated binding layout.
155      * @see #getBinding(View)
156      */
157     @SuppressWarnings("unchecked")
bind(View root, DataBindingComponent bindingComponent)158     public static <T extends ViewDataBinding> T bind(View root,
159             DataBindingComponent bindingComponent) {
160         T binding = getBinding(root);
161         if (binding != null) {
162             return binding;
163         }
164         Object tagObj = root.getTag();
165         if (!(tagObj instanceof String)) {
166             throw new IllegalArgumentException("View is not a binding layout");
167         } else {
168             String tag = (String) tagObj;
169             int layoutId = sMapper.getLayoutId(tag);
170             if (layoutId == 0) {
171                 throw new IllegalArgumentException("View is not a binding layout");
172             }
173             return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
174         }
175     }
176 
177     @SuppressWarnings("unchecked")
bind(DataBindingComponent bindingComponent, View[] roots, int layoutId)178     static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
179             int layoutId) {
180         return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
181     }
182 
bind(DataBindingComponent bindingComponent, View root, int layoutId)183     static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
184             int layoutId) {
185         return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
186     }
187 
188     /**
189      * Retrieves the binding responsible for the given View. If <code>view</code> is not a
190      * binding layout root, its parents will be searched for the binding. If there is no binding,
191      * <code>null</code> will be returned.
192      * <p>
193      * This differs from {@link #getBinding(View)} in that findBinding takes any view in the
194      * layout and searches for the binding associated with the root. <code>getBinding</code>
195      * takes only the root view.
196      *
197      * @param view A <code>View</code> in the bound layout.
198      * @return The ViewDataBinding associated with the given view or <code>null</code> if
199      * view is not part of a bound layout.
200      */
findBinding(View view)201     public static <T extends ViewDataBinding> T findBinding(View view) {
202         while (view != null) {
203             ViewDataBinding binding = ViewDataBinding.getBinding(view);
204             if (binding != null) {
205                 return (T) binding;
206             }
207             Object tag = view.getTag();
208             if (tag instanceof String) {
209                 String tagString = (String) tag;
210                 if (tagString.startsWith("layout") && tagString.endsWith("_0")) {
211                     final char nextChar = tagString.charAt(6);
212                     final int slashIndex = tagString.indexOf('/', 7);
213                     boolean isUnboundRoot = false;
214                     if (nextChar == '/') {
215                         // only one slash should exist
216                         isUnboundRoot = slashIndex == -1;
217                     } else if (nextChar == '-' && slashIndex != -1) {
218                         int nextSlashIndex = tagString.indexOf('/', slashIndex + 1);
219                         // only one slash should exist
220                         isUnboundRoot = nextSlashIndex == -1;
221                     }
222                     if (isUnboundRoot) {
223                         // An inflated, but unbound layout
224                         return null;
225                     }
226                 }
227             }
228             ViewParent viewParent = view.getParent();
229             if (viewParent instanceof View) {
230                 view = (View) viewParent;
231             } else {
232                 view = null;
233             }
234         }
235         return null;
236     }
237 
238     /**
239      * Retrieves the binding responsible for the given View layout root. If there is no binding,
240      * <code>null</code> will be returned. This uses the DataBindingComponent set in
241      * {@link #setDefaultComponent(DataBindingComponent)}.
242      *
243      * @param view The root <code>View</code> in the layout with binding.
244      * @return The ViewDataBinding associated with the given view or <code>null</code> if
245      * either the view is not a root View for a layout or view hasn't been bound.
246      */
getBinding(View view)247     public static <T extends ViewDataBinding> T getBinding(View view) {
248         return (T) ViewDataBinding.getBinding(view);
249     }
250 
251     /**
252      * Set the Activity's content view to the given layout and return the associated binding.
253      * The given layout resource must not be a merge layout.
254      *
255      * @param activity The Activity whose content View should change.
256      * @param layoutId The resource ID of the layout to be inflated, bound, and set as the
257      *                 Activity's content.
258      * @return The binding associated with the inflated content view.
259      */
setContentView(Activity activity, int layoutId)260     public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId) {
261         return setContentView(activity, layoutId, sDefaultComponent);
262     }
263 
264     /**
265      * Set the Activity's content view to the given layout and return the associated binding.
266      * The given layout resource must not be a merge layout.
267      *
268      * @param bindingComponent The DataBindingComponent to use in data binding.
269      * @param activity The Activity whose content View should change.
270      * @param layoutId The resource ID of the layout to be inflated, bound, and set as the
271      *                 Activity's content.
272      * @return The binding associated with the inflated content view.
273      */
setContentView(Activity activity, int layoutId, DataBindingComponent bindingComponent)274     public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId,
275             DataBindingComponent bindingComponent) {
276         activity.setContentView(layoutId);
277         View decorView = activity.getWindow().getDecorView();
278         ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
279         return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
280     }
281 
282     /**
283      * Converts the given BR id to its string representation which might be useful for logging
284      * purposes.
285      *
286      * @param id The integer id, which should be a field from BR class.
287      * @return The name if the BR id or null if id is out of bounds.
288      */
convertBrIdToString(int id)289     public static String convertBrIdToString(int id) {
290         return sMapper.convertBrIdToString(id);
291     }
292 
bindToAddedViews(DataBindingComponent component, ViewGroup parent, int startChildren, int layoutId)293     private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
294             ViewGroup parent, int startChildren, int layoutId) {
295         final int endChildren = parent.getChildCount();
296         final int childrenAdded = endChildren - startChildren;
297         if (childrenAdded == 1) {
298             final View childView = parent.getChildAt(endChildren - 1);
299             return bind(component, childView, layoutId);
300         } else {
301             final View[] children = new View[childrenAdded];
302             for (int i = 0; i < childrenAdded; i++) {
303                 children[i] = parent.getChildAt(i + startChildren);
304             }
305             return bind(component, children, layoutId);
306         }
307     }
308 }
309