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 com.android.setupwizardlib; 18 19 import android.annotation.TargetApi; 20 import android.content.Context; 21 import android.content.res.TypedArray; 22 import android.os.Build.VERSION_CODES; 23 import android.util.AttributeSet; 24 import android.view.LayoutInflater; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.view.ViewTreeObserver; 28 import android.widget.FrameLayout; 29 30 import 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 */ 38 public 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 TemplateLayout(Context context, int template, int containerId)46 public TemplateLayout(Context context, int template, int containerId) { 47 super(context); 48 init(template, containerId, null, R.attr.suwLayoutTheme); 49 } 50 TemplateLayout(Context context, AttributeSet attrs)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) TemplateLayout(Context context, AttributeSet attrs, int defStyleAttr)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. init(int template, int containerId, AttributeSet attrs, int defStyleAttr)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 addView(View child, int index, ViewGroup.LayoutParams params)79 public void addView(View child, int index, ViewGroup.LayoutParams params) { 80 mContainer.addView(child, index, params); 81 } 82 addViewInternal(View child)83 private void addViewInternal(View child) { 84 super.addView(child, -1, generateDefaultLayoutParams()); 85 } 86 inflateTemplate(int templateResource, int containerId)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 */ onInflateTemplate(LayoutInflater inflater, int template)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 findContainer(int containerId)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 */ onTemplateInflated()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 getContainerId()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) setXFraction(float fraction)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) getXFraction()193 public float getXFraction() { 194 return mXFraction; 195 } 196 } 197