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 com.android.setupwizardlib; 18 19import android.annotation.TargetApi; 20import android.content.Context; 21import android.content.res.TypedArray; 22import android.os.Build.VERSION_CODES; 23import android.support.annotation.Keep; 24import android.support.annotation.LayoutRes; 25import android.support.annotation.StyleRes; 26import android.util.AttributeSet; 27import android.view.LayoutInflater; 28import android.view.View; 29import android.view.ViewGroup; 30import android.view.ViewTreeObserver; 31import android.widget.FrameLayout; 32 33import com.android.setupwizardlib.template.Mixin; 34import com.android.setupwizardlib.util.FallbackThemeWrapper; 35 36import java.util.HashMap; 37import java.util.Map; 38 39/** 40 * A generic template class that inflates a template, provided in the constructor or in 41 * {@code android:layout} through XML, and adds its children to a "container" in the template. When 42 * inflating this layout from XML, the {@code android:layout} and {@code suwContainer} attributes 43 * are required. 44 */ 45public class TemplateLayout extends FrameLayout { 46 47 /** 48 * The container of the actual content. This will be a view in the template, which child views 49 * will be added to when {@link #addView(View)} is called. 50 */ 51 private ViewGroup mContainer; 52 53 private Map<Class<? extends Mixin>, Mixin> mMixins = new HashMap<>(); 54 55 public TemplateLayout(Context context, int template, int containerId) { 56 super(context); 57 init(template, containerId, null, R.attr.suwLayoutTheme); 58 } 59 60 public TemplateLayout(Context context, AttributeSet attrs) { 61 super(context, attrs); 62 init(0, 0, attrs, R.attr.suwLayoutTheme); 63 } 64 65 @TargetApi(VERSION_CODES.HONEYCOMB) 66 public TemplateLayout(Context context, AttributeSet attrs, int defStyleAttr) { 67 super(context, attrs, defStyleAttr); 68 init(0, 0, attrs, defStyleAttr); 69 } 70 71 // All the constructors delegate to this init method. The 3-argument constructor is not 72 // available in LinearLayout before v11, so call super with the exact same arguments. 73 private void init(int template, int containerId, AttributeSet attrs, int defStyleAttr) { 74 final TypedArray a = getContext().obtainStyledAttributes(attrs, 75 R.styleable.SuwTemplateLayout, defStyleAttr, 0); 76 if (template == 0) { 77 template = a.getResourceId(R.styleable.SuwTemplateLayout_android_layout, 0); 78 } 79 if (containerId == 0) { 80 containerId = a.getResourceId(R.styleable.SuwTemplateLayout_suwContainer, 0); 81 } 82 inflateTemplate(template, containerId); 83 84 a.recycle(); 85 } 86 87 /** 88 * Registers a mixin with a given class. This method should be called in the constructor. 89 * 90 * @param cls The class to register the mixin. In most cases, {@code cls} is the same as 91 * {@code mixin.getClass()}, but {@code cls} can also be a super class of that. In 92 * the latter case the the mixin must be retrieved using {@code cls} in 93 * {@link #getMixin(Class)}, not the subclass. 94 * @param mixin The mixin to be registered. 95 * @param <M> The class of the mixin to register. This is the same as {@code cls} 96 */ 97 protected <M extends Mixin> void registerMixin(Class<M> cls, M mixin) { 98 mMixins.put(cls, mixin); 99 } 100 101 /** 102 * Same as {@link android.view.View#findViewById(int)}, but may include views that are managed 103 * by this view but not currently added to the view hierarchy. e.g. recycler view or list view 104 * headers that are not currently shown. 105 */ 106 public View findManagedViewById(int id) { 107 return findViewById(id); 108 } 109 110 /** 111 * Get a {@link Mixin} from this template registered earlier in 112 * {@link #registerMixin(Class, Mixin)}. 113 * 114 * @param cls The class marker of Mixin being requested. The actual Mixin returned may be a 115 * subclass of this marker. Note that this must be the same class as registered in 116 * {@link #registerMixin(Class, Mixin)}, which is not necessarily the 117 * same as the concrete class of the instance returned by this method. 118 * @param <M> The type of the class marker. 119 * @return The mixin marked by {@code cls}, or null if the template does not have a matching 120 * mixin. 121 */ 122 @SuppressWarnings("unchecked") 123 public <M extends Mixin> M getMixin(Class<M> cls) { 124 return (M) mMixins.get(cls); 125 } 126 127 @Override 128 public void addView(View child, int index, ViewGroup.LayoutParams params) { 129 mContainer.addView(child, index, params); 130 } 131 132 private void addViewInternal(View child) { 133 super.addView(child, -1, generateDefaultLayoutParams()); 134 } 135 136 private void inflateTemplate(int templateResource, int containerId) { 137 final LayoutInflater inflater = LayoutInflater.from(getContext()); 138 final View templateRoot = onInflateTemplate(inflater, templateResource); 139 addViewInternal(templateRoot); 140 141 mContainer = findContainer(containerId); 142 if (mContainer == null) { 143 throw new IllegalArgumentException("Container cannot be null in TemplateLayout"); 144 } 145 onTemplateInflated(); 146 } 147 148 /** 149 * This method inflates the template. Subclasses can override this method to customize the 150 * template inflation, or change to a different default template. The root of the inflated 151 * layout should be returned, and not added to the view hierarchy. 152 * 153 * @param inflater A LayoutInflater to inflate the template. 154 * @param template The resource ID of the template to be inflated, or 0 if no template is 155 * specified. 156 * @return Root of the inflated layout. 157 */ 158 protected View onInflateTemplate(LayoutInflater inflater, @LayoutRes int template) { 159 return inflateTemplate(inflater, 0, template); 160 } 161 162 /** 163 * Inflate the template using the given inflater and theme. The fallback theme will be applied 164 * to the theme without overriding the values already defined in the theme, but simply providing 165 * default values for values which have not been defined. This allows templates to add 166 * additional required theme attributes without breaking existing clients. 167 * 168 * <p>In general, clients should still set the activity theme to the corresponding theme in 169 * setup wizard lib, so that the content area gets the correct styles as well. 170 * 171 * @param inflater A LayoutInflater to inflate the template. 172 * @param fallbackTheme A fallback theme to apply to the template. If the values defined in the 173 * fallback theme is already defined in the original theme, the value in 174 * the original theme takes precedence. 175 * @param template The layout template to be inflated. 176 * @return Root of the inflated layout. 177 * 178 * @see FallbackThemeWrapper 179 */ 180 protected final View inflateTemplate(LayoutInflater inflater, @StyleRes int fallbackTheme, 181 @LayoutRes int template) { 182 if (template == 0) { 183 throw new IllegalArgumentException("android:layout not specified for TemplateLayout"); 184 } 185 if (fallbackTheme != 0) { 186 inflater = LayoutInflater.from( 187 new FallbackThemeWrapper(inflater.getContext(), fallbackTheme)); 188 } 189 return inflater.inflate(template, this, false); 190 } 191 192 protected ViewGroup findContainer(int containerId) { 193 if (containerId == 0) { 194 // Maintain compatibility with the deprecated way of specifying container ID. 195 containerId = getContainerId(); 196 } 197 return (ViewGroup) findViewById(containerId); 198 } 199 200 /** 201 * This is called after the template has been inflated and added to the view hierarchy. 202 * Subclasses can implement this method to modify the template as necessary, such as caching 203 * views retrieved from findViewById, or other view operations that need to be done in code. 204 * You can think of this as {@link View#onFinishInflate()} but for inflation of the 205 * template instead of for child views. 206 */ 207 protected void onTemplateInflated() { 208 } 209 210 /** 211 * @return ID of the default container for this layout. This will be used to find the container 212 * ViewGroup, which all children views of this layout will be placed in. 213 * @deprecated Override {@link #findContainer(int)} instead. 214 */ 215 @Deprecated 216 protected int getContainerId() { 217 return 0; 218 } 219 220 /* Animator support */ 221 222 private float mXFraction; 223 private ViewTreeObserver.OnPreDrawListener mPreDrawListener; 224 225 /** 226 * Set the X translation as a fraction of the width of this view. Make sure this method is not 227 * stripped out by proguard when using this with {@link android.animation.ObjectAnimator}. You 228 * may need to add 229 * <code> 230 * -keep @android.support.annotation.Keep class * 231 * </code> 232 * to your proguard configuration if you are seeing mysterious {@link NoSuchMethodError} at 233 * runtime. 234 */ 235 @Keep 236 @TargetApi(VERSION_CODES.HONEYCOMB) 237 public void setXFraction(float fraction) { 238 mXFraction = fraction; 239 final int width = getWidth(); 240 if (width != 0) { 241 setTranslationX(width * fraction); 242 } else { 243 // If we haven't done a layout pass yet, wait for one and then set the fraction before 244 // the draw occurs using an OnPreDrawListener. Don't call translationX until we know 245 // getWidth() has a reliable, non-zero value or else we will see the fragment flicker on 246 // screen. 247 if (mPreDrawListener == null) { 248 mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() { 249 @Override 250 public boolean onPreDraw() { 251 getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener); 252 setXFraction(mXFraction); 253 return true; 254 } 255 }; 256 getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); 257 } 258 } 259 } 260 261 /** 262 * Return the X translation as a fraction of the width, as previously set in 263 * {@link #setXFraction(float)}. 264 * 265 * @see #setXFraction(float) 266 */ 267 @Keep 268 @TargetApi(VERSION_CODES.HONEYCOMB) 269 public float getXFraction() { 270 return mXFraction; 271 } 272} 273