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