/* * Copyright (C) 2012-2013, The Linux Foundation. All rights reserved. * Not a Contribution * * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.caf.fmradio; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.Widget; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.graphics.Paint.Align; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.text.InputFilter; import android.text.InputType; import android.text.Spanned; import android.text.TextUtils; import android.text.method.NumberKeyListener; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.LayoutInflater.Filter; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.Scroller; import android.widget.TextView; import com.android.internal.R; /** * A widget that enables the user to select a number form a predefined range. * The widget presents an input filed and up and down buttons for selecting the * current value. Pressing/long pressing the up and down buttons increments and * decrements the current value respectively. Touching the input filed shows a * scroll wheel, tapping on which while shown and not moving allows direct edit * of the current value. Sliding motions up or down hide the buttons and the * input filed, show the scroll wheel, and rotate the latter. Flinging is also * supported. The widget enables mapping from positions to strings such that * instead the position index the corresponding string is displayed. *
* For an example of using this widget, see {@link android.widget.TimePicker}. *
*/ @Widget public class HorizontalNumberPicker extends LinearLayout { /** * The default update interval during long press. */ private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300; /** * The index of the middle selector item. */ // private static final int SELECTOR_MIDDLE_ITEM_INDEX = 10; /** * The coefficient by which to adjust (divide) the max fling velocity. */ private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 4; /** * The the duration for adjusting the selector wheel. */ private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800; /** * The duration of scrolling to the next/previous value while changing the * current value by one, i.e. increment or decrement. */ private static final int CHANGE_CURRENT_BY_ONE_SCROLL_DURATION = 300; /** * The the delay for showing the input controls after a single tap on the * input text. */ private static final int SHOW_INPUT_CONTROLS_DELAY_MILLIS = ViewConfiguration .getDoubleTapTimeout(); /** * The strength of fading in the top and bottom while drawing the selector. */ private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f; /** * The default unscaled height of the selection divider. */ private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2; /** * In this state the selector wheel is not shown. */ private static final int SELECTOR_WHEEL_STATE_NONE = 0; /** * In this state the selector wheel is small. */ private static final int SELECTOR_WHEEL_STATE_SMALL = 1; /** * In this state the selector wheel is large. */ private static final int SELECTOR_WHEEL_STATE_LARGE = 2; /** * The alpha of the selector wheel when it is bright. */ private static final int SELECTOR_WHEEL_BRIGHT_ALPHA = 255; /** * The alpha of the selector wheel when it is dimmed. */ private static final int SELECTOR_WHEEL_DIM_ALPHA = 60; /** * The alpha for the increment/decrement button when it is transparent. */ private static final int BUTTON_ALPHA_TRANSPARENT = 0; /** * The alpha for the increment/decrement button when it is opaque. */ private static final int BUTTON_ALPHA_OPAQUE = 1; /** * The property for setting the selector paint. */ private static final String PROPERTY_SELECTOR_PAINT_ALPHA = "selectorPaintAlpha"; /** * The property for setting the increment/decrement button alpha. */ private static final String PROPERTY_BUTTON_ALPHA = "alpha"; /** * The numbers accepted by the input text's {@link Filter} */ private static final char[] DIGIT_CHARACTERS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; /** * Constant for unspecified size. */ private static final int SIZE_UNSPECIFIED = -1; /** * Use a custom NumberPicker formatting callback to use two-digit minutes * strings like "01". Keeping a static formatter etc. is the most efficient * way to do this; it avoids creating temporary objects on every call to * format(). * * @hide */ public static final HorizontalNumberPicker.Formatter TWO_DIGIT_FORMATTER = new HorizontalNumberPicker.Formatter() { final StringBuilder mBuilder = new StringBuilder(); final java.util.Formatter mFmt = new java.util.Formatter(mBuilder, java.util.Locale.US); final Object[] mArgs = new Object[1]; public String format(int value) { mArgs[0] = value; mBuilder.delete(0, mBuilder.length()); mFmt.format("%02d", mArgs); return mFmt.toString(); } }; private static final String TAG = "HorizontalNumberPicker"; /** * The increment button. */ // private final ImageButton mIncrementButton; /** * The decrement button. */ // private final ImageButton mDecrementButton; /** * The text for showing the current value. */ // private final EditText mInputText; /** * The min height of this widget. */ private final int mMinHeight = 0; /** * The max height of this widget. */ private int mMaxHeight; /** * The max width of this widget. */ private final int mMinWidth = 0; /** * The max width of this widget. */ private int mMaxWidth; /** * Flag whether to compute the max width. */ private final boolean mComputeMaxWidth; /** * The height of the text. */ private int mTextSize = 60; /** * The height of the gap between text elements if the selector wheel. */ private int mSelectorTextGapHeight; /** * The width of the gap between text elements if the selector wheel. */ private int mSelectorTextGapWidth; /** * The values to be displayed instead the indices. */ private String[] mDisplayedValues; /** * Lower value of the range of numbers allowed for the NumberPicker */ private int mMinValue; /** * Upper value of the range of numbers allowed for the NumberPicker */ private int mMaxValue; /** * Current value of this NumberPicker */ private int mValue; /** * Listener to be notified upon current value change. */ private OnValueChangeListener mOnValueChangeListener; /** * Listener to be notified upon scroll state change. */ private OnScrollListener mOnScrollListener; /** * Formatter for for displaying the current value. */ private Formatter mFormatter; /** * The speed for updating the value form long press. */ private long mLongPressUpdateInterval = DEFAULT_LONG_PRESS_UPDATE_INTERVAL; /** * Cache for the string representation of selector indices. */ private final SparseArray* Note: If you have provided alternative values for the values this * formatter is never invoked. *
* * @param formatter * The formatter object. If formatter isnull
,
* {@link String#valueOf(int)} will be used.
*
* @see #setDisplayedValues(String[])
*/
public void setFormatter(Formatter formatter) {
if (formatter == mFormatter) {
return;
}
mFormatter = formatter;
initializeSelectorWheelIndices();
// updateInputTextView();
}
/**
* Set the current value for the number picker.
*
* If the argument is less than the {@link NumberPicker#getMinValue()} and
* {@link NumberPicker#getWrapSelectorWheel()} is false
the
* current value is set to the {@link NumberPicker#getMinValue()} value.
*
* If the argument is less than the {@link NumberPicker#getMinValue()} and
* {@link NumberPicker#getWrapSelectorWheel()} is true
the
* current value is set to the {@link NumberPicker#getMaxValue()} value.
*
* If the argument is less than the {@link NumberPicker#getMaxValue()} and
* {@link NumberPicker#getWrapSelectorWheel()} is false
the
* current value is set to the {@link NumberPicker#getMaxValue()} value.
*
* If the argument is less than the {@link NumberPicker#getMaxValue()} and
* {@link NumberPicker#getWrapSelectorWheel()} is true
the
* current value is set to the {@link NumberPicker#getMinValue()} value.
*
* By default if the range (max - min) is more than five (the number of * items shown on the selector wheel) the selector wheel wrapping is * enabled. *
* * @param wrapSelectorWheel * Whether to wrap. */ public void setWrapSelectorWheel(boolean wrapSelectorWheel) { if (wrapSelectorWheel && (mMaxValue - mMinValue) < mSelectorIndices.length) { throw new IllegalStateException( "Range less than selector items count."); } if (wrapSelectorWheel != mWrapSelectorWheel) { mWrapSelectorWheel = wrapSelectorWheel; updateIncrementAndDecrementButtonsVisibilityState(); } } /** * Sets the speed at which the numbers be incremented and decremented when * the up and down buttons are long pressed respectively. ** The default value is 300 ms. *
* * @param intervalMillis * The speed (in milliseconds) at which the numbers will be * incremented and decremented. */ public void setOnLongPressUpdateInterval(long intervalMillis) { mLongPressUpdateInterval = intervalMillis; } /** * Returns the value of the picker. * * @return The value. */ public int getValue() { return mValue; } /** * Returns the min value of the picker. * * @return The min value */ public int getMinValue() { return mMinValue; } /** * Sets the min value of the picker. * * @param minValue * The min value. */ public void setMinValue(int minValue) { if (mMinValue == minValue) { return; } if (minValue < 0) { throw new IllegalArgumentException("minValue must be >= 0"); } mMinValue = minValue; if (mMinValue > mValue) { mValue = mMinValue; } boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; setWrapSelectorWheel(wrapSelectorWheel); initializeSelectorWheelIndices(); updateInputTextView(); tryComputeMaxWidth(); } /** * Returns the max value of the picker. * * @return The max value. */ public int getMaxValue() { return mMaxValue; } /** * Sets the max value of the picker. * * @param maxValue * The max value. */ public void setMaxValue(int maxValue) { if (mMaxValue == maxValue) { return; } if (maxValue < 0) { throw new IllegalArgumentException("maxValue must be >= 0"); } mMaxValue = maxValue; if (mMaxValue < mValue) { mValue = mMaxValue; } boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; setWrapSelectorWheel(wrapSelectorWheel); initializeSelectorWheelIndices(); tryComputeMaxWidth(); } /** * Gets the values to be displayed instead of string values. * * @return The displayed values. */ public String[] getDisplayedValues() { return mDisplayedValues; } /** * Sets the values to be displayed. * * @param displayedValues * The displayed values. */ public void setDisplayedValues(String[] displayedValues) { if (mDisplayedValues == displayedValues) { return; } mDisplayedValues = displayedValues; if (mDisplayedValues != null) { // Allow text entry rather than strictly numeric entry. // mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT // | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); } else { // mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER); } // updateInputTextView(); initializeSelectorWheelIndices(); tryComputeMaxWidth(); } /** * Sets the values to be displayed.If autoCalMinMax passed true, will calculate * and set min value and max value. * * @param displayedValues * The displayed values. * @param autoCalMinMax * Whether auto calculate and set the min value and max value. */ public void setDisplayedValues(String[] displayeValues , boolean autoCalculateMinMax) { if(autoCalculateMinMax){ mMinValue = 0; mMaxValue = displayeValues.length - 1; } setDisplayedValues(displayeValues); } @Override protected float getTopFadingEdgeStrength() { return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH; } @Override protected float getBottomFadingEdgeStrength() { return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); // make sure we show the controls only the very // first time the user sees this widget if (mFlingable && !isInEditMode()) { // animate a bit slower the very first time showInputControls(mShowInputControlsAnimimationDuration * 2); } } @Override protected void onDetachedFromWindow() { removeAllCallbacks(); } @Override protected void dispatchDraw(Canvas canvas) { // There is a good reason for doing this. See comments in draw(). } @Override public void draw(Canvas canvas) { // Dispatch draw to our children only if we are not currently running // the animation for simultaneously dimming the scroll wheel and // showing in the buttons. This class takes advantage of the View // implementation of fading edges effect to draw the selector wheel. // However, in View.draw(), the fading is applied after all the children // have been drawn and we do not want this fading to be applied to the // buttons. Therefore, we draw our children after we have completed // drawing ourselves. super.draw(canvas); // Draw our children if we are not showing the selector wheel of fading // it out // if (mShowInputControlsAnimator.isRunning() // || mSelectorWheelState != SELECTOR_WHEEL_STATE_LARGE) { // long drawTime = getDrawingTime(); // for (int i = 0, count = getChildCount(); i < count; i++) { // View child = getChildAt(i); // if (!child.isShown()) { // continue; // } // drawChild(canvas, getChildAt(i), drawTime); // } // } } @Override protected void onDraw(Canvas canvas) { if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) { return; } float x = 0.0f; float y = 0.0f; if (!mHorizontal) { x = (mRight - mLeft) / 2; y = mCurrentScrollOffset; } else { x = mCurrentScrollOffset; y = (mBottom - mTop) / 2 + mTextSize/2; if(Math.abs(mDensity - 1.5f) < 0.001f){ y += mHdpiPositionAdjust; } } final int restoreCount = canvas.save(); if (mSelectorWheelState == SELECTOR_WHEEL_STATE_SMALL) { Rect clipBounds = canvas.getClipBounds(); clipBounds.inset(0, mSelectorElementHeight); canvas.clipRect(clipBounds); } // draw the selector wheel int[] selectorIndices = mSelectorIndices; for (int i = 0; i < selectorIndices.length; i++) { int selectorIndex = selectorIndices[i]; float fNumber = 0; String scrollSelectorValue = mSelectorIndexToStringCache .get(selectorIndex); if(i - mSelectorMiddleItemIndex > 0 ){ mSelectorWheelPaint.setColor(Color.WHITE); mSelectorWheelPaint.setAlpha(SELECTOR_TEXT_ALPHA_TRANSPARENT_NONE); }else if(i - mSelectorMiddleItemIndex < 0 ){ mSelectorWheelPaint.setColor(Color.WHITE); mSelectorWheelPaint.setAlpha(SELECTOR_TEXT_ALPHA_TRANSPARENT_NONE); }else{ mSelectorWheelPaint.setColor(Color.WHITE); mSelectorWheelPaint.setAlpha(0); } try { fNumber = Float.valueOf(scrollSelectorValue).floatValue(); } catch(NumberFormatException e) { e.printStackTrace(); } boolean bShowNumber = false; float fWidthOfScale = mScaleWidth ; float fGapBetweenNumAndScale = mGapBetweenNumAndScale * mDensity; float fScaleLength = mScaleLengthShort * mDensity; //every 0.5MHz show number. if((int)(fNumber * 100)%50 == 0 ){ if(!(selectorIndex == getMaxValue())){ bShowNumber = true; fScaleLength = mScaleLengthLong * mDensity; } } else { fWidthOfScale-=2; } if(bShowNumber){ float originalWidth = mSelectorWheelPaint.getStrokeWidth(); int originalAlpha = mSelectorWheelPaint.getAlpha(); mSelectorWheelPaint.setTypeface(Typeface.DEFAULT_BOLD); mSelectorWheelPaint.setStrokeWidth(2); mSelectorWheelPaint.setStyle(Paint.Style.FILL_AND_STROKE); mSelectorWheelPaint.setAlpha(SELECTOR_TEXT_ALPHA_TRANSPARENT_NONE); canvas.drawText(scrollSelectorValue, x, mTextSize * 2, mSelectorWheelPaint); mSelectorWheelPaint.setStyle(Paint.Style.FILL); mSelectorWheelPaint.setStrokeWidth(originalWidth); mSelectorWheelPaint.setAlpha(originalAlpha); } float left = x; float top = (mBottom - mTop) - fGapBetweenNumAndScale - fScaleLength; float right = x+fWidthOfScale; float bottom = (mBottom - mTop); canvas.drawRect(left, top, right, bottom, mSelectorWheelPaint); if (mHorizontal) { x += mSelectorElementWidth; } else { y += mSelectorElementHeight; } } canvas.restoreToCount(restoreCount); } @Override public void sendAccessibilityEvent(int eventType) { // Do not send accessibility events - we want the user to // perceive this widget as several controls rather as a whole. } /** * Makes a measure spec that tries greedily to use the max value. * * @param measureSpec * The measure spec. * @param maxSize * The max value for the size. * @return A measure spec greedily imposing the max size. */ private int makeMeasureSpec(int measureSpec, int maxSize) { if (maxSize == SIZE_UNSPECIFIED) { return measureSpec; } final int size = MeasureSpec.getSize(measureSpec); final int mode = MeasureSpec.getMode(measureSpec); switch (mode) { case MeasureSpec.EXACTLY: return measureSpec; case MeasureSpec.AT_MOST: return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY); case MeasureSpec.UNSPECIFIED: return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY); default: throw new IllegalArgumentException("Unknown measure mode: " + mode); } } /** * Utility to reconcile a desired size and state, with constraints imposed * by a MeasureSpec. Tries to respect the min size, unless a different size * is imposed by the constraints. * * @param minSize * The minimal desired size. * @param measuredSize * The currently measured size. * @param measureSpec * The current measure spec. * @return The resolved size and state. */ private int resolveSizeAndStateRespectingMinSize(int minSize, int measuredSize, int measureSpec) { if (minSize != SIZE_UNSPECIFIED) { final int desiredWidth = Math.max(minSize, measuredSize); return resolveSizeAndState(desiredWidth, measureSpec, 0); } else { return measuredSize; } } /** * Resets the selector indices and clear the cached string representation of * these indices. */ private void initializeSelectorWheelIndices() { mSelectorIndexToStringCache.clear(); int[] selectorIdices = mSelectorIndices; int current = getValue(); for (int i = 0; i < mSelectorIndices.length; i++) { int selectorIndex = current + (i - mSelectorMiddleItemIndex); if (mWrapSelectorWheel) { try { selectorIndex = getWrappedSelectorIndex(selectorIndex); } catch(RuntimeException e) { e.printStackTrace(); } } mSelectorIndices[i] = selectorIndex; ensureCachedScrollSelectorValue(mSelectorIndices[i]); } } /** * Sets the current value of this NumberPicker, and sets mPrevious to the * previous value. If current is greater than mEnd less than mStart, the * value of mCurrent is wrapped around. Subclasses can override this to * change the wrapping behavior * * @param current * the new value of the NumberPicker */ private void changeCurrent(int current) { if (mValue == current) { return; } // Wrap around the values if we go past the start or end if (mWrapSelectorWheel) { try { current = getWrappedSelectorIndex(current); } catch(RuntimeException e) { e.printStackTrace(); } } int previous = mValue; setValue(current); notifyChange(previous, current); } /** * Changes the current value by one which is increment or decrement based on * the passes argument. * * @param increment * True to increment, false to decrement. */ private void changeCurrentByOne(boolean increment) { if (mFlingable) { mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA); mPreviousScrollerY = 0; mPreviousScrollerX = 0; forceCompleteChangeCurrentByOneViaScroll(); if (increment) { if (mHorizontal) { mFlingScroller.startScroll(0, 0, -mSelectorElementHeight, 0, CHANGE_CURRENT_BY_ONE_SCROLL_DURATION); } else { mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, CHANGE_CURRENT_BY_ONE_SCROLL_DURATION); } } else { if (mHorizontal) { mFlingScroller.startScroll(0, 0, mSelectorElementHeight, 0, CHANGE_CURRENT_BY_ONE_SCROLL_DURATION); } else { mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight, CHANGE_CURRENT_BY_ONE_SCROLL_DURATION); } } invalidate(); } else { if (increment) { changeCurrent(mValue + 1); } else { changeCurrent(mValue - 1); } } } /** * Ensures that if we are in the process of changing the current value by * one via scrolling the scroller gets to its final state and the value is * updated. */ private void forceCompleteChangeCurrentByOneViaScroll() { Scroller scroller = mFlingScroller; if (!scroller.isFinished()) { if (mHorizontal) { final int xBeforeAbort = scroller.getCurrX(); scroller.abortAnimation(); final int xDelta = scroller.getCurrX() - xBeforeAbort; scrollBy(xDelta, 0); } else { final int yBeforeAbort = scroller.getCurrY(); scroller.abortAnimation(); final int yDelta = scroller.getCurrY() - yBeforeAbort; scrollBy(0, yDelta); } } } /** * Sets thealpha
of the {@link Paint} for drawing the selector
* wheel.
*/
@SuppressWarnings("unused")
// Called via reflection
private void setSelectorPaintAlpha(int alpha) {
mSelectorWheelPaint.setAlpha(alpha);
invalidate();
}
/**
* @return If the event
is in the visible view
.
*/
private boolean isEventInVisibleViewHitRect(MotionEvent event, View view) {
if (view.getVisibility() == VISIBLE) {
view.getHitRect(mTempRect);
return mTempRect.contains((int) event.getX(), (int) event.getY());
}
return false;
}
/**
* Sets the selectorWheelState
.
*/
private void setSelectorWheelState(int selectorWheelState) {
mSelectorWheelState = selectorWheelState;
if (selectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
}
if (mFlingable && selectorWheelState == SELECTOR_WHEEL_STATE_LARGE
&& AccessibilityManager.getInstance(mContext).isEnabled()) {
AccessibilityManager.getInstance(mContext).interrupt();
String text = mContext
.getString(R.string.number_picker_increment_scroll_action);
}
}
private void initializeSelectorWheel() {
initializeSelectorWheelIndices();
int[] selectorIndices = mSelectorIndices;
int totalTextHeight = selectorIndices.length * mTextSize;
int totalTextWidth = (selectorIndices.length - 1) * 2;
// set it horizontal
float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
float totalTextGapWidth = (mRight - mLeft) - totalTextWidth;
float textGapCount = selectorIndices.length - 1;
if (mHorizontal) {
mSelectorTextGapWidth = (int) (totalTextGapWidth / textGapCount);
Log.d(TAG,"mSelectorTextGapWidth :" + mSelectorTextGapWidth);
mSelectorElementWidth = 2 + mSelectorTextGapWidth;
mInitialScrollOffset = INIT_SCROLL_OFFSET_HORIZONTAL;
} else {
mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
mInitialScrollOffset = INIT_SCROLL_OFFSET_VERTICAL;
}
mCurrentScrollOffset = mInitialScrollOffset;
}
private void initializeFadingEdges() {
setVerticalFadingEdgeEnabled(true);
setFadingEdgeLength((mBottom - mTop - mTextSize) / 2);
}
/**
* Callback invoked upon completion of a given scroller
.
*/
private void onScrollerFinished(Scroller scroller) {
if(mOnScrollFinishListener != null){
mOnScrollFinishListener.onScrollFinish(mValue);
}
if (scroller == mFlingScroller) {
if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
postAdjustScrollerCommand(0);
onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
} else {
// updateInputTextView();
// fadeSelectorWheel(mShowInputControlsAnimimationDuration);
}
} else {
// updateInputTextView();
// showInputControls(mShowInputControlsAnimimationDuration);
}
}
/**
* Handles transition to a given scrollState
*/
private void onScrollStateChange(int scrollState) {
if (mScrollState == scrollState) {
return;
}
mScrollState = scrollState;
if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChange(this, scrollState);
}
}
/**
* Flings the selector with the given velocityY
.
*/
private void fling(int velocity) {
mPreviousScrollerY = 0;
mPreviousScrollerX = 0;
int velocityY = velocity;
int velocityX = velocity;
if (mHorizontal) {
if (velocityX > 0) {
mFlingScroller.fling(0, 0, velocityX, 0, 0, Integer.MAX_VALUE,
0, 0);
} else {
mFlingScroller.fling(Integer.MAX_VALUE, 0, velocityX, 0, 0,
Integer.MAX_VALUE, 0, 0);
}
} else {
if (velocityY > 0) {
mFlingScroller.fling(0, 0, 0, velocityY, 0, 0, 0,
Integer.MAX_VALUE);
} else {
mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocityY, 0, 0,
0, Integer.MAX_VALUE);
}
}
invalidate();
}
/**
* Hides the input controls which is the up/down arrows and the text field.
*/
private void hideInputControls() {
// mShowInputControlsAnimator.cancel();
// mIncrementButton.setVisibility(INVISIBLE);
// mDecrementButton.setVisibility(INVISIBLE);
// mInputText.setVisibility(INVISIBLE);
}
/**
* Show the input controls by making them visible and animating the alpha
* property up/down arrows.
*
* @param animationDuration
* The duration of the animation.
*/
private void showInputControls(long animationDuration) {
// updateIncrementAndDecrementButtonsVisibilityState();
// mInputText.setVisibility(VISIBLE);
// mShowInputControlsAnimator.setDuration(animationDuration);
// mShowInputControlsAnimator.start();
}
/**
* Fade the selector wheel via an animation.
*
* @param animationDuration
* The duration of the animation.
*/
// mark this 1
private void fadeSelectorWheel(long animationDuration) {
// mInputText.setVisibility(VISIBLE);
// mDimSelectorWheelAnimator.setDuration(animationDuration);
// mDimSelectorWheelAnimator.start();
}
/**
* Updates the visibility state of the increment and decrement buttons.
*/
private void updateIncrementAndDecrementButtonsVisibilityState() {
if (mWrapSelectorWheel || mValue < mMaxValue) {
// mIncrementButton.setVisibility(VISIBLE);
} else {
// mIncrementButton.setVisibility(INVISIBLE);
}
if (mWrapSelectorWheel || mValue > mMinValue) {
// mDecrementButton.setVisibility(VISIBLE);
} else {
// mDecrementButton.setVisibility(INVISIBLE);
}
}
/**
* @return The wrapped index selectorIndex
value.
*/
private int getWrappedSelectorIndex(int selectorIndex) {
if (selectorIndex > mMaxValue) {
return mMinValue + (selectorIndex - mMaxValue)
% (mMaxValue - mMinValue) - 1;
} else if (selectorIndex < mMinValue) {
return mMaxValue - (mMinValue - selectorIndex)
% (mMaxValue - mMinValue) + 1;
}
return selectorIndex;
}
/**
* Increments the selectorIndices
whose string representations
* will be displayed in the selector.
*/
private void incrementSelectorIndices(int[] selectorIndices) {
for (int i = 0; i < selectorIndices.length - 1; i++) {
selectorIndices[i] = selectorIndices[i + 1];
}
int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) {
nextScrollSelectorIndex = mMinValue;
}
selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
}
/**
* Decrements the selectorIndices
whose string representations
* will be displayed in the selector.
*/
private void decrementSelectorIndices(int[] selectorIndices) {
for (int i = selectorIndices.length - 1; i > 0; i--) {
selectorIndices[i] = selectorIndices[i - 1];
}
int nextScrollSelectorIndex = selectorIndices[1] - 1;
if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) {
nextScrollSelectorIndex = mMaxValue;
}
selectorIndices[0] = nextScrollSelectorIndex;
ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
}
/**
* Ensures we have a cached string representation of the given
* selectorIndex
* to avoid multiple instantiations of the same string.
*/
private void ensureCachedScrollSelectorValue(int selectorIndex) {
SparseArrayvalue
.
*/
private int getSelectedPos(String value) {
if (mDisplayedValues == null) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
// Ignore as if it's not a number we don't care
}
} else {
for (int i = 0; i < mDisplayedValues.length; i++) {
// Don't force the user to type in jan when ja will do
value = value.toLowerCase();
if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
return mMinValue + i;
}
}
/*
* The user might have typed in a number into the month field i.e.
* 10 instead of OCT so support that too.
*/
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
// Ignore as if it's not a number we don't care
}
}
return mMinValue;
}
/**
* Posts an {@link SetSelectionCommand} from the given selectionStart
*
to
* selectionEnd
.
*/
private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
if (mSetSelectionCommand == null) {
mSetSelectionCommand = new SetSelectionCommand();
} else {
removeCallbacks(mSetSelectionCommand);
}
mSetSelectionCommand.mSelectionStart = selectionStart;
mSetSelectionCommand.mSelectionEnd = selectionEnd;
post(mSetSelectionCommand);
}
/**
* Posts an {@link AdjustScrollerCommand} within the given
* delayMillis
* .
*/
private void postAdjustScrollerCommand(int delayMillis) {
if (mAdjustScrollerCommand == null) {
mAdjustScrollerCommand = new AdjustScrollerCommand();
} else {
removeCallbacks(mAdjustScrollerCommand);
}
postDelayed(mAdjustScrollerCommand, delayMillis);
}
/**
* Filter for accepting only valid indices or prefixes of the string
* representation of valid indices.
*/
class InputTextFilter extends NumberKeyListener {
// XXX This doesn't allow for range limits when controlled by a
// soft input method!
public int getInputType() {
return InputType.TYPE_CLASS_TEXT;
}
@Override
protected char[] getAcceptedChars() {
return DIGIT_CHARACTERS;
}
@Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
if (mDisplayedValues == null) {
CharSequence filtered = super.filter(source, start, end, dest,
dstart, dend);
if (filtered == null) {
filtered = source.subSequence(start, end);
}
String result = String.valueOf(dest.subSequence(0, dstart))
+ filtered + dest.subSequence(dend, dest.length());
if ("".equals(result)) {
return result;
}
int val = getSelectedPos(result);
/*
* Ensure the user can't type in a value greater than the max
* allowed. We have to allow less than min as the user might
* want to delete some numbers and then type a new number.
*/
if (val > mMaxValue) {
return "";
} else {
return filtered;
}
} else {
CharSequence filtered = String.valueOf(source.subSequence(
start, end));
if (TextUtils.isEmpty(filtered)) {
return "";
}
String result = String.valueOf(dest.subSequence(0, dstart))
+ filtered + dest.subSequence(dend, dest.length());
String str = String.valueOf(result).toLowerCase();
for (String val : mDisplayedValues) {
String valLowerCase = val.toLowerCase();
if (valLowerCase.startsWith(str)) {
postSetSelectionCommand(result.length(), val.length());
return val.subSequence(dstart, val.length());
}
}
return "";
}
}
}
/**
* Command for setting the input text selection.
*/
class SetSelectionCommand implements Runnable {
private int mSelectionStart;
private int mSelectionEnd;
public void run() {
// mInputText.setSelection(mSelectionStart, mSelectionEnd);
}
}
/**
* Command for adjusting the scroller to show in its center the closest of
* the displayed items.
*/
class AdjustScrollerCommand implements Runnable {
public void run() {
mPreviousScrollerY = 0;
mPreviousScrollerX = 0;
if (mInitialScrollOffset == mCurrentScrollOffset) {
return;
}
if (mHorizontal) {
// adjust to the closest value
int deltaX = mInitialScrollOffset - mCurrentScrollOffset;
mAdjustScroller.startScroll(0, 0, deltaX, 0,
SELECTOR_ADJUSTMENT_DURATION_MILLIS);
} else {
// adjust to the closest value
int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
deltaY += (deltaY > 0) ? -mSelectorElementHeight
: mSelectorElementHeight;
}
mAdjustScroller.startScroll(0, 0, 0, deltaY,
SELECTOR_ADJUSTMENT_DURATION_MILLIS);
}
invalidate();
}
}
/**
* Command for changing the current value from a long press by one.
*/
class ChangeCurrentByOneFromLongPressCommand implements Runnable {
private boolean mIncrement;
private void setIncrement(boolean increment) {
mIncrement = increment;
}
public void run() {
changeCurrentByOne(mIncrement);
postDelayed(this, mLongPressUpdateInterval);
}
}
}