• 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             final int endChildren = parent.getChildCount();
119             final int childrenAdded = endChildren - startChildren;
120             if (childrenAdded == 1) {
121                 final View childView = parent.getChildAt(endChildren - 1);
122                 return bind(bindingComponent, childView, layoutId);
123             } else {
124                 final View[] children = new View[childrenAdded];
125                 for (int i = 0; i < childrenAdded; i++) {
126                     children[i] = parent.getChildAt(i + startChildren);
127                 }
128                 return bind(bindingComponent, children, layoutId);
129             }
130         } else {
131             return bind(bindingComponent, view, layoutId);
132         }
133     }
134 
135     /**
136      * Returns the binding for the given layout root or creates a binding if one
137      * does not exist. This uses the DataBindingComponent set in
138      * {@link #setDefaultComponent(DataBindingComponent)}.
139      * <p>
140      * Prefer using the generated Binding's <code>bind</code> method to ensure type-safe inflation
141      * when it is known that <code>root</code> has not yet been bound.
142      *
143      * @param root The root View of the inflated binding layout.
144      * @return A ViewDataBinding for the given root View. If one already exists, the
145      * existing one will be returned.
146      * @throws IllegalArgumentException when root is not from an inflated binding layout.
147      * @see #getBinding(View)
148      */
149     @SuppressWarnings("unchecked")
bind(View root)150     public static <T extends ViewDataBinding> T bind(View root) {
151         return bind(root, sDefaultComponent);
152     }
153 
154     /**
155      * Returns the binding for the given layout root or creates a binding if one
156      * does not exist.
157      * <p>
158      * Prefer using the generated Binding's <code>bind</code> method to ensure type-safe inflation
159      * when it is known that <code>root</code> has not yet been bound.
160      *
161      * @param root The root View of the inflated binding layout.
162      * @param bindingComponent The DataBindingComponent to use in data binding.
163      * @return A ViewDataBinding for the given root View. If one already exists, the
164      * existing one will be returned.
165      * @throws IllegalArgumentException when root is not from an inflated binding layout.
166      * @see #getBinding(View)
167      */
168     @SuppressWarnings("unchecked")
bind(View root, DataBindingComponent bindingComponent)169     public static <T extends ViewDataBinding> T bind(View root,
170             DataBindingComponent bindingComponent) {
171         T binding = getBinding(root);
172         if (binding != null) {
173             return binding;
174         }
175         Object tagObj = root.getTag();
176         if (!(tagObj instanceof String)) {
177             throw new IllegalArgumentException("View is not a binding layout");
178         } else {
179             String tag = (String) tagObj;
180             int layoutId = sMapper.getLayoutId(tag);
181             if (layoutId == 0) {
182                 throw new IllegalArgumentException("View is not a binding layout");
183             }
184             return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
185         }
186     }
187 
188     @SuppressWarnings("unchecked")
bind(DataBindingComponent bindingComponent, View[] roots, int layoutId)189     static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
190             int layoutId) {
191         return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
192     }
193 
bind(DataBindingComponent bindingComponent, View root, int layoutId)194     static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
195             int layoutId) {
196         return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
197     }
198 
199     /**
200      * Retrieves the binding responsible for the given View. If <code>view</code> is not a
201      * binding layout root, its parents will be searched for the binding. If there is no binding,
202      * <code>null</code> will be returned.
203      * <p>
204      * This differs from {@link #getBinding(View)} in that findBinding takes any view in the
205      * layout and searches for the binding associated with the root. <code>getBinding</code>
206      * takes only the root view.
207      *
208      * @param view A <code>View</code> in the bound layout.
209      * @return The ViewDataBinding associated with the given view or <code>null</code> if
210      * view is not part of a bound layout.
211      */
findBinding(View view)212     public static <T extends ViewDataBinding> T findBinding(View view) {
213         while (view != null) {
214             ViewDataBinding binding = ViewDataBinding.getBinding(view);
215             if (binding != null) {
216                 return (T) binding;
217             }
218             Object tag = view.getTag();
219             if (tag instanceof String) {
220                 String tagString = (String) tag;
221                 if (tagString.startsWith("layout") && tagString.endsWith("_0")) {
222                     final char nextChar = tagString.charAt(6);
223                     final int slashIndex = tagString.indexOf('/', 7);
224                     boolean isUnboundRoot = false;
225                     if (nextChar == '/') {
226                         // only one slash should exist
227                         isUnboundRoot = slashIndex == -1;
228                     } else if (nextChar == '-' && slashIndex != -1) {
229                         int nextSlashIndex = tagString.indexOf('/', slashIndex + 1);
230                         // only one slash should exist
231                         isUnboundRoot = nextSlashIndex == -1;
232                     }
233                     if (isUnboundRoot) {
234                         // An inflated, but unbound layout
235                         return null;
236                     }
237                 }
238             }
239             ViewParent viewParent = view.getParent();
240             if (viewParent instanceof View) {
241                 view = (View) viewParent;
242             } else {
243                 view = null;
244             }
245         }
246         return null;
247     }
248 
249     /**
250      * Retrieves the binding responsible for the given View layout root. If there is no binding,
251      * <code>null</code> will be returned. This uses the DataBindingComponent set in
252      * {@link #setDefaultComponent(DataBindingComponent)}.
253      *
254      * @param view The root <code>View</code> in the layout with binding.
255      * @return The ViewDataBinding associated with the given view or <code>null</code> if
256      * either the view is not a root View for a layout or view hasn't been bound.
257      */
getBinding(View view)258     public static <T extends ViewDataBinding> T getBinding(View view) {
259         return (T) ViewDataBinding.getBinding(view);
260     }
261 
262     /**
263      * Set the Activity's content view to the given layout and return the associated binding.
264      * The given layout resource must not be a merge layout.
265      *
266      * @param activity The Activity whose content View should change.
267      * @param layoutId The resource ID of the layout to be inflated, bound, and set as the
268      *                 Activity's content.
269      * @return The binding associated with the inflated content view.
270      */
setContentView(Activity activity, int layoutId)271     public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId) {
272         return setContentView(activity, layoutId, sDefaultComponent);
273     }
274 
275     /**
276      * Set the Activity's content view to the given layout and return the associated binding.
277      * The given layout resource must not be a merge layout.
278      *
279      * @param bindingComponent The DataBindingComponent to use in data binding.
280      * @param activity The Activity whose content View should change.
281      * @param layoutId The resource ID of the layout to be inflated, bound, and set as the
282      *                 Activity's content.
283      * @return The binding associated with the inflated content view.
284      */
setContentView(Activity activity, int layoutId, DataBindingComponent bindingComponent)285     public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId,
286             DataBindingComponent bindingComponent) {
287         // Force the content view to exist if it didn't already.
288         View decorView = activity.getWindow().getDecorView();
289         ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
290         T binding = inflate(activity.getLayoutInflater(), layoutId, contentView, false,
291                 bindingComponent);
292         activity.setContentView(binding.getRoot(), binding.getRoot().getLayoutParams());
293         return binding;
294     }
295 
296     /**
297      * Converts the given BR id to its string representation which might be useful for logging
298      * purposes.
299      *
300      * @param id The integer id, which should be a field from BR class.
301      * @return The name if the BR id or null if id is out of bounds.
302      */
convertBrIdToString(int id)303     public static String convertBrIdToString(int id) {
304         return sMapper.convertBrIdToString(id);
305     }
306 }
307