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