1 /* 2 * Copyright (C) 2010 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.contacts.widget; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.graphics.Rect; 22 import android.util.AttributeSet; 23 import android.view.Gravity; 24 import android.view.View; 25 import android.view.ViewGroup; 26 import android.widget.LinearLayout; 27 28 import com.android.contacts.R; 29 30 /** 31 * Layout similar to LinearLayout that allows a child to specify examples of 32 * desired size depending on the parent size. For example if the widget wants to 33 * be 100dip when parent is 200dip and 110dip when parent is 400dip, the layout 34 * will ensure these requirements and interpolate for other parent sizes. 35 * You can also specify minWidth for each child. You can have at most one 36 * child with layout_width="match_parent" - it will take the entire remaining 37 * space. 38 */ 39 public class InterpolatingLayout extends ViewGroup { 40 41 private Rect mInRect = new Rect(); 42 private Rect mOutRect = new Rect(); 43 InterpolatingLayout(Context context)44 public InterpolatingLayout(Context context) { 45 super(context); 46 } 47 InterpolatingLayout(Context context, AttributeSet attrs)48 public InterpolatingLayout(Context context, AttributeSet attrs) { 49 super(context, attrs); 50 } 51 InterpolatingLayout(Context context, AttributeSet attrs, int defStyle)52 public InterpolatingLayout(Context context, AttributeSet attrs, int defStyle) { 53 super(context, attrs, defStyle); 54 } 55 56 public final static class LayoutParams extends LinearLayout.LayoutParams { 57 58 public int narrowParentWidth; 59 public int narrowWidth; 60 public int narrowMarginLeft; 61 public int narrowPaddingLeft; 62 public int narrowMarginRight; 63 public int narrowPaddingRight; 64 public int wideParentWidth; 65 public int wideWidth; 66 public int wideMarginLeft; 67 public int widePaddingLeft; 68 public int wideMarginRight; 69 public int widePaddingRight; 70 private float widthMultiplier; 71 private int widthConstant; 72 private float leftMarginMultiplier; 73 private int leftMarginConstant; 74 private float leftPaddingMultiplier; 75 private int leftPaddingConstant; 76 private float rightMarginMultiplier; 77 private int rightMarginConstant; 78 private float rightPaddingMultiplier; 79 private int rightPaddingConstant; 80 LayoutParams(Context c, AttributeSet attrs)81 public LayoutParams(Context c, AttributeSet attrs) { 82 super(c, attrs); 83 TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.InterpolatingLayout_Layout); 84 85 narrowParentWidth = a.getDimensionPixelSize( 86 R.styleable.InterpolatingLayout_Layout_layout_narrowParentWidth, -1); 87 narrowWidth = a.getDimensionPixelSize( 88 R.styleable.InterpolatingLayout_Layout_layout_narrowWidth, -1); 89 narrowMarginLeft = a.getDimensionPixelSize( 90 R.styleable.InterpolatingLayout_Layout_layout_narrowMarginLeft, -1); 91 narrowPaddingLeft = a.getDimensionPixelSize( 92 R.styleable.InterpolatingLayout_Layout_layout_narrowPaddingLeft, -1); 93 narrowMarginRight = a.getDimensionPixelSize( 94 R.styleable.InterpolatingLayout_Layout_layout_narrowMarginRight, -1); 95 narrowPaddingRight = a.getDimensionPixelSize( 96 R.styleable.InterpolatingLayout_Layout_layout_narrowPaddingRight, -1); 97 wideParentWidth = a.getDimensionPixelSize( 98 R.styleable.InterpolatingLayout_Layout_layout_wideParentWidth, -1); 99 wideWidth = a.getDimensionPixelSize( 100 R.styleable.InterpolatingLayout_Layout_layout_wideWidth, -1); 101 wideMarginLeft = a.getDimensionPixelSize( 102 R.styleable.InterpolatingLayout_Layout_layout_wideMarginLeft, -1); 103 widePaddingLeft = a.getDimensionPixelSize( 104 R.styleable.InterpolatingLayout_Layout_layout_widePaddingLeft, -1); 105 wideMarginRight = a.getDimensionPixelSize( 106 R.styleable.InterpolatingLayout_Layout_layout_wideMarginRight, -1); 107 widePaddingRight = a.getDimensionPixelSize( 108 R.styleable.InterpolatingLayout_Layout_layout_widePaddingRight, -1); 109 110 a.recycle(); 111 112 if (narrowWidth != -1) { 113 widthMultiplier = (float) (wideWidth - narrowWidth) 114 / (wideParentWidth - narrowParentWidth); 115 widthConstant = (int) (narrowWidth - narrowParentWidth * widthMultiplier); 116 } 117 118 if (narrowMarginLeft != -1) { 119 leftMarginMultiplier = (float) (wideMarginLeft - narrowMarginLeft) 120 / (wideParentWidth - narrowParentWidth); 121 leftMarginConstant = (int) (narrowMarginLeft - narrowParentWidth 122 * leftMarginMultiplier); 123 } 124 125 if (narrowPaddingLeft != -1) { 126 leftPaddingMultiplier = (float) (widePaddingLeft - narrowPaddingLeft) 127 / (wideParentWidth - narrowParentWidth); 128 leftPaddingConstant = (int) (narrowPaddingLeft - narrowParentWidth 129 * leftPaddingMultiplier); 130 } 131 132 if (narrowMarginRight != -1) { 133 rightMarginMultiplier = (float) (wideMarginRight - narrowMarginRight) 134 / (wideParentWidth - narrowParentWidth); 135 rightMarginConstant = (int) (narrowMarginRight - narrowParentWidth 136 * rightMarginMultiplier); 137 } 138 139 if (narrowPaddingRight != -1) { 140 rightPaddingMultiplier = (float) (widePaddingRight - narrowPaddingRight) 141 / (wideParentWidth - narrowParentWidth); 142 rightPaddingConstant = (int) (narrowPaddingRight - narrowParentWidth 143 * rightPaddingMultiplier); 144 } 145 } 146 LayoutParams(int width, int height)147 public LayoutParams(int width, int height) { 148 super(width, height); 149 } 150 LayoutParams(MarginLayoutParams source)151 public LayoutParams(MarginLayoutParams source) { 152 super(source); 153 } 154 resolveWidth(int parentSize)155 public int resolveWidth(int parentSize) { 156 if (narrowWidth == -1) { 157 return width; 158 } else { 159 int w = (int) (parentSize * widthMultiplier) + widthConstant; 160 return w <= 0 ? WRAP_CONTENT : w; 161 } 162 } 163 resolveLeftMargin(int parentSize)164 public int resolveLeftMargin(int parentSize) { 165 if (narrowMarginLeft == -1) { 166 return leftMargin; 167 } else { 168 int w = (int) (parentSize * leftMarginMultiplier) + leftMarginConstant; 169 return w < 0 ? 0 : w; 170 } 171 } 172 resolveLeftPadding(int parentSize)173 public int resolveLeftPadding(int parentSize) { 174 int w = (int) (parentSize * leftPaddingMultiplier) + leftPaddingConstant; 175 return w < 0 ? 0 : w; 176 } 177 resolveRightMargin(int parentSize)178 public int resolveRightMargin(int parentSize) { 179 if (narrowMarginRight == -1) { 180 return rightMargin; 181 } else { 182 int w = (int) (parentSize * rightMarginMultiplier) + rightMarginConstant; 183 return w < 0 ? 0 : w; 184 } 185 } 186 resolveRightPadding(int parentSize)187 public int resolveRightPadding(int parentSize) { 188 int w = (int) (parentSize * rightPaddingMultiplier) + rightPaddingConstant; 189 return w < 0 ? 0 : w; 190 } 191 } 192 193 @Override generateLayoutParams(AttributeSet attrs)194 public LayoutParams generateLayoutParams(AttributeSet attrs) { 195 return new LayoutParams(getContext(), attrs); 196 } 197 198 @Override generateDefaultLayoutParams()199 protected LayoutParams generateDefaultLayoutParams() { 200 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 201 } 202 203 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)204 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 205 int parentWidth = MeasureSpec.getSize(widthMeasureSpec); 206 int parentHeight = MeasureSpec.getSize(heightMeasureSpec); 207 208 int width = 0; 209 int height = 0; 210 211 View fillChild = null; 212 int count = getChildCount(); 213 for (int i = 0; i < count; i++) { 214 View child = getChildAt(i); 215 if (child.getVisibility() == View.GONE) { 216 continue; 217 } 218 219 LayoutParams params = (LayoutParams) child.getLayoutParams(); 220 if (params.width == LayoutParams.MATCH_PARENT) { 221 if (fillChild != null) { 222 throw new RuntimeException( 223 "Interpolating layout allows at most one child" 224 + " with layout_width='match_parent'"); 225 } 226 fillChild = child; 227 } else { 228 int childWidth = params.resolveWidth(parentWidth); 229 int childWidthMeasureSpec; 230 switch (childWidth) { 231 case LayoutParams.WRAP_CONTENT: 232 childWidthMeasureSpec = MeasureSpec.UNSPECIFIED; 233 break; 234 default: 235 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( 236 childWidth, MeasureSpec.EXACTLY); 237 break; 238 } 239 240 int childHeightMeasureSpec; 241 switch (params.height) { 242 case LayoutParams.WRAP_CONTENT: 243 childHeightMeasureSpec = MeasureSpec.UNSPECIFIED; 244 break; 245 case LayoutParams.MATCH_PARENT: 246 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( 247 parentHeight - params.topMargin - params.bottomMargin, 248 MeasureSpec.EXACTLY); 249 break; 250 default: 251 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( 252 params.height, MeasureSpec.EXACTLY); 253 break; 254 } 255 256 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 257 width += child.getMeasuredWidth(); 258 height = Math.max(child.getMeasuredHeight(), height); 259 } 260 261 width += params.resolveLeftMargin(parentWidth) + params.resolveRightMargin(parentWidth); 262 } 263 264 if (fillChild != null) { 265 int remainder = parentWidth - width; 266 int childMeasureSpec = remainder > 0 267 ? MeasureSpec.makeMeasureSpec(remainder, MeasureSpec.EXACTLY) 268 : MeasureSpec.UNSPECIFIED; 269 fillChild.measure(childMeasureSpec, heightMeasureSpec); 270 width += fillChild.getMeasuredWidth(); 271 height = Math.max(fillChild.getMeasuredHeight(), height); 272 } 273 274 setMeasuredDimension( 275 resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec)); 276 } 277 278 @Override onLayout(boolean changed, int left, int top, int right, int bottom)279 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 280 int offset = 0; 281 int width = right - left; 282 int count = getChildCount(); 283 for (int i = 0; i < count; i++) { 284 View child = getChildAt(i); 285 286 if (child.getVisibility() == View.GONE) { 287 continue; 288 } 289 290 LayoutParams params = (LayoutParams) child.getLayoutParams(); 291 int gravity = params.gravity; 292 if (gravity == -1) { 293 gravity = Gravity.START | Gravity.TOP; 294 } 295 296 if (params.narrowPaddingLeft != -1 || params.narrowPaddingRight != -1) { 297 int leftPadding = params.narrowPaddingLeft == -1 ? child.getPaddingLeft() 298 : params.resolveLeftPadding(width); 299 int rightPadding = params.narrowPaddingRight == -1 ? child.getPaddingRight() 300 : params.resolveRightPadding(width); 301 child.setPadding( 302 leftPadding, child.getPaddingTop(), rightPadding, child.getPaddingBottom()); 303 } 304 305 int leftMargin = params.resolveLeftMargin(width); 306 int rightMargin = params.resolveRightMargin(width); 307 308 mInRect.set(offset + leftMargin, params.topMargin, 309 right - rightMargin, bottom - params.bottomMargin); 310 311 Gravity.apply(gravity, child.getMeasuredWidth(), child.getMeasuredHeight(), 312 mInRect, mOutRect); 313 child.layout(mOutRect.left, mOutRect.top, mOutRect.right, mOutRect.bottom); 314 315 offset = mOutRect.right + rightMargin; 316 } 317 } 318 } 319