OSDN Git Service

Move drawables for VerticalTextSpinner in to music app, update
[android-x86/packages-apps-Music.git] / src / com / android / music / VerticalTextSpinner.java
1 /*
2  * Copyright (C) 2008 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.android.music;
18
19 import android.content.Context;
20 import android.graphics.Canvas;
21 import android.graphics.Paint;
22 import android.graphics.Rect;
23 import android.graphics.drawable.Drawable;
24 import android.text.TextPaint;
25 import android.util.AttributeSet;
26 import android.view.KeyEvent;
27 import android.view.MotionEvent;
28 import android.view.View;
29
30
31 public class VerticalTextSpinner extends View {
32
33     private static final int SELECTOR_ARROW_HEIGHT = 15;
34
35     private static final int TEXT_SPACING = 18;
36     private static final int TEXT_MARGIN_RIGHT = 25;
37     private static final int TEXT_SIZE = 22;
38
39     /* Keep the calculations as this is really a for loop from
40      * -2 to 2 but precalculated so we don't have to do in the onDraw.
41      */
42     private static final int TEXT1_Y = (TEXT_SIZE * (-2 + 2)) + (TEXT_SPACING * (-2 + 1));
43     private static final int TEXT2_Y = (TEXT_SIZE * (-1 + 2)) + (TEXT_SPACING * (-1 + 1));
44     private static final int TEXT3_Y = (TEXT_SIZE * (0 + 2)) + (TEXT_SPACING * (0 + 1));
45     private static final int TEXT4_Y = (TEXT_SIZE * (1 + 2)) + (TEXT_SPACING * (1 + 1));
46     private static final int TEXT5_Y = (TEXT_SIZE * (2 + 2)) + (TEXT_SPACING * (2 + 1));
47
48     private static final int SCROLL_MODE_NONE = 0;
49     private static final int SCROLL_MODE_UP = 1;
50     private static final int SCROLL_MODE_DOWN = 2;
51
52     private static final long DEFAULT_SCROLL_INTERVAL_MS = 400;
53     private static final int SCROLL_DISTANCE = TEXT_SIZE + TEXT_SPACING;
54     private static final int MIN_ANIMATIONS = 4;
55
56     private final Drawable mBackgroundFocused;
57     private final Drawable mSelectorFocused;
58     private final Drawable mSelectorNormal;
59     private final int mSelectorDefaultY;
60     private final int mSelectorMinY;
61     private final int mSelectorMaxY;
62     private final int mSelectorHeight;
63     private final TextPaint mTextPaintDark;
64     private final TextPaint mTextPaintLight;
65
66     private int mSelectorY;
67     private Drawable mSelector;
68     private int mDownY;
69     private boolean isDraggingSelector;
70     private int mScrollMode;
71     private long mScrollInterval;
72     private boolean mIsAnimationRunning;
73     private boolean mStopAnimation;
74     private boolean mWrapAround = true;
75
76     private int mTotalAnimatedDistance;
77     private int mNumberOfAnimations;
78     private long mDelayBetweenAnimations;
79     private int mDistanceOfEachAnimation;
80
81     private String[] mTextList;
82     private int mCurrentSelectedPos;
83     private OnChangedListener mListener;
84
85     private String mText1;
86     private String mText2;
87     private String mText3;
88     private String mText4;
89     private String mText5;
90
91     public interface OnChangedListener {
92         void onChanged(
93                 VerticalTextSpinner spinner, int oldPos, int newPos, String[] items);
94     }
95
96     public VerticalTextSpinner(Context context) {
97         this(context, null);
98     }
99
100     public VerticalTextSpinner(Context context, AttributeSet attrs) {
101         this(context, attrs, 0);
102     }
103
104     public VerticalTextSpinner(Context context, AttributeSet attrs,
105             int defStyle) {
106         super(context, attrs, defStyle);
107
108         mBackgroundFocused = context.getResources().getDrawable(R.drawable.pickerbox_background);
109         mSelectorFocused = context.getResources().getDrawable(R.drawable.pickerbox_selected);
110         mSelectorNormal = context.getResources().getDrawable(R.drawable.pickerbox_unselected);
111
112         mSelectorHeight = mSelectorFocused.getIntrinsicHeight();
113         mSelectorDefaultY = (mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight) / 2;
114         mSelectorMinY = 0;
115         mSelectorMaxY = mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight;
116
117         mSelector = mSelectorNormal;
118         mSelectorY = mSelectorDefaultY;
119
120         mTextPaintDark = new TextPaint(Paint.ANTI_ALIAS_FLAG);
121         mTextPaintDark.setTextSize(TEXT_SIZE);
122         mTextPaintDark.setColor(context.getResources()
123             .getColor(android.R.color.primary_text_light));
124
125         mTextPaintLight = new TextPaint(Paint.ANTI_ALIAS_FLAG);
126         mTextPaintLight.setTextSize(TEXT_SIZE);
127         mTextPaintLight.setColor(context.getResources()
128             .getColor(android.R.color.secondary_text_dark));
129
130         mScrollMode = SCROLL_MODE_NONE;
131         mScrollInterval = DEFAULT_SCROLL_INTERVAL_MS;
132         calculateAnimationValues();
133     }
134
135     public void setOnChangeListener(OnChangedListener listener) {
136         mListener = listener;
137     }
138
139     public void setItems(String[] textList) {
140         mTextList = textList;
141         calculateTextPositions();
142     }
143
144     public void setSelectedPos(int selectedPos) {
145         mCurrentSelectedPos = selectedPos;
146         calculateTextPositions();
147         postInvalidate();
148     }
149
150     public void setScrollInterval(long interval) {
151         mScrollInterval = interval;
152         calculateAnimationValues();
153     }
154
155     public void setWrapAround(boolean wrap) {
156         mWrapAround = wrap;
157     }
158
159     @Override
160     public boolean onKeyDown(int keyCode, KeyEvent event) {
161
162         /* This is a bit confusing, when we get the key event
163          * DPAD_DOWN we actually roll the spinner up. When the
164          * key event is DPAD_UP we roll the spinner down.
165          */
166         if ((keyCode == KeyEvent.KEYCODE_DPAD_UP) && canScrollDown()) {
167             mScrollMode = SCROLL_MODE_DOWN;
168             scroll();
169             mStopAnimation = true;
170             return true;
171         } else if ((keyCode == KeyEvent.KEYCODE_DPAD_DOWN) && canScrollUp()) {
172             mScrollMode = SCROLL_MODE_UP;
173             scroll();
174             mStopAnimation = true;
175             return true;
176         }
177         return super.onKeyDown(keyCode, event);
178     }
179
180     private boolean canScrollDown() {
181         return (mCurrentSelectedPos > 0) || mWrapAround;
182     }
183
184     private boolean canScrollUp() {
185         return ((mCurrentSelectedPos < (mTextList.length - 1)) || mWrapAround);
186     }
187
188     @Override
189     protected void onFocusChanged(boolean gainFocus, int direction,
190             Rect previouslyFocusedRect) {
191         if (gainFocus) {
192             setBackgroundDrawable(mBackgroundFocused);
193             mSelector = mSelectorFocused;
194         } else {
195             setBackgroundDrawable(null);
196             mSelector = mSelectorNormal;
197             mSelectorY = mSelectorDefaultY;
198         }
199     }
200
201     @Override
202     public boolean onTouchEvent(MotionEvent event) {
203         final int action = event.getAction();
204         final int y = (int) event.getY();
205
206         switch (action) {
207         case MotionEvent.ACTION_DOWN:
208             requestFocus();
209             mDownY = y;
210             isDraggingSelector = (y >= mSelectorY) &&
211                     (y <= (mSelectorY + mSelector.getIntrinsicHeight()));
212             break;
213
214         case MotionEvent.ACTION_MOVE:
215             if (isDraggingSelector) {
216                 int top = mSelectorDefaultY + (y - mDownY);
217                 if (top <= mSelectorMinY && canScrollDown()) {
218                     mSelectorY = mSelectorMinY;
219                     mStopAnimation = false;
220                     if (mScrollMode != SCROLL_MODE_DOWN) {
221                         mScrollMode = SCROLL_MODE_DOWN;
222                         scroll();
223                     }
224                 } else if (top >= mSelectorMaxY && canScrollUp()) {
225                     mSelectorY = mSelectorMaxY;
226                     mStopAnimation = false;
227                     if (mScrollMode != SCROLL_MODE_UP) {
228                         mScrollMode = SCROLL_MODE_UP;
229                         scroll();
230                     }
231                 } else {
232                     mSelectorY = top;
233                     mStopAnimation = true;
234                 }
235             }
236             break;
237
238         case MotionEvent.ACTION_UP:
239         case MotionEvent.ACTION_CANCEL:
240         default:
241             mSelectorY = mSelectorDefaultY;
242             mStopAnimation = true;
243             invalidate();
244             break;
245         }
246         return true;
247     }
248
249     @Override
250     protected void onDraw(Canvas canvas) {
251
252         /* The bounds of the selector */
253         final int selectorLeft = 0;
254         final int selectorTop = mSelectorY;
255         final int selectorRight = getMeasuredWidth();
256         final int selectorBottom = mSelectorY + mSelectorHeight;
257
258         /* Draw the selector */
259         mSelector.setBounds(selectorLeft, selectorTop, selectorRight, selectorBottom);
260         mSelector.draw(canvas);
261
262         if (mTextList == null) {
263
264             /* We're not setup with values so don't draw anything else */
265             return;
266         }
267
268         final TextPaint textPaintDark = mTextPaintDark;
269         if (hasFocus()) {
270
271             /* The bounds of the top area where the text should be light */
272             final int topLeft = 0;
273             final int topTop = 0;
274             final int topRight = selectorRight;
275             final int topBottom = selectorTop + SELECTOR_ARROW_HEIGHT;
276
277             /* Assign a bunch of local finals for performance */
278             final String text1 = mText1;
279             final String text2 = mText2;
280             final String text3 = mText3;
281             final String text4 = mText4;
282             final String text5 = mText5;
283             final TextPaint textPaintLight = mTextPaintLight;
284
285             /*
286              * Draw the 1st, 2nd and 3rd item in light only, clip it so it only
287              * draws in the area above the selector
288              */
289             canvas.save();
290             canvas.clipRect(topLeft, topTop, topRight, topBottom);
291             drawText(canvas, text1, TEXT1_Y
292                     + mTotalAnimatedDistance, textPaintLight);
293             drawText(canvas, text2, TEXT2_Y
294                     + mTotalAnimatedDistance, textPaintLight);
295             drawText(canvas, text3,
296                     TEXT3_Y + mTotalAnimatedDistance, textPaintLight);
297             canvas.restore();
298
299             /*
300              * Draw the 2nd, 3rd and 4th clipped to the selector bounds in dark
301              * paint
302              */
303             canvas.save();
304             canvas.clipRect(selectorLeft, selectorTop + SELECTOR_ARROW_HEIGHT,
305                     selectorRight, selectorBottom - SELECTOR_ARROW_HEIGHT);
306             drawText(canvas, text2, TEXT2_Y
307                     + mTotalAnimatedDistance, textPaintDark);
308             drawText(canvas, text3,
309                     TEXT3_Y + mTotalAnimatedDistance, textPaintDark);
310             drawText(canvas, text4,
311                     TEXT4_Y + mTotalAnimatedDistance, textPaintDark);
312             canvas.restore();
313
314             /* The bounds of the bottom area where the text should be light */
315             final int bottomLeft = 0;
316             final int bottomTop = selectorBottom - SELECTOR_ARROW_HEIGHT;
317             final int bottomRight = selectorRight;
318             final int bottomBottom = getMeasuredHeight();
319
320             /*
321              * Draw the 3rd, 4th and 5th in white text, clip it so it only draws
322              * in the area below the selector.
323              */
324             canvas.save();
325             canvas.clipRect(bottomLeft, bottomTop, bottomRight, bottomBottom);
326             drawText(canvas, text3,
327                     TEXT3_Y + mTotalAnimatedDistance, textPaintLight);
328             drawText(canvas, text4,
329                     TEXT4_Y + mTotalAnimatedDistance, textPaintLight);
330             drawText(canvas, text5,
331                     TEXT5_Y + mTotalAnimatedDistance, textPaintLight);
332             canvas.restore();
333
334         } else {
335             drawText(canvas, mText3, TEXT3_Y, textPaintDark);
336         }
337         if (mIsAnimationRunning) {
338             if ((Math.abs(mTotalAnimatedDistance) + mDistanceOfEachAnimation) > SCROLL_DISTANCE) {
339                 mTotalAnimatedDistance = 0;
340                 if (mScrollMode == SCROLL_MODE_UP) {
341                     int oldPos = mCurrentSelectedPos;
342                     int newPos = getNewIndex(1);
343                     if (newPos >= 0) {
344                         mCurrentSelectedPos = newPos;
345                         if (mListener != null) {
346                             mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList);
347                         }
348                     }
349                     if (newPos < 0 || ((newPos >= mTextList.length - 1) && !mWrapAround)) {
350                         mStopAnimation = true;
351                     }
352                     calculateTextPositions();
353                 } else if (mScrollMode == SCROLL_MODE_DOWN) {
354                     int oldPos = mCurrentSelectedPos;
355                     int newPos = getNewIndex(-1);
356                     if (newPos >= 0) {
357                         mCurrentSelectedPos = newPos;
358                         if (mListener != null) {
359                             mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList);
360                         }
361                     }
362                     if (newPos < 0 || (newPos == 0 && !mWrapAround)) {
363                         mStopAnimation = true;
364                     }
365                     calculateTextPositions();
366                 }
367                 if (mStopAnimation) {
368                     final int previousScrollMode = mScrollMode;
369
370                     /* No longer scrolling, we wait till the current animation
371                      * completes then we stop.
372                      */
373                     mIsAnimationRunning = false;
374                     mStopAnimation = false;
375                     mScrollMode = SCROLL_MODE_NONE;
376
377                     /* If the current selected item is an empty string
378                      * scroll past it.
379                      */
380                     if ("".equals(mTextList[mCurrentSelectedPos])) {
381                        mScrollMode = previousScrollMode;
382                        scroll();
383                        mStopAnimation = true;
384                     }
385                 }
386             } else {
387                 if (mScrollMode == SCROLL_MODE_UP) {
388                     mTotalAnimatedDistance -= mDistanceOfEachAnimation;
389                 } else if (mScrollMode == SCROLL_MODE_DOWN) {
390                     mTotalAnimatedDistance += mDistanceOfEachAnimation;
391                 }
392             }
393             if (mDelayBetweenAnimations > 0) {
394                 postInvalidateDelayed(mDelayBetweenAnimations);
395             } else {
396                 invalidate();
397             }
398         }
399     }
400
401     /**
402      * Called every time the text items or current position
403      * changes. We calculate store we don't have to calculate
404      * onDraw.
405      */
406     private void calculateTextPositions() {
407         mText1 = getTextToDraw(-2);
408         mText2 = getTextToDraw(-1);
409         mText3 = getTextToDraw(0);
410         mText4 = getTextToDraw(1);
411         mText5 = getTextToDraw(2);
412     }
413
414     private String getTextToDraw(int offset) {
415         int index = getNewIndex(offset);
416         if (index < 0) {
417             return "";
418         }
419         return mTextList[index];
420     }
421
422     private int getNewIndex(int offset) {
423         int index = mCurrentSelectedPos + offset;
424         if (index < 0) {
425             if (mWrapAround) {
426                 index += mTextList.length;
427             } else {
428                 return -1;
429             }
430         } else if (index >= mTextList.length) {
431             if (mWrapAround) {
432                 index -= mTextList.length;
433             } else {
434                 return -1;
435             }
436         }
437         return index;
438     }
439
440     private void scroll() {
441         if (mIsAnimationRunning) {
442             return;
443         }
444         mTotalAnimatedDistance = 0;
445         mIsAnimationRunning = true;
446         invalidate();
447     }
448
449     private void calculateAnimationValues() {
450         mNumberOfAnimations = (int) mScrollInterval / SCROLL_DISTANCE;
451         if (mNumberOfAnimations < MIN_ANIMATIONS) {
452             mNumberOfAnimations = MIN_ANIMATIONS;
453             mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations;
454             mDelayBetweenAnimations = 0;
455         } else {
456             mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations;
457             mDelayBetweenAnimations = mScrollInterval / mNumberOfAnimations;
458         }
459     }
460
461     private void drawText(Canvas canvas, String text, int y, TextPaint paint) {
462         int width = (int) paint.measureText(text);
463         int x = getMeasuredWidth() - width - TEXT_MARGIN_RIGHT;
464         canvas.drawText(text, x, y, paint);
465     }
466
467     public int getCurrentSelectedPos() {
468         return mCurrentSelectedPos;
469     }
470 }