| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.util; |
| |
| import android.content.Context; |
| import android.content.res.TypedArray; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.Paint; |
| import android.graphics.Rect; |
| import android.view.KeyEvent; |
| import android.view.View; |
| |
| import com.android.frameworks.coretests.R; |
| |
| /** |
| * A view that has a known number of selectable rows, and maintains a notion of which |
| * row is selected. The rows take up the |
| * entire width of the view. The height of the view is divided evenly among |
| * the rows. |
| * |
| * Note: If the height of the view does not divide exactly to the number of rows, |
| * the last row's height is inflated with the remainder. For example, if the |
| * view height is 22 and there are two rows, the height of the first row is |
| * 10 and the second 22. |
| * |
| * Notice what this view does to be a good citizen w.r.t its internal selection: |
| * 1) calls {@link View#requestRectangleOnScreen} each time the selection changes due to |
| * internal navigation. |
| * 2) implements {@link View#getFocusedRect} by filling in the rectangle of the currently |
| * selected row |
| * 3) overrides {@link View#onFocusChanged} and sets selection appropriately according to |
| * the previously focused rectangle. |
| */ |
| public class InternalSelectionView extends View { |
| |
| private Paint mPainter = new Paint(); |
| private Paint mTextPaint = new Paint(); |
| private Rect mTempRect = new Rect(); |
| |
| private int mNumRows = 5; |
| private int mSelectedRow = 0; |
| private final int mEstimatedPixelHeight = 10; |
| |
| private Integer mDesiredHeight = null; |
| private String mLabel = null; |
| |
| public InternalSelectionView(Context context, int numRows, String label) { |
| super(context); |
| mNumRows = numRows; |
| mLabel = label; |
| init(); |
| } |
| |
| public InternalSelectionView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| TypedArray a = |
| context.obtainStyledAttributes( |
| attrs, R.styleable.SelectableRowView); |
| mNumRows = a.getInt(R.styleable.SelectableRowView_numRows, 5); |
| init(); |
| } |
| |
| private void init() { |
| setFocusable(true); |
| mTextPaint.setAntiAlias(true); |
| mTextPaint.setTextSize(10); |
| mTextPaint.setColor(Color.WHITE); |
| } |
| |
| public int getNumRows() { |
| return mNumRows; |
| } |
| |
| public int getSelectedRow() { |
| return mSelectedRow; |
| } |
| |
| public void setDesiredHeight(int desiredHeight) { |
| mDesiredHeight = desiredHeight; |
| } |
| |
| public String getLabel() { |
| return mLabel; |
| } |
| |
| @Override |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| setMeasuredDimension( |
| measureWidth(widthMeasureSpec), |
| measureHeight(heightMeasureSpec)); |
| } |
| |
| private int measureWidth(int measureSpec) { |
| int specMode = MeasureSpec.getMode(measureSpec); |
| int specSize = MeasureSpec.getSize(measureSpec); |
| |
| int desiredWidth = 300 + mPaddingLeft + mPaddingRight; |
| if (specMode == MeasureSpec.EXACTLY) { |
| // We were told how big to be |
| return specSize; |
| } else if (specMode == MeasureSpec.AT_MOST) { |
| return desiredWidth < specSize ? desiredWidth : specSize; |
| } else { |
| return desiredWidth; |
| } |
| } |
| |
| private int measureHeight(int measureSpec) { |
| int specMode = MeasureSpec.getMode(measureSpec); |
| int specSize = MeasureSpec.getSize(measureSpec); |
| |
| int desiredHeight = mDesiredHeight != null ? |
| mDesiredHeight : |
| mNumRows * mEstimatedPixelHeight + mPaddingTop + mPaddingBottom; |
| if (specMode == MeasureSpec.EXACTLY) { |
| // We were told how big to be |
| return specSize; |
| } else if (specMode == MeasureSpec.AT_MOST) { |
| return desiredHeight < specSize ? desiredHeight : specSize; |
| } else { |
| return desiredHeight; |
| } |
| } |
| |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| int rectTop = mPaddingTop; |
| int rectLeft = mPaddingLeft; |
| int rectRight = getWidth() - mPaddingRight; |
| for (int i = 0; i < mNumRows; i++) { |
| |
| mPainter.setColor(Color.BLACK); |
| mPainter.setAlpha(0x20); |
| |
| int rowHeight = getRowHeight(i); |
| |
| // draw background rect |
| mTempRect.set(rectLeft, rectTop, rectRight, rectTop + rowHeight); |
| canvas.drawRect(mTempRect, mPainter); |
| |
| // draw forground rect |
| if (i == mSelectedRow && hasFocus()) { |
| mPainter.setColor(Color.RED); |
| mPainter.setAlpha(0xF0); |
| mTextPaint.setAlpha(0xFF); |
| } else { |
| mPainter.setColor(Color.BLACK); |
| mPainter.setAlpha(0x40); |
| mTextPaint.setAlpha(0xF0); |
| } |
| mTempRect.set(rectLeft + 2, rectTop + 2, |
| rectRight - 2, rectTop + rowHeight - 2); |
| canvas.drawRect(mTempRect, mPainter); |
| |
| // draw text to help when visually inspecting |
| canvas.drawText( |
| Integer.toString(i), |
| rectLeft + 2, |
| rectTop + 2 - (int) mTextPaint.ascent(), |
| mTextPaint); |
| |
| rectTop += rowHeight; |
| } |
| } |
| |
| private int getRowHeight(int row) { |
| final int availableHeight = getHeight() - mPaddingTop - mPaddingBottom; |
| final int desiredRowHeight = availableHeight / mNumRows; |
| if (row < mNumRows - 1) { |
| return desiredRowHeight; |
| } else { |
| final int residualHeight = availableHeight % mNumRows; |
| return desiredRowHeight + residualHeight; |
| } |
| } |
| |
| public void getRectForRow(Rect rect, int row) { |
| final int rowHeight = getRowHeight(row); |
| final int top = mPaddingTop + row * rowHeight; |
| rect.set(mPaddingLeft, |
| top, |
| getWidth() - mPaddingRight, |
| top + rowHeight); |
| } |
| |
| |
| void ensureRectVisible() { |
| getRectForRow(mTempRect, mSelectedRow); |
| requestRectangleOnScreen(mTempRect); |
| } |
| |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| switch(event.getKeyCode()) { |
| case KeyEvent.KEYCODE_DPAD_UP: |
| if (mSelectedRow > 0) { |
| mSelectedRow--; |
| invalidate(); |
| ensureRectVisible(); |
| return true; |
| } |
| break; |
| case KeyEvent.KEYCODE_DPAD_DOWN: |
| if (mSelectedRow < (mNumRows - 1)) { |
| mSelectedRow++; |
| invalidate(); |
| ensureRectVisible(); |
| return true; |
| } |
| break; |
| } |
| return false; |
| } |
| |
| |
| @Override |
| public void getFocusedRect(Rect r) { |
| getRectForRow(r, mSelectedRow); |
| } |
| |
| @Override |
| protected void onFocusChanged(boolean focused, int direction, |
| Rect previouslyFocusedRect) { |
| super.onFocusChanged(focused, direction, previouslyFocusedRect); |
| |
| if (focused) { |
| switch (direction) { |
| case View.FOCUS_DOWN: |
| mSelectedRow = 0; |
| break; |
| case View.FOCUS_UP: |
| mSelectedRow = mNumRows - 1; |
| break; |
| case View.FOCUS_LEFT: // fall through |
| case View.FOCUS_RIGHT: |
| // set the row that is closest to the rect |
| if (previouslyFocusedRect != null) { |
| int y = previouslyFocusedRect.top |
| + (previouslyFocusedRect.height() / 2); |
| int yPerRow = getHeight() / mNumRows; |
| mSelectedRow = y / yPerRow; |
| } else { |
| mSelectedRow = 0; |
| } |
| break; |
| default: |
| // can't gleam any useful information about what internal |
| // selection should be... |
| return; |
| } |
| invalidate(); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| if (mLabel != null) { |
| return mLabel; |
| } |
| return super.toString(); |
| } |
| } |