1/* 2 * Copyright (C) 2007 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 android.util; 18 19import com.android.frameworks.coretests.R; 20 21import android.view.View; 22import android.view.KeyEvent; 23import android.content.Context; 24import android.content.res.TypedArray; 25import android.graphics.Paint; 26import android.graphics.Canvas; 27import android.graphics.Rect; 28import android.graphics.Color; 29import android.util.AttributeSet; 30 31 32 33/** 34 * A view that has a known number of selectable rows, and maintains a notion of which 35 * row is selected. The rows take up the 36 * entire width of the view. The height of the view is divided evenly among 37 * the rows. 38 * 39 * Note: If the height of the view does not divide exactly to the number of rows, 40 * the last row's height is inflated with the remainder. For example, if the 41 * view height is 22 and there are two rows, the height of the first row is 42 * 10 and the second 22. 43 * 44 * Notice what this view does to be a good citizen w.r.t its internal selection: 45 * 1) calls {@link View#requestRectangleOnScreen} each time the selection changes due to 46 * internal navigation. 47 * 2) implements {@link View#getFocusedRect} by filling in the rectangle of the currently 48 * selected row 49 * 3) overrides {@link View#onFocusChanged} and sets selection appropriately according to 50 * the previously focused rectangle. 51 */ 52public class InternalSelectionView extends View { 53 54 private Paint mPainter = new Paint(); 55 private Paint mTextPaint = new Paint(); 56 private Rect mTempRect = new Rect(); 57 58 private int mNumRows = 5; 59 private int mSelectedRow = 0; 60 private final int mEstimatedPixelHeight = 10; 61 62 private Integer mDesiredHeight = null; 63 private String mLabel = null; 64 65 public InternalSelectionView(Context context, int numRows, String label) { 66 super(context); 67 mNumRows = numRows; 68 mLabel = label; 69 init(); 70 } 71 72 public InternalSelectionView(Context context, AttributeSet attrs) { 73 super(context, attrs); 74 TypedArray a = 75 context.obtainStyledAttributes( 76 attrs, R.styleable.SelectableRowView); 77 mNumRows = a.getInt(R.styleable.SelectableRowView_numRows, 5); 78 init(); 79 } 80 81 private void init() { 82 setFocusable(true); 83 mTextPaint.setAntiAlias(true); 84 mTextPaint.setTextSize(10); 85 mTextPaint.setColor(Color.WHITE); 86 } 87 88 public int getNumRows() { 89 return mNumRows; 90 } 91 92 public int getSelectedRow() { 93 return mSelectedRow; 94 } 95 96 public void setDesiredHeight(int desiredHeight) { 97 mDesiredHeight = desiredHeight; 98 } 99 100 public String getLabel() { 101 return mLabel; 102 } 103 104 @Override 105 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 106 setMeasuredDimension( 107 measureWidth(widthMeasureSpec), 108 measureHeight(heightMeasureSpec)); 109 } 110 111 private int measureWidth(int measureSpec) { 112 int specMode = MeasureSpec.getMode(measureSpec); 113 int specSize = MeasureSpec.getSize(measureSpec); 114 115 int desiredWidth = 300 + mPaddingLeft + mPaddingRight; 116 if (specMode == MeasureSpec.EXACTLY) { 117 // We were told how big to be 118 return specSize; 119 } else if (specMode == MeasureSpec.AT_MOST) { 120 return desiredWidth < specSize ? desiredWidth : specSize; 121 } else { 122 return desiredWidth; 123 } 124 } 125 126 private int measureHeight(int measureSpec) { 127 int specMode = MeasureSpec.getMode(measureSpec); 128 int specSize = MeasureSpec.getSize(measureSpec); 129 130 int desiredHeight = mDesiredHeight != null ? 131 mDesiredHeight : 132 mNumRows * mEstimatedPixelHeight + mPaddingTop + mPaddingBottom; 133 if (specMode == MeasureSpec.EXACTLY) { 134 // We were told how big to be 135 return specSize; 136 } else if (specMode == MeasureSpec.AT_MOST) { 137 return desiredHeight < specSize ? desiredHeight : specSize; 138 } else { 139 return desiredHeight; 140 } 141 } 142 143 144 @Override 145 protected void onDraw(Canvas canvas) { 146 int rectTop = mPaddingTop; 147 int rectLeft = mPaddingLeft; 148 int rectRight = getWidth() - mPaddingRight; 149 for (int i = 0; i < mNumRows; i++) { 150 151 mPainter.setColor(Color.BLACK); 152 mPainter.setAlpha(0x20); 153 154 int rowHeight = getRowHeight(i); 155 156 // draw background rect 157 mTempRect.set(rectLeft, rectTop, rectRight, rectTop + rowHeight); 158 canvas.drawRect(mTempRect, mPainter); 159 160 // draw forground rect 161 if (i == mSelectedRow && hasFocus()) { 162 mPainter.setColor(Color.RED); 163 mPainter.setAlpha(0xF0); 164 mTextPaint.setAlpha(0xFF); 165 } else { 166 mPainter.setColor(Color.BLACK); 167 mPainter.setAlpha(0x40); 168 mTextPaint.setAlpha(0xF0); 169 } 170 mTempRect.set(rectLeft + 2, rectTop + 2, 171 rectRight - 2, rectTop + rowHeight - 2); 172 canvas.drawRect(mTempRect, mPainter); 173 174 // draw text to help when visually inspecting 175 canvas.drawText( 176 Integer.toString(i), 177 rectLeft + 2, 178 rectTop + 2 - (int) mTextPaint.ascent(), 179 mTextPaint); 180 181 rectTop += rowHeight; 182 } 183 } 184 185 private int getRowHeight(int row) { 186 final int availableHeight = getHeight() - mPaddingTop - mPaddingBottom; 187 final int desiredRowHeight = availableHeight / mNumRows; 188 if (row < mNumRows - 1) { 189 return desiredRowHeight; 190 } else { 191 final int residualHeight = availableHeight % mNumRows; 192 return desiredRowHeight + residualHeight; 193 } 194 } 195 196 public void getRectForRow(Rect rect, int row) { 197 final int rowHeight = getRowHeight(row); 198 final int top = mPaddingTop + row * rowHeight; 199 rect.set(mPaddingLeft, 200 top, 201 getWidth() - mPaddingRight, 202 top + rowHeight); 203 } 204 205 206 void ensureRectVisible() { 207 getRectForRow(mTempRect, mSelectedRow); 208 requestRectangleOnScreen(mTempRect); 209 } 210 211 @Override 212 public boolean onKeyDown(int keyCode, KeyEvent event) { 213 switch(event.getKeyCode()) { 214 case KeyEvent.KEYCODE_DPAD_UP: 215 if (mSelectedRow > 0) { 216 mSelectedRow--; 217 invalidate(); 218 ensureRectVisible(); 219 return true; 220 } 221 break; 222 case KeyEvent.KEYCODE_DPAD_DOWN: 223 if (mSelectedRow < (mNumRows - 1)) { 224 mSelectedRow++; 225 invalidate(); 226 ensureRectVisible(); 227 return true; 228 } 229 break; 230 } 231 return false; 232 } 233 234 235 @Override 236 public void getFocusedRect(Rect r) { 237 getRectForRow(r, mSelectedRow); 238 } 239 240 @Override 241 protected void onFocusChanged(boolean focused, int direction, 242 Rect previouslyFocusedRect) { 243 super.onFocusChanged(focused, direction, previouslyFocusedRect); 244 245 if (focused) { 246 switch (direction) { 247 case View.FOCUS_DOWN: 248 mSelectedRow = 0; 249 break; 250 case View.FOCUS_UP: 251 mSelectedRow = mNumRows - 1; 252 break; 253 case View.FOCUS_LEFT: // fall through 254 case View.FOCUS_RIGHT: 255 // set the row that is closest to the rect 256 if (previouslyFocusedRect != null) { 257 int y = previouslyFocusedRect.top 258 + (previouslyFocusedRect.height() / 2); 259 int yPerRow = getHeight() / mNumRows; 260 mSelectedRow = y / yPerRow; 261 } else { 262 mSelectedRow = 0; 263 } 264 break; 265 default: 266 // can't gleam any useful information about what internal 267 // selection should be... 268 return; 269 } 270 invalidate(); 271 } 272 } 273 274 @Override 275 public String toString() { 276 if (mLabel != null) { 277 return mLabel; 278 } 279 return super.toString(); 280 } 281} 282