Home | History | Annotate | Download | only in view
      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