Home | History | Annotate | Download | only in databinding
      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      */
     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             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")
    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")
    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")
    189     static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
    190             int layoutId) {
    191         return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
    192     }
    193 
    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      */
    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      */
    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      */
    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      */
    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      */
    303     public static String convertBrIdToString(int id) {
    304         return sMapper.convertBrIdToString(id);
    305     }
    306 }
    307