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