/*
* 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 com.android.frameworks.coretests.R;
import android.view.View;
import android.view.KeyEvent;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Color;
import android.util.AttributeSet;
/**
* 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();
}
}