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