1 /* 2 * Copyright (C) 2012 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.common.widget; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.util.AttributeSet; 22 import android.view.View; 23 import android.view.ViewGroup; 24 25 import com.android.contacts.common.R; 26 27 /** 28 * Layout that calculates its height based on its width, or vice versa (depending on the set 29 * {@link #setDirection(Direction)}. The factor is specified in {@link #setRatio(float)}. 30 * <p>For {@link Direction#heightToWidth}: width := height * factor</p> 31 * <p>For {@link Direction#widthToHeight}: height := width * factor</p> 32 * <p>Only one child is allowed; if more are required, another ViewGroup can be used as the direct 33 * child of this layout.</p> 34 */ 35 public class ProportionalLayout extends ViewGroup { 36 /** Specifies whether the width should be calculated based on the height or vice-versa */ 37 public enum Direction { 38 widthToHeight("widthToHeight"), 39 heightToWidth("heightToWidth"); 40 41 public final String XmlName; 42 Direction(String xmlName)43 private Direction(String xmlName) { 44 XmlName = xmlName; 45 } 46 47 /** 48 * Parses the given direction string and returns the Direction instance. This 49 * should be used when inflating from xml 50 */ parse(String value)51 public static Direction parse(String value) { 52 if (widthToHeight.XmlName.equals(value)) { 53 return Direction.widthToHeight; 54 } else if (heightToWidth.XmlName.equals(value)) { 55 return Direction.heightToWidth; 56 } else { 57 throw new IllegalStateException("direction must be either " + 58 widthToHeight.XmlName + " or " + heightToWidth.XmlName); 59 } 60 } 61 } 62 63 private Direction mDirection; 64 private float mRatio; 65 ProportionalLayout(Context context)66 public ProportionalLayout(Context context) { 67 super(context); 68 } 69 ProportionalLayout(Context context, AttributeSet attrs)70 public ProportionalLayout(Context context, AttributeSet attrs) { 71 super(context, attrs); 72 initFromAttributes(context, attrs); 73 } 74 ProportionalLayout(Context context, AttributeSet attrs, int defStyle)75 public ProportionalLayout(Context context, AttributeSet attrs, int defStyle) { 76 super(context, attrs, defStyle); 77 initFromAttributes(context, attrs); 78 } 79 initFromAttributes(Context context, AttributeSet attrs)80 private void initFromAttributes(Context context, AttributeSet attrs) { 81 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProportionalLayout); 82 83 mDirection = Direction.parse(a.getString(R.styleable.ProportionalLayout_direction)); 84 mRatio = a.getFloat(R.styleable.ProportionalLayout_ratio, 1.0f); 85 86 a.recycle(); 87 } 88 89 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)90 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 91 if (getChildCount() != 1) { 92 throw new IllegalStateException("ProportionalLayout requires exactly one child"); 93 } 94 95 final View child = getChildAt(0); 96 97 // Do a first pass to get the optimal size 98 measureChild(child, widthMeasureSpec, heightMeasureSpec); 99 final int childWidth = child.getMeasuredWidth(); 100 final int childHeight = child.getMeasuredHeight(); 101 102 final int width; 103 final int height; 104 if (mDirection == Direction.heightToWidth) { 105 width = Math.round(childHeight * mRatio); 106 height = childHeight; 107 } else { 108 width = childWidth; 109 height = Math.round(childWidth * mRatio); 110 } 111 112 // Do a second pass so that all children are informed of the new size 113 measureChild(child, 114 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 115 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 116 117 setMeasuredDimension( 118 resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec)); 119 } 120 121 @Override onLayout(boolean changed, int left, int top, int right, int bottom)122 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 123 if (getChildCount() != 1) { 124 throw new IllegalStateException("ProportionalLayout requires exactly one child"); 125 } 126 127 final View child = getChildAt(0); 128 child.layout(0, 0, right-left, bottom-top); 129 } 130 getDirection()131 public Direction getDirection() { 132 return mDirection; 133 } 134 setDirection(Direction direction)135 public void setDirection(Direction direction) { 136 mDirection = direction; 137 } 138 getRatio()139 public float getRatio() { 140 return mRatio; 141 } 142 setRatio(float ratio)143 public void setRatio(float ratio) { 144 mRatio = ratio; 145 } 146 } 147