import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Align;
+import android.graphics.drawable.Drawable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
import android.text.method.NumberKeyListener;
import android.util.AttributeSet;
import android.util.SparseArray;
+import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.LayoutInflater.Filter;
-import android.view.animation.OvershootInterpolator;
+import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
/**
private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f;
/**
+ * The default unscaled height of the selection divider.
+ */
+ private final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2;
+
+ /**
* The numbers accepted by the input text's {@link Filter}
*/
private static final char[] DIGIT_CHARACTERS = new char[] {
private final boolean mFlingable;
/**
+ * Divider for showing item to be selected while scrolling
+ */
+ private final Drawable mSelectionDivider;
+
+ /**
+ * The height of the selection divider.
+ */
+ private final int mSelectionDividerHeight;
+
+ /**
* Reusable {@link Rect} instance.
*/
private final Rect mTempRect = new Rect();
private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
/**
+ * The duration of the animation for showing the input controls.
+ */
+ private final long mShowInputControlsAnimimationDuration;
+
+ /**
* Interface to listen for changes of the current value.
*/
public interface OnValueChangedListener {
R.styleable.NumberPicker, defStyle, 0);
mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0);
mFlingable = attributesArray.getBoolean(R.styleable.NumberPicker_flingable, true);
+ mSelectionDivider = attributesArray.getDrawable(R.styleable.NumberPicker_selectionDivider);
+ int defSelectionDividerHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT,
+ getResources().getDisplayMetrics());
+ mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
+ R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);
attributesArray.recycle();
+ mShowInputControlsAnimimationDuration = getResources().getInteger(
+ R.integer.config_longAnimTime);
+
// By default Linearlayout that we extend is not drawn. This is
// its draw() method is not called but dispatchDraw() is called
// directly (see ViewGroup.drawChild()). However, this class uses
mShowInputControlsAnimator = new AnimatorSet();
mShowInputControlsAnimator.playTogether(fadeScroller, showIncrementButton,
showDecrementButton);
- mShowInputControlsAnimator.setDuration(getResources().getInteger(
- R.integer.config_longAnimTime));
mShowInputControlsAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mCanceled = false;
// create the fling and adjust scrollers
mFlingScroller = new Scroller(getContext(), null, true);
- mAdjustScroller = new Scroller(getContext(), new OvershootInterpolator());
+ mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f));
updateInputTextView();
updateIncrementAndDecrementButtonsVisibilityState();
+
+ if (mFlingable) {
+ // Start with shown selector wheel and hidden controls. When made
+ // visible hide the selector and fade-in the controls to suggest
+ // fling interaction.
+ setDrawSelectorWheel(true);
+ hideInputControls();
+ }
}
@Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
- if (!hasWindowFocus) {
- removeAllCallbacks();
- }
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ // need to do this when we know our size
+ initializeScrollWheel();
}
@Override
if (!scrollersFinished) {
mFlingScroller.forceFinished(true);
mAdjustScroller.forceFinished(true);
- tryNotifyScrollListener(OnScrollListener.SCROLL_STATE_IDLE);
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
}
mBeginEditOnUpEvent = scrollersFinished;
mAdjustScrollerOnUpEvent = true;
int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
if (deltaDownY > mTouchSlop) {
mBeginEditOnUpEvent = false;
- tryNotifyScrollListener(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
setDrawSelectorWheel(true);
hideInputControls();
return true;
int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
if (deltaDownY > mTouchSlop) {
mBeginEditOnUpEvent = false;
- tryNotifyScrollListener(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
}
}
int deltaMoveY = (int) (currentMoveY - mLastMotionEventY);
case MotionEvent.ACTION_UP:
if (mBeginEditOnUpEvent) {
setDrawSelectorWheel(false);
- showInputControls();
+ showInputControls(mShowInputControlsAnimimationDuration);
mInputText.requestFocus();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
int initialVelocity = (int) velocityTracker.getYVelocity();
if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
fling(initialVelocity);
- tryNotifyScrollListener(OnScrollListener.SCROLL_STATE_FLING);
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
} else {
if (mAdjustScrollerOnUpEvent) {
if (mFlingScroller.isFinished() && mAdjustScroller.isFinished()) {
@Override
public void scrollBy(int x, int y) {
int[] selectorIndices = getSelectorIndices();
- if (mInitialScrollOffset == Integer.MIN_VALUE) {
- int totalTextHeight = selectorIndices.length * mTextSize;
- int totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
- int textGapCount = selectorIndices.length - 1;
- int selectorTextGapHeight = totalTextGapHeight / textGapCount;
- // compensate for integer division loss of the components used to
- // calculate the text gap
- int integerDivisionLoss = (mTextSize + mBottom - mTop) % textGapCount;
- mInitialScrollOffset = mCurrentScrollOffset = mTextSize - integerDivisionLoss / 2;
- mSelectorElementHeight = mTextSize + selectorTextGapHeight;
- }
-
if (!mWrapSelectorWheel && y > 0
&& selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
mCurrentScrollOffset = mInitialScrollOffset;
}
mFormatter = formatter;
resetSelectorWheelIndices();
+ updateInputTextView();
}
/**
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ // make sure we show the controls only the very
+ // first time the user sees this widget
+ if (mFlingable) {
+ // animate a bit slower the very first time
+ showInputControls(mShowInputControlsAnimimationDuration * 2);
+ }
+ }
+
+ @Override
protected void onDetachedFromWindow() {
removeAllCallbacks();
}
// 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 which are currently showing in. Therefore, we draw our
- // children
- // after we have completed drawing ourselves.
-
+ // children after we have completed drawing ourselves.
super.draw(canvas);
// Draw our children if we are not showing the selector wheel of fading
float x = (mRight - mLeft) / 2;
float y = mCurrentScrollOffset;
+ // draw the selector wheel
int[] selectorIndices = getSelectorIndices();
for (int i = 0; i < selectorIndices.length; i++) {
int selectorIndex = selectorIndices[i];
canvas.drawText(scrollSelectorValue, x, y, mSelectorPaint);
y += mSelectorElementHeight;
}
+
+ // draw the selection dividers (only if scrolling and drawable specified)
+ if (mSelectionDivider != null) {
+ mSelectionDivider.setAlpha(mSelectorPaint.getAlpha());
+ // draw the top divider
+ int topOfTopDivider =
+ (getHeight() - mSelectorElementHeight - mSelectionDividerHeight) / 2;
+ int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
+ mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider);
+ mSelectionDivider.draw(canvas);
+
+ // draw the bottom divider
+ int topOfBottomDivider = topOfTopDivider + mSelectorElementHeight;
+ int bottomOfBottomDivider = bottomOfTopDivider + mSelectorElementHeight;
+ mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider);
+ mSelectionDivider.draw(canvas);
+ }
}
/**
setVerticalFadingEdgeEnabled(drawSelectorWheel);
}
+ private void initializeScrollWheel() {
+ if (mInitialScrollOffset != Integer.MIN_VALUE) {
+ return;
+
+ }
+ int[] selectorIndices = getSelectorIndices();
+ int totalTextHeight = selectorIndices.length * mTextSize;
+ int totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
+ int textGapCount = selectorIndices.length - 1;
+ int selectorTextGapHeight = totalTextGapHeight / textGapCount;
+ // compensate for integer division loss of the components used to
+ // calculate the text gap
+ int integerDivisionLoss = (mTextSize + mBottom - mTop) % textGapCount;
+ mInitialScrollOffset = mCurrentScrollOffset = mTextSize - integerDivisionLoss / 2;
+ mSelectorElementHeight = mTextSize + selectorTextGapHeight;
+ updateInputTextView();
+ }
+
/**
* Callback invoked upon completion of a given <code>scroller</code>.
*/
private void onScrollerFinished(Scroller scroller) {
if (scroller == mFlingScroller) {
postAdjustScrollerCommand(0);
- tryNotifyScrollListener(OnScrollListener.SCROLL_STATE_IDLE);
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
} else {
updateInputTextView();
- showInputControls();
+ showInputControls(mShowInputControlsAnimimationDuration);
}
}
/**
- * Notifies the scroll listener for the given <code>scrollState</code>
- * if the scroll state differs from the current scroll state.
+ * Handles transition to a given <code>scrollState</code>
*/
- private void tryNotifyScrollListener(int scrollState) {
- if (mOnScrollListener != null && mScrollState != scrollState) {
- mScrollState = scrollState;
+ private void onScrollStateChange(int scrollState) {
+ if (mScrollState == scrollState) {
+ return;
+ }
+ mScrollState = scrollState;
+ if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChange(this, scrollState);
}
}
/**
* 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() {
+ private void showInputControls(long animationDuration) {
updateIncrementAndDecrementButtonsVisibilityState();
mInputText.setVisibility(VISIBLE);
+ mShowInputControlsAnimator.setDuration(animationDuration);
mShowInputControlsAnimator.start();
}
/**
* Updates the view of this NumberPicker. If displayValues were specified in
- * {@link #setRange}, the string corresponding to the index specified by the
- * current value will be returned. Otherwise, the formatter specified in
- * {@link #setFormatter} will be used to format the number.
+ * the string corresponding to the index specified by the current value will
+ * be returned. Otherwise, the formatter specified in {@link #setFormatter}
+ * will be used to format the number.
*/
private void updateInputTextView() {
/*
mPreviousScrollerY = 0;
if (mInitialScrollOffset == mCurrentScrollOffset) {
updateInputTextView();
- showInputControls();
+ showInputControls(mShowInputControlsAnimimationDuration);
return;
}
// adjust to the closest value
if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
}
- float delayCoef = (float) Math.abs(deltaY) / (float) mTextSize;
- int duration = (int) (delayCoef * SELECTOR_ADJUSTMENT_DURATION_MILLIS);
- mAdjustScroller.startScroll(0, 0, 0, deltaY, duration);
+ mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
invalidate();
}
}