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