OSDN Git Service

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