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 
InternalSelectionView(Context context, int numRows)58     public InternalSelectionView(Context context, int numRows) {
59         this(context, numRows, "");
60     }
61 
InternalSelectionView(Context context, int numRows, String label)62     public InternalSelectionView(Context context, int numRows, String label) {
63         super(context);
64         mNumRows = numRows;
65         mLabel = label;
66         init();
67     }
68 
InternalSelectionView(Context context, AttributeSet attrs)69     public InternalSelectionView(Context context, AttributeSet attrs) {
70         super(context, attrs);
71         init();
72     }
73 
init()74     private void init() {
75         setFocusable(true);
76         mTextPaint.setAntiAlias(true);
77         mTextPaint.setTextSize(10);
78         mTextPaint.setColor(Color.WHITE);
79     }
80 
getNumRows()81     public int getNumRows() {
82         return mNumRows;
83     }
84 
getSelectedRow()85     public int getSelectedRow() {
86         return mSelectedRow;
87     }
88 
setDesiredHeight(int desiredHeight)89     public void setDesiredHeight(int desiredHeight) {
90         mDesiredHeight = desiredHeight;
91     }
92 
getLabel()93     public String getLabel() {
94         return mLabel;
95     }
96 
97     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)98     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
99         setMeasuredDimension(
100             measureWidth(widthMeasureSpec),
101             measureHeight(heightMeasureSpec));
102     }
103 
measureWidth(int measureSpec)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 
measureHeight(int measureSpec)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
onDraw(Canvas canvas)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 
getRowHeight()179     private int getRowHeight() {
180         return (getHeight() - getPaddingTop() - getPaddingBottom()) / mNumRows;
181     }
182 
getRectForRow(Rect rect, int row)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 
ensureRectVisible()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
onKeyDown(int keyCode, KeyEvent event)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
getFocusedRect(Rect r)227     public void getFocusedRect(Rect r) {
228         getRectForRow(r, mSelectedRow);
229     }
230 
231     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)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
toString()266     public String toString() {
267         if (mLabel != null) {
268             return mLabel;
269         }
270         return super.toString();
271     }
272 }
273