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