1 /* 2 * Copyright (C) 2008 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.phone; 18 19 import android.content.Context; 20 import android.util.AttributeSet; 21 import android.util.Log; 22 import android.view.View.MeasureSpec; 23 import android.view.View; 24 import android.view.ViewGroup; 25 26 /** 27 * Create a 4x3 grid of dial buttons. 28 * 29 * It was easier and more efficient to do it this way than use 30 * standard layouts. It's perfectly fine (and actually encouraged) to 31 * use custom layouts rather than piling up standard layouts. 32 * 33 * The horizontal and vertical spacings between buttons are controlled 34 * by the amount of padding (attributes on the ButtonGridLayout element): 35 * - horizontal = left + right padding and 36 * - vertical = top + bottom padding. 37 * 38 * This class assumes that all the buttons have the same size. 39 * The buttons will be bottom aligned in their view on layout. 40 * 41 * Invocation: onMeasure is called first by the framework to know our 42 * size. Then onLayout is invoked to layout the buttons. 43 */ 44 // TODO: Blindly layout the buttons w/o checking if we overrun the 45 // bottom-right corner. 46 47 public class ButtonGridLayout extends ViewGroup { 48 static private final String TAG = "ButtonGridLayout"; 49 static private final int COLUMNS = 3; 50 static private final int ROWS = 4; 51 static private final int NUM_CHILDREN = ROWS * COLUMNS; 52 53 private View[] mButtons = new View[NUM_CHILDREN]; 54 55 // This what the fields represent (height is similar): 56 // PL: mPaddingLeft 57 // BW: mButtonWidth 58 // PR: mPaddingRight 59 // 60 // mWidthInc 61 // <--------------------> 62 // PL BW PR 63 // <----><--------><----> 64 // -------- 65 // | | 66 // | button | 67 // | | 68 // -------- 69 // 70 // We assume mPaddingLeft == mPaddingRight == 1/2 padding between 71 // buttons. 72 // 73 // mWidth == COLUMNS x mWidthInc 74 75 // Width and height of a button 76 private int mButtonWidth; 77 private int mButtonHeight; 78 79 // Width and height of a button + padding. 80 private int mWidthInc; 81 private int mHeightInc; 82 83 // Height of the dialpad. Used to align it at the bottom of the 84 // view. 85 private int mWidth; 86 private int mHeight; 87 88 89 public ButtonGridLayout(Context context) { 90 super(context); 91 } 92 93 public ButtonGridLayout(Context context, AttributeSet attrs) { 94 super(context, attrs); 95 } 96 97 public ButtonGridLayout(Context context, AttributeSet attrs, int defStyle) { 98 super(context, attrs, defStyle); 99 } 100 101 /** 102 * Cache the buttons in a member array for faster access. Compute 103 * the measurements for the width/height of buttons. The inflate 104 * sequence is called right after the constructor and before the 105 * measure/layout phase. 106 */ 107 @Override 108 protected void onFinishInflate () { 109 super.onFinishInflate(); 110 final View[] buttons = mButtons; 111 for (int i = 0; i < NUM_CHILDREN; i++) { 112 buttons[i] = getChildAt(i); 113 // Measure the button to get initialized. 114 buttons[i].measure(MeasureSpec.UNSPECIFIED , MeasureSpec.UNSPECIFIED); 115 } 116 117 // Cache the measurements. 118 final View child = buttons[0]; 119 mButtonWidth = child.getMeasuredWidth(); 120 mButtonHeight = child.getMeasuredHeight(); 121 mWidthInc = mButtonWidth + mPaddingLeft + mPaddingRight; 122 mHeightInc = mButtonHeight + mPaddingTop + mPaddingBottom; 123 mWidth = COLUMNS * mWidthInc; 124 mHeight = ROWS * mHeightInc; 125 } 126 127 /** 128 * Set the background of all the children. Typically a selector to 129 * change the background based on some combination of the button's 130 * attributes (e.g pressed, enabled...) 131 * @param resid Is a resource id to be used for each button's background. 132 */ 133 public void setChildrenBackgroundResource(int resid) { 134 final View[] buttons = mButtons; 135 for (int i = 0; i < NUM_CHILDREN; i++) { 136 buttons[i].setBackgroundResource(resid); 137 } 138 } 139 140 @Override 141 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 142 final View[] buttons = mButtons; 143 final int paddingLeft = mPaddingLeft; 144 final int buttonWidth = mButtonWidth; 145 final int buttonHeight = mButtonHeight; 146 final int widthInc = mWidthInc; 147 final int heightInc = mHeightInc; 148 149 int i = 0; 150 // The last row is bottom aligned. 151 int y = (bottom - top) - mHeight + mPaddingTop; 152 for (int row = 0; row < ROWS; row++) { 153 int x = paddingLeft; 154 for (int col = 0; col < COLUMNS; col++) { 155 buttons[i].layout(x, y, x + buttonWidth, y + buttonHeight); 156 x += widthInc; 157 i++; 158 } 159 y += heightInc; 160 } 161 } 162 163 /** 164 * This method is called twice in practice. The first time both 165 * with and height are constraint by AT_MOST. The second time, the 166 * width is still AT_MOST and the height is EXACTLY. Either way 167 * the full width/height should be in mWidth and mHeight and we 168 * use 'resolveSize' to do the right thing. 169 */ 170 @Override 171 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 172 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 173 final int width = resolveSize(mWidth, widthMeasureSpec); 174 final int height = resolveSize(mHeight, heightMeasureSpec); 175 setMeasuredDimension(width, height); 176 } 177 } 178