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