Merge "Revamping of the NumberPicker widget for improved usablility."

This commit is contained in:
Svetoslav Ganov
2011-09-28 19:43:55 -07:00
committed by Android (Google) Code Review
3 changed files with 282 additions and 141 deletions

View File

@@ -16,13 +16,10 @@
package android.widget; package android.widget;
import com.android.internal.R;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet; import android.animation.AnimatorSet;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.Widget; import android.annotation.Widget;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
@@ -30,8 +27,8 @@ import android.content.res.TypedArray;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Align; import android.graphics.Paint.Align;
import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.InputFilter; import android.text.InputFilter;
import android.text.InputType; import android.text.InputType;
@@ -43,16 +40,18 @@ import android.util.SparseArray;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.LayoutInflater.Filter;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.VelocityTracker; import android.view.VelocityTracker;
import android.view.View; import android.view.View;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
import android.view.LayoutInflater.Filter;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
import android.view.animation.DecelerateInterpolator; import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
/** /**
* A widget that enables the user to select a number form a predefined range. * 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 * The widget presents an input filed and up and down buttons for selecting the
@@ -90,6 +89,12 @@ public class NumberPicker extends LinearLayout {
*/ */
private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800; 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 * The the delay for showing the input controls after a single tap on the
* input text. * input text.
@@ -97,16 +102,6 @@ public class NumberPicker extends LinearLayout {
private static final int SHOW_INPUT_CONTROLS_DELAY_MILLIS = ViewConfiguration private static final int SHOW_INPUT_CONTROLS_DELAY_MILLIS = ViewConfiguration
.getDoubleTapTimeout(); .getDoubleTapTimeout();
/**
* The update step for incrementing the current value.
*/
private static final int UPDATE_STEP_INCREMENT = 1;
/**
* The update step for decrementing the current value.
*/
private static final int UPDATE_STEP_DECREMENT = -1;
/** /**
* The strength of fading in the top and bottom while drawing the selector. * The strength of fading in the top and bottom while drawing the selector.
*/ */
@@ -115,7 +110,52 @@ public class NumberPicker extends LinearLayout {
/** /**
* The default unscaled height of the selection divider. * The default unscaled height of the selection divider.
*/ */
private final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2; 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} * The numbers accepted by the input text's {@link Filter}
@@ -167,6 +207,11 @@ public class NumberPicker extends LinearLayout {
*/ */
private final int mTextSize; private final int mTextSize;
/**
* The height of the gap between text elements if the selector wheel.
*/
private int mSelectorTextGapHeight;
/** /**
* The values to be displayed instead the indices. * The values to be displayed instead the indices.
*/ */
@@ -223,7 +268,7 @@ public class NumberPicker extends LinearLayout {
/** /**
* The {@link Paint} for drawing the selector. * The {@link Paint} for drawing the selector.
*/ */
private final Paint mSelectorPaint; private final Paint mSelectorWheelPaint;
/** /**
* The height of a selector element (text + gap). * The height of a selector element (text + gap).
@@ -266,16 +311,21 @@ public class NumberPicker extends LinearLayout {
private AdjustScrollerCommand mAdjustScrollerCommand; private AdjustScrollerCommand mAdjustScrollerCommand;
/** /**
* Handle to the reusable command for updating the current value from long * Handle to the reusable command for changing the current value from long
* press. * press by one.
*/ */
private UpdateValueFromLongPressCommand mUpdateFromLongPressCommand; private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand;
/** /**
* {@link Animator} for showing the up/down arrows. * {@link Animator} for showing the up/down arrows.
*/ */
private final AnimatorSet mShowInputControlsAnimator; private final AnimatorSet mShowInputControlsAnimator;
/**
* {@link Animator} for dimming the selector wheel.
*/
private final Animator mDimSelectorWheelAnimator;
/** /**
* The Y position of the last down event. * The Y position of the last down event.
*/ */
@@ -297,9 +347,9 @@ public class NumberPicker extends LinearLayout {
private boolean mAdjustScrollerOnUpEvent; private boolean mAdjustScrollerOnUpEvent;
/** /**
* Flag if to draw the selector wheel. * The state of the selector wheel.
*/ */
private boolean mDrawSelectorWheel; private int mSelectorWheelState;
/** /**
* Determines speed during touch scrolling. * Determines speed during touch scrolling.
@@ -361,6 +411,11 @@ public class NumberPicker extends LinearLayout {
*/ */
private final long mShowInputControlsAnimimationDuration; private final long mShowInputControlsAnimimationDuration;
/**
* Flag whether the scoll wheel and the fading edges have been initialized.
*/
private boolean mScrollWheelAndFadingEdgesInitialized;
/** /**
* Interface to listen for changes of the current value. * Interface to listen for changes of the current value.
*/ */
@@ -473,7 +528,7 @@ public class NumberPicker extends LinearLayout {
// the fading edge effect implemented by View and we need our // the fading edge effect implemented by View and we need our
// draw() method to be called. Therefore, we declare we will draw. // draw() method to be called. Therefore, we declare we will draw.
setWillNotDraw(false); setWillNotDraw(false);
setDrawScrollWheel(false); setSelectorWheelState(SELECTOR_WHEEL_STATE_NONE);
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE); Context.LAYOUT_INFLATER_SERVICE);
@@ -483,9 +538,9 @@ public class NumberPicker extends LinearLayout {
public void onClick(View v) { public void onClick(View v) {
mInputText.clearFocus(); mInputText.clearFocus();
if (v.getId() == R.id.increment) { if (v.getId() == R.id.increment) {
changeCurrent(mValue + 1); changeCurrentByOne(true);
} else { } else {
changeCurrent(mValue - 1); changeCurrentByOne(false);
} }
} }
}; };
@@ -494,9 +549,9 @@ public class NumberPicker extends LinearLayout {
public boolean onLongClick(View v) { public boolean onLongClick(View v) {
mInputText.clearFocus(); mInputText.clearFocus();
if (v.getId() == R.id.increment) { if (v.getId() == R.id.increment) {
postUpdateValueFromLongPress(UPDATE_STEP_INCREMENT); postChangeCurrentByOneFromLongPress(true);
} else { } else {
postUpdateValueFromLongPress(UPDATE_STEP_DECREMENT); postChangeCurrentByOneFromLongPress(false);
} }
return true; return true;
} }
@@ -516,10 +571,17 @@ public class NumberPicker extends LinearLayout {
mInputText = (EditText) findViewById(R.id.numberpicker_input); mInputText = (EditText) findViewById(R.id.numberpicker_input);
mInputText.setOnFocusChangeListener(new OnFocusChangeListener() { mInputText.setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) { public void onFocusChange(View v, boolean hasFocus) {
InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
if (hasFocus) { if (hasFocus) {
mInputText.selectAll(); mInputText.selectAll();
if (inputMethodManager != null) {
inputMethodManager.showSoftInput(mInputText, 0);
}
} else { } else {
mInputText.setSelection(0, 0); mInputText.setSelection(0, 0);
if (inputMethodManager != null) {
inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
}
validateInputTextView(v); validateInputTextView(v);
} }
} }
@@ -548,16 +610,17 @@ public class NumberPicker extends LinearLayout {
ColorStateList colors = mInputText.getTextColors(); ColorStateList colors = mInputText.getTextColors();
int color = colors.getColorForState(ENABLED_STATE_SET, Color.WHITE); int color = colors.getColorForState(ENABLED_STATE_SET, Color.WHITE);
paint.setColor(color); paint.setColor(color);
mSelectorPaint = paint; mSelectorWheelPaint = paint;
// create the animator for showing the input controls // create the animator for showing the input controls
final ValueAnimator fadeScroller = ObjectAnimator.ofInt(this, "selectorPaintAlpha", 255, 0); mDimSelectorWheelAnimator = ObjectAnimator.ofInt(this, PROPERTY_SELECTOR_PAINT_ALPHA,
SELECTOR_WHEEL_BRIGHT_ALPHA, SELECTOR_WHEEL_DIM_ALPHA);
final ObjectAnimator showIncrementButton = ObjectAnimator.ofFloat(mIncrementButton, final ObjectAnimator showIncrementButton = ObjectAnimator.ofFloat(mIncrementButton,
"alpha", 0, 1); PROPERTY_BUTTON_ALPHA, BUTTON_ALPHA_TRANSPARENT, BUTTON_ALPHA_OPAQUE);
final ObjectAnimator showDecrementButton = ObjectAnimator.ofFloat(mDecrementButton, final ObjectAnimator showDecrementButton = ObjectAnimator.ofFloat(mDecrementButton,
"alpha", 0, 1); PROPERTY_BUTTON_ALPHA, BUTTON_ALPHA_TRANSPARENT, BUTTON_ALPHA_OPAQUE);
mShowInputControlsAnimator = new AnimatorSet(); mShowInputControlsAnimator = new AnimatorSet();
mShowInputControlsAnimator.playTogether(fadeScroller, showIncrementButton, mShowInputControlsAnimator.playTogether(mDimSelectorWheelAnimator, showIncrementButton,
showDecrementButton); showDecrementButton);
mShowInputControlsAnimator.addListener(new AnimatorListenerAdapter() { mShowInputControlsAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mCanceled = false; private boolean mCanceled = false;
@@ -566,11 +629,9 @@ public class NumberPicker extends LinearLayout {
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
if (!mCanceled) { if (!mCanceled) {
// if canceled => we still want the wheel drawn // if canceled => we still want the wheel drawn
setDrawScrollWheel(false); setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL);
} }
mCanceled = false; mCanceled = false;
mSelectorPaint.setAlpha(255);
invalidate();
} }
@Override @Override
@@ -588,20 +649,28 @@ public class NumberPicker extends LinearLayout {
updateInputTextView(); updateInputTextView();
updateIncrementAndDecrementButtonsVisibilityState(); updateIncrementAndDecrementButtonsVisibilityState();
if (mFlingable && !isInEditMode()) { if (mFlingable) {
// Start with shown selector wheel and hidden controls. When made if (isInEditMode()) {
// visible hide the selector and fade-in the controls to suggest setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL);
// fling interaction. } else {
setDrawScrollWheel(true); // Start with shown selector wheel and hidden controls. When made
hideInputControls(); // visible hide the selector and fade-in the controls to suggest
// fling interaction.
setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
hideInputControls();
}
} }
} }
@Override @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom); super.onLayout(changed, left, top, right, bottom);
// need to do this when we know our size if (!mScrollWheelAndFadingEdgesInitialized) {
initializeScrollWheel(); mScrollWheelAndFadingEdgesInitialized = true;
// need to do all this when we know our size
initializeSelectorWheel();
initializeFadingEdges();
}
} }
@Override @Override
@@ -614,9 +683,10 @@ public class NumberPicker extends LinearLayout {
mLastMotionEventY = mLastDownEventY = event.getY(); mLastMotionEventY = mLastDownEventY = event.getY();
removeAllCallbacks(); removeAllCallbacks();
mShowInputControlsAnimator.cancel(); mShowInputControlsAnimator.cancel();
mDimSelectorWheelAnimator.cancel();
mBeginEditOnUpEvent = false; mBeginEditOnUpEvent = false;
mAdjustScrollerOnUpEvent = true; mAdjustScrollerOnUpEvent = true;
if (mDrawSelectorWheel) { if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
boolean scrollersFinished = mFlingScroller.isFinished() boolean scrollersFinished = mFlingScroller.isFinished()
&& mAdjustScroller.isFinished(); && mAdjustScroller.isFinished();
if (!scrollersFinished) { if (!scrollersFinished) {
@@ -635,7 +705,7 @@ public class NumberPicker extends LinearLayout {
|| (!mDecrementButton.isShown() || (!mDecrementButton.isShown()
&& isEventInViewHitRect(event, mDecrementButton))) { && isEventInViewHitRect(event, mDecrementButton))) {
mAdjustScrollerOnUpEvent = false; mAdjustScrollerOnUpEvent = false;
setDrawScrollWheel(true); setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
hideInputControls(); hideInputControls();
return true; return true;
} }
@@ -646,7 +716,7 @@ public class NumberPicker extends LinearLayout {
if (deltaDownY > mTouchSlop) { if (deltaDownY > mTouchSlop) {
mBeginEditOnUpEvent = false; mBeginEditOnUpEvent = false;
onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
setDrawScrollWheel(true); setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
hideInputControls(); hideInputControls();
return true; return true;
} }
@@ -683,12 +753,9 @@ public class NumberPicker extends LinearLayout {
break; break;
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
if (mBeginEditOnUpEvent) { if (mBeginEditOnUpEvent) {
setDrawScrollWheel(false); setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL);
showInputControls(mShowInputControlsAnimimationDuration); showInputControls(mShowInputControlsAnimimationDuration);
mInputText.requestFocus(); mInputText.requestFocus();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mInputText, 0);
return true; return true;
} }
VelocityTracker velocityTracker = mVelocityTracker; VelocityTracker velocityTracker = mVelocityTracker;
@@ -715,10 +782,18 @@ public class NumberPicker extends LinearLayout {
@Override @Override
public boolean dispatchTouchEvent(MotionEvent event) { public boolean dispatchTouchEvent(MotionEvent event) {
int action = event.getActionMasked(); final int action = event.getActionMasked();
if ((action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) switch (action) {
&& !isEventInViewHitRect(event, mInputText)) { case MotionEvent.ACTION_MOVE:
removeAllCallbacks(); if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
removeAllCallbacks();
forceCompleteChangeCurrentByOneViaScroll();
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
removeAllCallbacks();
break;
} }
return super.dispatchTouchEvent(event); return super.dispatchTouchEvent(event);
} }
@@ -743,7 +818,7 @@ public class NumberPicker extends LinearLayout {
@Override @Override
public void computeScroll() { public void computeScroll() {
if (!mDrawSelectorWheel) { if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
return; return;
} }
Scroller scroller = mFlingScroller; Scroller scroller = mFlingScroller;
@@ -777,7 +852,10 @@ public class NumberPicker extends LinearLayout {
@Override @Override
public void scrollBy(int x, int y) { public void scrollBy(int x, int y) {
int[] selectorIndices = getSelectorIndices(); if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
return;
}
int[] selectorIndices = mSelectorIndices;
if (!mWrapSelectorWheel && y > 0 if (!mWrapSelectorWheel && y > 0
&& selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) { && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
mCurrentScrollOffset = mInitialScrollOffset; mCurrentScrollOffset = mInitialScrollOffset;
@@ -789,19 +867,19 @@ public class NumberPicker extends LinearLayout {
return; return;
} }
mCurrentScrollOffset += y; mCurrentScrollOffset += y;
while (mCurrentScrollOffset - mInitialScrollOffset >= mSelectorElementHeight) { while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
mCurrentScrollOffset -= mSelectorElementHeight; mCurrentScrollOffset -= mSelectorElementHeight;
decrementSelectorIndices(selectorIndices); decrementSelectorIndices(selectorIndices);
changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]); changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]);
if (selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) { if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
mCurrentScrollOffset = mInitialScrollOffset; mCurrentScrollOffset = mInitialScrollOffset;
} }
} }
while (mCurrentScrollOffset - mInitialScrollOffset <= -mSelectorElementHeight) { while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
mCurrentScrollOffset += mSelectorElementHeight; mCurrentScrollOffset += mSelectorElementHeight;
incrementScrollSelectorIndices(selectorIndices); incrementSelectorIndices(selectorIndices);
changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]); changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]);
if (selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) { if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
mCurrentScrollOffset = mInitialScrollOffset; mCurrentScrollOffset = mInitialScrollOffset;
} }
} }
@@ -847,7 +925,7 @@ public class NumberPicker extends LinearLayout {
return; return;
} }
mFormatter = formatter; mFormatter = formatter;
resetSelectorWheelIndices(); initializeSelectorWheelIndices();
updateInputTextView(); updateInputTextView();
} }
@@ -890,8 +968,10 @@ public class NumberPicker extends LinearLayout {
value = mWrapSelectorWheel ? mMinValue : mMaxValue; value = mWrapSelectorWheel ? mMinValue : mMaxValue;
} }
mValue = value; mValue = value;
initializeSelectorWheelIndices();
updateInputTextView(); updateInputTextView();
updateIncrementAndDecrementButtonsVisibilityState(); updateIncrementAndDecrementButtonsVisibilityState();
invalidate();
} }
/** /**
@@ -981,7 +1061,7 @@ public class NumberPicker extends LinearLayout {
} }
boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
setWrapSelectorWheel(wrapSelectorWheel); setWrapSelectorWheel(wrapSelectorWheel);
resetSelectorWheelIndices(); initializeSelectorWheelIndices();
updateInputTextView(); updateInputTextView();
} }
@@ -1012,7 +1092,7 @@ public class NumberPicker extends LinearLayout {
} }
boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
setWrapSelectorWheel(wrapSelectorWheel); setWrapSelectorWheel(wrapSelectorWheel);
resetSelectorWheelIndices(); initializeSelectorWheelIndices();
updateInputTextView(); updateInputTextView();
} }
@@ -1043,7 +1123,7 @@ public class NumberPicker extends LinearLayout {
mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER); mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
} }
updateInputTextView(); updateInputTextView();
resetSelectorWheelIndices(); initializeSelectorWheelIndices();
} }
@Override @Override
@@ -1080,18 +1160,19 @@ public class NumberPicker extends LinearLayout {
@Override @Override
public void draw(Canvas canvas) { public void draw(Canvas canvas) {
// Dispatch draw to our children only if we are not currently running // Dispatch draw to our children only if we are not currently running
// the animation for simultaneously fading out the scroll wheel and // the animation for simultaneously dimming the scroll wheel and
// showing in the buttons. This class takes advantage of the View // showing in the buttons. This class takes advantage of the View
// implementation of fading edges effect to draw the selector wheel. // implementation of fading edges effect to draw the selector wheel.
// However, in View.draw(), the fading is applied after all the children // 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 // 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 // buttons. Therefore, we draw our children after we have completed
// children after we have completed drawing ourselves. // drawing ourselves.
super.draw(canvas); super.draw(canvas);
// Draw our children if we are not showing the selector wheel of fading // Draw our children if we are not showing the selector wheel of fading
// it out // it out
if (mShowInputControlsAnimator.isRunning() || !mDrawSelectorWheel) { if (mShowInputControlsAnimator.isRunning()
|| mSelectorWheelState != SELECTOR_WHEEL_STATE_LARGE) {
long drawTime = getDrawingTime(); long drawTime = getDrawingTime();
for (int i = 0, count = getChildCount(); i < count; i++) { for (int i = 0, count = getChildCount(); i < count; i++) {
View child = getChildAt(i); View child = getChildAt(i);
@@ -1105,25 +1186,32 @@ public class NumberPicker extends LinearLayout {
@Override @Override
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
// we only draw the selector wheel if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
if (!mDrawSelectorWheel) {
return; return;
} }
float x = (mRight - mLeft) / 2; float x = (mRight - mLeft) / 2;
float y = mCurrentScrollOffset; float y = mCurrentScrollOffset;
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 // draw the selector wheel
int[] selectorIndices = getSelectorIndices(); int[] selectorIndices = mSelectorIndices;
for (int i = 0; i < selectorIndices.length; i++) { for (int i = 0; i < selectorIndices.length; i++) {
int selectorIndex = selectorIndices[i]; int selectorIndex = selectorIndices[i];
String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex); String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);
canvas.drawText(scrollSelectorValue, x, y, mSelectorPaint); canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);
y += mSelectorElementHeight; y += mSelectorElementHeight;
} }
// draw the selection dividers (only if scrolling and drawable specified) // draw the selection dividers (only if scrolling and drawable specified)
if (mSelectionDivider != null) { if (mSelectionDivider != null) {
mSelectionDivider.setAlpha(mSelectorPaint.getAlpha());
// draw the top divider // draw the top divider
int topOfTopDivider = int topOfTopDivider =
(getHeight() - mSelectorElementHeight - mSelectionDividerHeight) / 2; (getHeight() - mSelectorElementHeight - mSelectionDividerHeight) / 2;
@@ -1137,6 +1225,8 @@ public class NumberPicker extends LinearLayout {
mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider); mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider);
mSelectionDivider.draw(canvas); mSelectionDivider.draw(canvas);
} }
canvas.restoreToCount(restoreCount);
} }
@Override @Override
@@ -1149,11 +1239,17 @@ public class NumberPicker extends LinearLayout {
* Resets the selector indices and clear the cached * Resets the selector indices and clear the cached
* string representation of these indices. * string representation of these indices.
*/ */
private void resetSelectorWheelIndices() { private void initializeSelectorWheelIndices() {
mSelectorIndexToStringCache.clear(); mSelectorIndexToStringCache.clear();
int[] selectorIdices = getSelectorIndices(); int[] selectorIdices = mSelectorIndices;
for (int i = 0; i < selectorIdices.length; i++) { int current = getValue();
selectorIdices[i] = Integer.MIN_VALUE; for (int i = 0; i < mSelectorIndices.length; i++) {
int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
if (mWrapSelectorWheel) {
selectorIndex = getWrappedSelectorIndex(selectorIndex);
}
mSelectorIndices[i] = selectorIndex;
ensureCachedScrollSelectorValue(mSelectorIndices[i]);
} }
} }
@@ -1178,17 +1274,60 @@ public class NumberPicker extends LinearLayout {
notifyChange(previous, 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) {
mDimSelectorWheelAnimator.cancel();
mInputText.setVisibility(View.INVISIBLE);
mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
mPreviousScrollerY = 0;
forceCompleteChangeCurrentByOneViaScroll();
if (increment) {
mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight,
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()) {
final int yBeforeAbort = scroller.getCurrY();
scroller.abortAnimation();
final int yDelta = scroller.getCurrY() - yBeforeAbort;
scrollBy(0, yDelta);
}
}
/** /**
* Sets the <code>alpha</code> of the {@link Paint} for drawing the selector * Sets the <code>alpha</code> of the {@link Paint} for drawing the selector
* wheel. * wheel.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
// Called by ShowInputControlsAnimator via reflection // Called via reflection
private void setSelectorPaintAlpha(int alpha) { private void setSelectorPaintAlpha(int alpha) {
mSelectorPaint.setAlpha(alpha); mSelectorWheelPaint.setAlpha(alpha);
if (mDrawSelectorWheel) { invalidate();
invalidate();
}
} }
/** /**
@@ -1200,14 +1339,15 @@ public class NumberPicker extends LinearLayout {
} }
/** /**
* Sets if to <code>drawSelectionWheel</code>. * Sets the <code>selectorWheelState</code>.
*/ */
private void setDrawScrollWheel(boolean drawSelectorWheel) { private void setSelectorWheelState(int selectorWheelState) {
mDrawSelectorWheel = drawSelectorWheel; mSelectorWheelState = selectorWheelState;
// do not fade if the selector wheel not shown if (selectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
setVerticalFadingEdgeEnabled(drawSelectorWheel); mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
}
if (mFlingable && mDrawSelectorWheel if (mFlingable && selectorWheelState == SELECTOR_WHEEL_STATE_LARGE
&& AccessibilityManager.getInstance(mContext).isEnabled()) { && AccessibilityManager.getInstance(mContext).isEnabled()) {
AccessibilityManager.getInstance(mContext).interrupt(); AccessibilityManager.getInstance(mContext).interrupt();
String text = mContext.getString(R.string.number_picker_increment_scroll_action); String text = mContext.getString(R.string.number_picker_increment_scroll_action);
@@ -1217,16 +1357,14 @@ public class NumberPicker extends LinearLayout {
} }
} }
private void initializeScrollWheel() { private void initializeSelectorWheel() {
if (mInitialScrollOffset != Integer.MIN_VALUE) { initializeSelectorWheelIndices();
return; int[] selectorIndices = mSelectorIndices;
}
int[] selectorIndices = getSelectorIndices();
int totalTextHeight = selectorIndices.length * mTextSize; int totalTextHeight = selectorIndices.length * mTextSize;
float totalTextGapHeight = (mBottom - mTop) - totalTextHeight; float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
float textGapCount = selectorIndices.length - 1; float textGapCount = selectorIndices.length - 1;
int selectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f); mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
mSelectorElementHeight = mTextSize + selectorTextGapHeight; mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
// Ensure that the middle item is positioned the same as the text in mInputText // Ensure that the middle item is positioned the same as the text in mInputText
int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop(); int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop();
mInitialScrollOffset = editTextTextPosition - mInitialScrollOffset = editTextTextPosition -
@@ -1235,13 +1373,23 @@ public class NumberPicker extends LinearLayout {
updateInputTextView(); updateInputTextView();
} }
private void initializeFadingEdges() {
setVerticalFadingEdgeEnabled(true);
setFadingEdgeLength((mBottom - mTop - mTextSize) / 2);
}
/** /**
* Callback invoked upon completion of a given <code>scroller</code>. * Callback invoked upon completion of a given <code>scroller</code>.
*/ */
private void onScrollerFinished(Scroller scroller) { private void onScrollerFinished(Scroller scroller) {
if (scroller == mFlingScroller) { if (scroller == mFlingScroller) {
postAdjustScrollerCommand(0); if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); postAdjustScrollerCommand(0);
onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
} else {
updateInputTextView();
fadeSelectorWheel(mShowInputControlsAnimimationDuration);
}
} else { } else {
updateInputTextView(); updateInputTextView();
showInputControls(mShowInputControlsAnimimationDuration); showInputControls(mShowInputControlsAnimimationDuration);
@@ -1311,6 +1459,17 @@ public class NumberPicker extends LinearLayout {
mShowInputControlsAnimator.start(); mShowInputControlsAnimator.start();
} }
/**
* Fade the selector wheel via an animation.
*
* @param animationDuration The duration of the animation.
*/
private void fadeSelectorWheel(long animationDuration) {
mInputText.setVisibility(VISIBLE);
mDimSelectorWheelAnimator.setDuration(animationDuration);
mDimSelectorWheelAnimator.start();
}
/** /**
* Updates the visibility state of the increment and decrement buttons. * Updates the visibility state of the increment and decrement buttons.
*/ */
@@ -1327,25 +1486,6 @@ public class NumberPicker extends LinearLayout {
} }
} }
/**
* @return The selector indices array with proper values with the current as
* the middle one.
*/
private int[] getSelectorIndices() {
int current = getValue();
if (mSelectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] != current) {
for (int i = 0; i < mSelectorIndices.length; i++) {
int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
if (mWrapSelectorWheel) {
selectorIndex = getWrappedSelectorIndex(selectorIndex);
}
mSelectorIndices[i] = selectorIndex;
ensureCachedScrollSelectorValue(mSelectorIndices[i]);
}
}
return mSelectorIndices;
}
/** /**
* @return The wrapped index <code>selectorIndex</code> value. * @return The wrapped index <code>selectorIndex</code> value.
*/ */
@@ -1362,7 +1502,7 @@ public class NumberPicker extends LinearLayout {
* Increments the <code>selectorIndices</code> whose string representations * Increments the <code>selectorIndices</code> whose string representations
* will be displayed in the selector. * will be displayed in the selector.
*/ */
private void incrementScrollSelectorIndices(int[] selectorIndices) { private void incrementSelectorIndices(int[] selectorIndices) {
for (int i = 0; i < selectorIndices.length - 1; i++) { for (int i = 0; i < selectorIndices.length - 1; i++) {
selectorIndices[i] = selectorIndices[i + 1]; selectorIndices[i] = selectorIndices[i + 1];
} }
@@ -1467,25 +1607,26 @@ public class NumberPicker extends LinearLayout {
} }
/** /**
* Posts a command for updating the current value every <code>updateMillis * Posts a command for changing the current value by one.
* </code>. *
* @param increment Whether to increment or decrement the value.
*/ */
private void postUpdateValueFromLongPress(int updateMillis) { private void postChangeCurrentByOneFromLongPress(boolean increment) {
mInputText.clearFocus(); mInputText.clearFocus();
removeAllCallbacks(); removeAllCallbacks();
if (mUpdateFromLongPressCommand == null) { if (mChangeCurrentByOneFromLongPressCommand == null) {
mUpdateFromLongPressCommand = new UpdateValueFromLongPressCommand(); mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
} }
mUpdateFromLongPressCommand.setUpdateStep(updateMillis); mChangeCurrentByOneFromLongPressCommand.setIncrement(increment);
post(mUpdateFromLongPressCommand); post(mChangeCurrentByOneFromLongPressCommand);
} }
/** /**
* Removes all pending callback from the message queue. * Removes all pending callback from the message queue.
*/ */
private void removeAllCallbacks() { private void removeAllCallbacks() {
if (mUpdateFromLongPressCommand != null) { if (mChangeCurrentByOneFromLongPressCommand != null) {
removeCallbacks(mUpdateFromLongPressCommand); removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
} }
if (mAdjustScrollerCommand != null) { if (mAdjustScrollerCommand != null) {
removeCallbacks(mAdjustScrollerCommand); removeCallbacks(mAdjustScrollerCommand);
@@ -1658,17 +1799,17 @@ public class NumberPicker extends LinearLayout {
} }
/** /**
* Command for updating the current value from a long press. * Command for changing the current value from a long press by one.
*/ */
class UpdateValueFromLongPressCommand implements Runnable { class ChangeCurrentByOneFromLongPressCommand implements Runnable {
private int mUpdateStep = 0; private boolean mIncrement;
private void setUpdateStep(int updateStep) { private void setIncrement(boolean increment) {
mUpdateStep = updateStep; mIncrement = increment;
} }
public void run() { public void run() {
changeCurrent(mValue + mUpdateStep); changeCurrentByOne(mIncrement);
postDelayed(this, mLongPressUpdateInterval); postDelayed(this, mLongPressUpdateInterval);
} }
} }

View File

@@ -23,8 +23,6 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="?android:attr/numberPickerUpButtonStyle" style="?android:attr/numberPickerUpButtonStyle"
android:paddingTop="22dip"
android:paddingBottom="22dip"
android:contentDescription="@string/number_picker_increment_button" /> android:contentDescription="@string/number_picker_increment_button" />
<EditText android:id="@+id/numberpicker_input" <EditText android:id="@+id/numberpicker_input"
@@ -36,8 +34,6 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="?android:attr/numberPickerDownButtonStyle" style="?android:attr/numberPickerDownButtonStyle"
android:paddingTop="22dip"
android:paddingBottom="22dip"
android:contentDescription="@string/number_picker_decrement_button" /> android:contentDescription="@string/number_picker_decrement_button" />
</merge> </merge>

View File

@@ -524,10 +524,14 @@ please see styles_device_defaults.xml.
<style name="Widget.ImageButton.NumberPickerUpButton"> <style name="Widget.ImageButton.NumberPickerUpButton">
<item name="android:background">@android:drawable/numberpicker_up_btn</item> <item name="android:background">@android:drawable/numberpicker_up_btn</item>
<item name="android:paddingTop">22dip</item>
<item name="android:paddingBottom">22dip</item>
</style> </style>
<style name="Widget.ImageButton.NumberPickerDownButton"> <style name="Widget.ImageButton.NumberPickerDownButton">
<item name="android:background">@android:drawable/numberpicker_down_btn</item> <item name="android:background">@android:drawable/numberpicker_down_btn</item>
<item name="android:paddingTop">22dip</item>
<item name="android:paddingBottom">22dip</item>
</style> </style>
<style name="Widget.EditText.NumberPickerInputText"> <style name="Widget.EditText.NumberPickerInputText">
@@ -1651,15 +1655,15 @@ please see styles_device_defaults.xml.
<style name="Widget.Holo.ImageButton.NumberPickerUpButton"> <style name="Widget.Holo.ImageButton.NumberPickerUpButton">
<item name="android:background">@null</item> <item name="android:background">@null</item>
<item name="android:src">@android:drawable/numberpicker_up_btn_holo_dark</item> <item name="android:src">@android:drawable/numberpicker_up_btn_holo_dark</item>
<item name="android:paddingTop">26dip</item> <item name="android:paddingTop">16dip</item>
<item name="android:paddingBottom">26dip</item> <item name="android:paddingBottom">36dip</item>
</style> </style>
<style name="Widget.Holo.ImageButton.NumberPickerDownButton"> <style name="Widget.Holo.ImageButton.NumberPickerDownButton">
<item name="android:background">@null</item> <item name="android:background">@null</item>
<item name="android:src">@android:drawable/numberpicker_down_btn_holo_dark</item> <item name="android:src">@android:drawable/numberpicker_down_btn_holo_dark</item>
<item name="android:paddingTop">26dip</item> <item name="android:paddingTop">36dip</item>
<item name="android:paddingBottom">26dip</item> <item name="android:paddingBottom">16dip</item>
</style> </style>
<style name="Widget.Holo.EditText.NumberPickerInputText"> <style name="Widget.Holo.EditText.NumberPickerInputText">