[go: nahoru, domu]

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
17package android.databinding;
18
19import android.app.Activity;
20import android.support.annotation.Nullable;
21import android.view.InflateException;
22import android.view.LayoutInflater;
23import android.view.View;
24import android.view.ViewGroup;
25import android.view.ViewParent;
26
27/**
28 * Utility class to create {@link ViewDataBinding} from layouts.
29 */
30public class DataBindingUtil {
31    private static DataBinderMapper sMapper = new DataBinderMapper();
32    private static DataBindingComponent sDefaultComponent = null;
33
34    /**
35     * Prevent DataBindingUtil from being instantiated.
36     */
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     */
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     */
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     */
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     */
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")
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")
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")
178    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
179            int layoutId) {
180        return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
181    }
182
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     */
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     */
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     */
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     */
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     */
289    public static String convertBrIdToString(int id) {
290        return sMapper.convertBrIdToString(id);
291    }
292
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