diff --git a/api/current.txt b/api/current.txt index c8915b4ca33ba..505ee680dd618 100644 --- a/api/current.txt +++ b/api/current.txt @@ -26958,6 +26958,7 @@ package android.widget { method public void setOnValueChangedListener(android.widget.NumberPicker.OnValueChangeListener); method public void setValue(int); method public void setWrapSelectorWheel(boolean); + field public static final int SELECTOR_WHEEL_ITEM_COUNT = 5; // 0x5 } public static abstract interface NumberPicker.Formatter { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index b8fbf17871644..42426b98ffd8e 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2679,6 +2679,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return child.draw(canvas, this, drawingTime); } + @Override + public void requestLayout() { + if (mChildrenCount > 0 && getAccessibilityNodeProvider() != null) { + throw new IllegalStateException("Views with AccessibilityNodeProvider" + + " can't have children."); + } + super.requestLayout(); + } + /** * * @param enabled True if children should be drawn with layers, false otherwise. @@ -3100,6 +3109,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { + if (getAccessibilityNodeProvider() != null) { + throw new IllegalStateException("Views with AccessibilityNodeProvider" + + " can't have children."); + } + if (mTransition != null) { // Don't prevent other add transitions from completing, but cancel remove // transitions to let them complete the process before we add to the container diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index c5066b672861a..fd939802a5775 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -29,8 +29,8 @@ import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; -import android.view.View; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; @@ -280,7 +280,9 @@ public class DatePicker extends FrameLayout { reorderSpinners(); // set content descriptions - setContentDescriptions(); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + setContentDescriptions(); + } } /** @@ -715,27 +717,20 @@ public class DatePicker extends FrameLayout { private void setContentDescriptions() { // Day - trySetContentDescription(mDaySpinner, R.id.increment, - R.string.date_picker_increment_day_button); - trySetContentDescription(mDaySpinner, R.id.decrement, - R.string.date_picker_decrement_day_button); + String text = mContext.getString(R.string.date_picker_increment_day_button); + mDaySpinner.findViewById(R.id.increment).setContentDescription(text); + text = mContext.getString(R.string.date_picker_decrement_day_button); + mDaySpinner.findViewById(R.id.decrement).setContentDescription(text); // Month - trySetContentDescription(mMonthSpinner, R.id.increment, - R.string.date_picker_increment_month_button); - trySetContentDescription(mMonthSpinner, R.id.decrement, - R.string.date_picker_decrement_month_button); + text = mContext.getString(R.string.date_picker_increment_month_button); + mMonthSpinner.findViewById(R.id.increment).setContentDescription(text); + text = mContext.getString(R.string.date_picker_decrement_month_button); + mMonthSpinner.findViewById(R.id.decrement).setContentDescription(text); // Year - trySetContentDescription(mYearSpinner, R.id.increment, - R.string.date_picker_increment_year_button); - trySetContentDescription(mYearSpinner, R.id.decrement, - R.string.date_picker_decrement_year_button); - } - - private void trySetContentDescription(View root, int viewId, int contDescResId) { - View target = root.findViewById(viewId); - if (target != null) { - target.setContentDescription(mContext.getString(contDescResId)); - } + text = mContext.getString(R.string.date_picker_increment_year_button); + mYearSpinner.findViewById(R.id.increment).setContentDescription(text); + text = mContext.getString(R.string.date_picker_decrement_year_button); + mYearSpinner.findViewById(R.id.decrement).setContentDescription(text); } private void updateInputState() { diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index 506b0c06a85e3..3335da0bd6724 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -16,6 +16,10 @@ package android.widget; +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; @@ -44,41 +48,22 @@ import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityNodeProvider; import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import com.android.internal.R; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - /** * A widget that enables the user to select a number form a predefined range. - * There are two flavors of this widget and which one is presented to the user - * depends on the current theme. - *
* For an example of using this widget, see {@link android.widget.TimePicker}. *
@@ -89,7 +74,7 @@ public class NumberPicker extends LinearLayout { /** * The number of items show in the selector wheel. */ - private static final int SELECTOR_WHEEL_ITEM_COUNT = 3; + public static final int SELECTOR_WHEEL_ITEM_COUNT = 5; /** * The default update interval during long press. @@ -99,7 +84,7 @@ public class NumberPicker extends LinearLayout { /** * The index of the middle selector item. */ - private static final int SELECTOR_MIDDLE_ITEM_INDEX = SELECTOR_WHEEL_ITEM_COUNT / 2; + private static final int SELECTOR_MIDDLE_ITEM_INDEX = 2; /** * The coefficient by which to adjust (divide) the max fling velocity. @@ -112,11 +97,18 @@ public class NumberPicker extends LinearLayout { 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. + * 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. */ @@ -128,31 +120,56 @@ public class NumberPicker extends LinearLayout { private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2; /** - * The default unscaled distance between the selection dividers. + * In this state the selector wheel is not shown. */ - private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48; + private static final int SELECTOR_WHEEL_STATE_NONE = 0; /** - * The default unscaled minimal distance for a swipe to be considered a fling. + * In this state the selector wheel is small. */ - private static final int UNSCALED_DEFAULT_MIN_FLING_DISTANCE = 150; + private static final int SELECTOR_WHEEL_STATE_SMALL = 1; /** - * Coefficient for adjusting touch scroll distance. + * In this state the selector wheel is large. */ - private static final float TOUCH_SCROLL_DECELERATION_COEFFICIENT = 2.5f; + private static final int SELECTOR_WHEEL_STATE_LARGE = 2; /** - * The resource id for the default layout. + * The alpha of the selector wheel when it is bright. */ - private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker; + 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. @@ -197,11 +214,6 @@ public class NumberPicker extends LinearLayout { */ private final EditText mInputText; - /** - * The distance between the two selection dividers. - */ - private final int mSelectionDividersDistance; - /** * The min height of this widget. */ @@ -232,11 +244,6 @@ public class NumberPicker extends LinearLayout { */ private final int mTextSize; - /** - * The minimal distance for a swipe to be considered a fling. - */ - private final int mMinFlingDistance; - /** * The height of the gap between text elements if the selector wheel. */ @@ -290,7 +297,10 @@ public class NumberPicker extends LinearLayout { /** * The selector indices whose value are show by the selector. */ - private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT]; + private final int[] mSelectorIndices = new int[] { + Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, + Integer.MIN_VALUE + }; /** * The {@link Paint} for drawing the selector. @@ -332,6 +342,11 @@ public class NumberPicker extends LinearLayout { */ private SetSelectionCommand mSetSelectionCommand; + /** + * Handle to the reusable command for adjusting the scroller. + */ + private AdjustScrollerCommand mAdjustScrollerCommand; + /** * Handle to the reusable command for changing the current value from long * press by one. @@ -339,9 +354,14 @@ public class NumberPicker extends LinearLayout { private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand; /** - * Command for beginning an edit of the current value via IME on long press. + * {@link Animator} for showing the up/down arrows. */ - private BeginSoftInputOnLongPressCommand mBeginSoftInputOnLongPressCommand; + private final AnimatorSet mShowInputControlsAnimator; + + /** + * {@link Animator} for dimming the selector wheel. + */ + private final Animator mDimSelectorWheelAnimator; /** * The Y position of the last down event. @@ -349,14 +369,24 @@ public class NumberPicker extends LinearLayout { private float mLastDownEventY; /** - * The time of the last down event. + * The Y position of the last motion event. */ - private long mLastDownEventTime; + private float mLastMotionEventY; /** - * The Y position of the last down or move event. + * Flag if to check for double tap and potentially start edit. */ - private float mLastDownOrMoveEventY; + private boolean mCheckBeginEditOnUpEvent; + + /** + * Flag if to adjust the selector wheel on next up event. + */ + private boolean mAdjustScrollerOnUpEvent; + + /** + * The state of the selector wheel. + */ + private int mSelectorWheelState; /** * Determines speed during touch scrolling. @@ -389,9 +419,9 @@ public class NumberPicker extends LinearLayout { private final int mSolidColor; /** - * Flag whether this widget has a selector wheel. + * Flag indicating if this widget supports flinging. */ - private final boolean mHasSelectorWheel; + private final boolean mFlingable; /** * Divider for showing item to be selected while scrolling @@ -403,41 +433,30 @@ public class NumberPicker extends LinearLayout { */ private final int mSelectionDividerHeight; + /** + * Reusable {@link Rect} instance. + */ + private final Rect mTempRect = new Rect(); + /** * The current scroll state of the number picker. */ private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE; /** - * Flag whether to ignore move events - we ignore such when we show in IME - * to prevent the content from scrolling. + * The duration of the animation for showing the input controls. */ - private boolean mIngonreMoveEvents; + private final long mShowInputControlsAnimimationDuration; /** - * Flag whether to show soft input on tap. + * Flag whether the scoll wheel and the fading edges have been initialized. */ - private boolean mShowSoftInputOnTap; + private boolean mScrollWheelAndFadingEdgesInitialized; /** - * The top of the top selection divider. + * The time of the last up event. */ - private int mTopSelectionDividerTop; - - /** - * The bottom of the bottom selection divider. - */ - private int mBottomSelectionDividerBottom; - - /** - * The virtual id of the last hovered child. - */ - private int mLastHoveredChildVirtualViewId; - - /** - * Provider to report to clients the semantic structure of this widget. - */ - private AccessibilityNodeProviderImpl mAccessibilityNodeProvider; + private long mLastUpEventTimeMillis; /** * Interface to listen for changes of the current value. @@ -465,7 +484,7 @@ public class NumberPicker extends LinearLayout { public static int SCROLL_STATE_IDLE = 0; /** - * The user is scrolling using touch, and his finger is still on the screen. + * The user is scrolling using touch, and their finger is still on the screen. */ public static int SCROLL_STATE_TOUCH_SCROLL = 1; @@ -530,78 +549,58 @@ public class NumberPicker extends LinearLayout { super(context, attrs, defStyle); // process style attributes - TypedArray attributesArray = context.obtainStyledAttributes( - attrs, R.styleable.NumberPicker, defStyle, 0); - final int layoutResId = attributesArray.getResourceId( - R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID); - - mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID); - + TypedArray attributesArray = context.obtainStyledAttributes(attrs, + 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); - - final int defSelectionDividerHeight = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT, + int defSelectionDividerHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT, getResources().getDisplayMetrics()); mSelectionDividerHeight = attributesArray.getDimensionPixelSize( R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight); - - final int defSelectionDividerDistance = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE, - getResources().getDisplayMetrics()); - mSelectionDividersDistance = attributesArray.getDimensionPixelSize( - R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance); - - final int defMinFlingDistance = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_MIN_FLING_DISTANCE, - getResources().getDisplayMetrics()); - mMinFlingDistance = attributesArray.getDimensionPixelSize( - R.styleable.NumberPicker_minFlingDistance, defMinFlingDistance); - mMinHeight = attributesArray.getDimensionPixelSize( R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED); - mMaxHeight = attributesArray.getDimensionPixelSize( R.styleable.NumberPicker_internalMaxHeight, SIZE_UNSPECIFIED); if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED && mMinHeight > mMaxHeight) { throw new IllegalArgumentException("minHeight > maxHeight"); } - - mMinWidth = attributesArray.getDimensionPixelSize( - R.styleable.NumberPicker_internalMinWidth, SIZE_UNSPECIFIED); - - mMaxWidth = attributesArray.getDimensionPixelSize( - R.styleable.NumberPicker_internalMaxWidth, SIZE_UNSPECIFIED); + mMinWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_internalMinWidth, + SIZE_UNSPECIFIED); + mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_internalMaxWidth, + SIZE_UNSPECIFIED); if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED && mMinWidth > mMaxWidth) { throw new IllegalArgumentException("minWidth > maxWidth"); } - mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE); - 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 // the fading edge effect implemented by View and we need our // draw() method to be called. Therefore, we declare we will draw. - setWillNotDraw(!mHasSelectorWheel); + setWillNotDraw(false); + setSelectorWheelState(SELECTOR_WHEEL_STATE_NONE); LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(layoutResId, this, true); + inflater.inflate(R.layout.number_picker, this, true); OnClickListener onClickListener = new OnClickListener() { public void onClick(View v) { hideSoftInput(); mInputText.clearFocus(); if (v.getId() == R.id.increment) { - changeValueByOne(true); + changeCurrentByOne(true); } else { - changeValueByOne(false); + changeCurrentByOne(false); } } }; @@ -611,31 +610,23 @@ public class NumberPicker extends LinearLayout { hideSoftInput(); mInputText.clearFocus(); if (v.getId() == R.id.increment) { - postChangeCurrentByOneFromLongPress(true, 0); + postChangeCurrentByOneFromLongPress(true); } else { - postChangeCurrentByOneFromLongPress(false, 0); + postChangeCurrentByOneFromLongPress(false); } return true; } }; // increment button - if (!mHasSelectorWheel) { - mIncrementButton = (ImageButton) findViewById(R.id.increment); - mIncrementButton.setOnClickListener(onClickListener); - mIncrementButton.setOnLongClickListener(onLongClickListener); - } else { - mIncrementButton = null; - } + mIncrementButton = (ImageButton) findViewById(R.id.increment); + mIncrementButton.setOnClickListener(onClickListener); + mIncrementButton.setOnLongClickListener(onLongClickListener); // decrement button - if (!mHasSelectorWheel) { - mDecrementButton = (ImageButton) findViewById(R.id.decrement); - mDecrementButton.setOnClickListener(onClickListener); - mDecrementButton.setOnLongClickListener(onLongClickListener); - } else { - mDecrementButton = null; - } + mDecrementButton = (ImageButton) findViewById(R.id.decrement); + mDecrementButton.setOnClickListener(onClickListener); + mDecrementButton.setOnLongClickListener(onLongClickListener); // input text mInputText = (EditText) findViewById(R.id.numberpicker_input); @@ -657,6 +648,7 @@ public class NumberPicker extends LinearLayout { mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE); // initialize constants + mTouchSlop = ViewConfiguration.getTapTimeout(); ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledTouchSlop(); mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); @@ -675,22 +667,69 @@ public class NumberPicker extends LinearLayout { paint.setColor(color); mSelectorWheelPaint = paint; + // create the animator for showing the input controls + mDimSelectorWheelAnimator = ObjectAnimator.ofInt(this, PROPERTY_SELECTOR_PAINT_ALPHA, + SELECTOR_WHEEL_BRIGHT_ALPHA, SELECTOR_WHEEL_DIM_ALPHA); + final ObjectAnimator showIncrementButton = ObjectAnimator.ofFloat(mIncrementButton, + PROPERTY_BUTTON_ALPHA, BUTTON_ALPHA_TRANSPARENT, BUTTON_ALPHA_OPAQUE); + final ObjectAnimator showDecrementButton = ObjectAnimator.ofFloat(mDecrementButton, + PROPERTY_BUTTON_ALPHA, BUTTON_ALPHA_TRANSPARENT, BUTTON_ALPHA_OPAQUE); + mShowInputControlsAnimator = new AnimatorSet(); + mShowInputControlsAnimator.playTogether(mDimSelectorWheelAnimator, showIncrementButton, + showDecrementButton); + mShowInputControlsAnimator.addListener(new AnimatorListenerAdapter() { + private boolean mCanceled = false; + + @Override + public void onAnimationEnd(Animator animation) { + if (!mCanceled) { + // if canceled => we still want the wheel drawn + setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL); + } + mCanceled = false; + } + + @Override + public void onAnimationCancel(Animator animation) { + if (mShowInputControlsAnimator.isRunning()) { + mCanceled = true; + } + } + }); + // create the fling and adjust scrollers mFlingScroller = new Scroller(getContext(), null, true); mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f)); updateInputTextView(); + updateIncrementAndDecrementButtonsVisibilityState(); + + if (mFlingable) { + if (isInEditMode()) { + setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL); + } else { + // Start with shown selector wheel and hidden controls. When made + // visible hide the selector and fade-in the controls to suggest + // fling interaction. + setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE); + hideInputControls(); + } + } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (!mHasSelectorWheel) { - super.onLayout(changed, left, top, right, bottom); - return; - } final int msrdWdth = getMeasuredWidth(); final int msrdHght = getMeasuredHeight(); + // Increment button at the top. + final int inctBtnMsrdWdth = mIncrementButton.getMeasuredWidth(); + final int incrBtnLeft = (msrdWdth - inctBtnMsrdWdth) / 2; + final int incrBtnTop = 0; + final int incrBtnRight = incrBtnLeft + inctBtnMsrdWdth; + final int incrBtnBottom = incrBtnTop + mIncrementButton.getMeasuredHeight(); + mIncrementButton.layout(incrBtnLeft, incrBtnTop, incrBtnRight, incrBtnBottom); + // Input text centered horizontally. final int inptTxtMsrdWdth = mInputText.getMeasuredWidth(); final int inptTxtMsrdHght = mInputText.getMeasuredHeight(); @@ -700,23 +739,24 @@ public class NumberPicker extends LinearLayout { final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght; mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom); - if (changed) { + // Decrement button at the top. + final int decrBtnMsrdWdth = mIncrementButton.getMeasuredWidth(); + final int decrBtnLeft = (msrdWdth - decrBtnMsrdWdth) / 2; + final int decrBtnTop = msrdHght - mDecrementButton.getMeasuredHeight(); + final int decrBtnRight = decrBtnLeft + decrBtnMsrdWdth; + final int decrBtnBottom = msrdHght; + mDecrementButton.layout(decrBtnLeft, decrBtnTop, decrBtnRight, decrBtnBottom); + + if (!mScrollWheelAndFadingEdgesInitialized) { + mScrollWheelAndFadingEdgesInitialized = true; // need to do all this when we know our size initializeSelectorWheel(); initializeFadingEdges(); - mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2 - - mSelectionDividerHeight; - mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight - + mSelectionDividersDistance; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (!mHasSelectorWheel) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - return; - } // Try greedily to fit the max width and height. final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth); final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight); @@ -729,143 +769,120 @@ public class NumberPicker extends LinearLayout { setMeasuredDimension(widthSize, heightSize); } - /** - * Move to the final position of a scroller. Ensures to force finish the scroller - * and if it is not at its final position a scroll of the selector wheel is - * performed to fast forward to the final position. - * - * @param scroller The scroller to whose final position to get. - * @return True of the a move was performed, i.e. the scroller was not in final position. - */ - private boolean moveToFinalScrollerPosition(Scroller scroller) { - scroller.forceFinished(true); - int amountToScroll = scroller.getFinalY() - scroller.getCurrY(); - int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementHeight; - int overshootAdjustment = mInitialScrollOffset - futureScrollOffset; - if (overshootAdjustment != 0) { - if (Math.abs(overshootAdjustment) > mSelectorElementHeight / 2) { - if (overshootAdjustment > 0) { - overshootAdjustment -= mSelectorElementHeight; - } else { - overshootAdjustment += mSelectorElementHeight; - } - } - amountToScroll += overshootAdjustment; - scrollBy(0, amountToScroll); - return true; - } - return false; - } - @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (!mHasSelectorWheel || !isEnabled()) { + if (!isEnabled() || !mFlingable) { return false; } - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mLastMotionEventY = mLastDownEventY = event.getY(); removeAllCallbacks(); - mInputText.setVisibility(View.INVISIBLE); - mLastDownOrMoveEventY = mLastDownEventY = event.getY(); - mLastDownEventTime = event.getEventTime(); - mIngonreMoveEvents = false; - mShowSoftInputOnTap = false; - if (!mFlingScroller.isFinished()) { - mFlingScroller.forceFinished(true); - mAdjustScroller.forceFinished(true); - onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); - } else if (!mAdjustScroller.isFinished()) { - mFlingScroller.forceFinished(true); - mAdjustScroller.forceFinished(true); - } else if (mLastDownEventY < mTopSelectionDividerTop) { + mShowInputControlsAnimator.cancel(); + mDimSelectorWheelAnimator.cancel(); + mCheckBeginEditOnUpEvent = false; + mAdjustScrollerOnUpEvent = true; + if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) { + mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA); + boolean scrollersFinished = mFlingScroller.isFinished() + && mAdjustScroller.isFinished(); + if (!scrollersFinished) { + mFlingScroller.forceFinished(true); + mAdjustScroller.forceFinished(true); + onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); + } + mCheckBeginEditOnUpEvent = scrollersFinished; + mAdjustScrollerOnUpEvent = true; hideSoftInput(); - postChangeCurrentByOneFromLongPress( - false, ViewConfiguration.getLongPressTimeout()); - } else if (mLastDownEventY > mBottomSelectionDividerBottom) { - hideSoftInput(); - postChangeCurrentByOneFromLongPress( - true, ViewConfiguration.getLongPressTimeout()); - } else { - mShowSoftInputOnTap = true; - postBeginSoftInputOnLongPressCommand(); + hideInputControls(); + return true; } + if (isEventInVisibleViewHitRect(event, mIncrementButton) + || isEventInVisibleViewHitRect(event, mDecrementButton)) { + return false; + } + mAdjustScrollerOnUpEvent = false; + setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE); + hideSoftInput(); + hideInputControls(); return true; - } + case MotionEvent.ACTION_MOVE: + float currentMoveY = event.getY(); + int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY); + if (deltaDownY > mTouchSlop) { + mCheckBeginEditOnUpEvent = false; + onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); + setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE); + hideSoftInput(); + hideInputControls(); + return true; + } + break; } return false; } @Override - public boolean onTouchEvent(MotionEvent event) { - if (!isEnabled() || !mHasSelectorWheel) { + public boolean onTouchEvent(MotionEvent ev) { + if (!isEnabled()) { return false; } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } - mVelocityTracker.addMovement(event); - int action = event.getActionMasked(); + mVelocityTracker.addMovement(ev); + int action = ev.getActionMasked(); switch (action) { - case MotionEvent.ACTION_MOVE: { - if (mIngonreMoveEvents) { - break; - } - float currentMoveY = event.getY(); - if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { + case MotionEvent.ACTION_MOVE: + float currentMoveY = ev.getY(); + if (mCheckBeginEditOnUpEvent + || mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY); if (deltaDownY > mTouchSlop) { - removeAllCallbacks(); + mCheckBeginEditOnUpEvent = false; onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); } - } else { - int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY) - / TOUCH_SCROLL_DECELERATION_COEFFICIENT); - scrollBy(0, deltaMoveY); - invalidate(); } - mLastDownOrMoveEventY = currentMoveY; - } break; - case MotionEvent.ACTION_UP: { - removeBeginSoftInputCommand(); - removeChangeCurrentByOneFromLongPress(); + int deltaMoveY = (int) (currentMoveY - mLastMotionEventY); + scrollBy(0, deltaMoveY); + invalidate(); + mLastMotionEventY = currentMoveY; + break; + case MotionEvent.ACTION_UP: + if (mCheckBeginEditOnUpEvent) { + mCheckBeginEditOnUpEvent = false; + final long deltaTapTimeMillis = ev.getEventTime() - mLastUpEventTimeMillis; + if (deltaTapTimeMillis < ViewConfiguration.getDoubleTapTimeout()) { + setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL); + showInputControls(mShowInputControlsAnimimationDuration); + mInputText.requestFocus(); + InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); + if (inputMethodManager != null) { + inputMethodManager.showSoftInput(mInputText, 0); + } + mLastUpEventTimeMillis = ev.getEventTime(); + return true; + } + } VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); int initialVelocity = (int) velocityTracker.getYVelocity(); if (Math.abs(initialVelocity) > mMinimumFlingVelocity) { - int deltaMove = (int) (event.getY() - mLastDownEventY); - int absDeltaMoveY = Math.abs(deltaMove); - if (absDeltaMoveY > mMinFlingDistance) { - fling(initialVelocity); - } else { - changeValueByOne(deltaMove < 0); - } + fling(initialVelocity); onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); } else { - int eventY = (int) event.getY(); - int deltaMoveY = (int) Math.abs(eventY - mLastDownEventY); - long deltaTime = event.getEventTime() - mLastDownEventTime; - if (deltaMoveY <= mTouchSlop && deltaTime < ViewConfiguration.getTapTimeout()) { - if (mShowSoftInputOnTap) { - mShowSoftInputOnTap = false; - showSoftInput(); - } else { - int selectorIndexOffset = (eventY / mSelectorElementHeight) - - SELECTOR_MIDDLE_ITEM_INDEX; - if (selectorIndexOffset > 0) { - changeValueByOne(true); - } else if (selectorIndexOffset < 0) { - changeValueByOne(false); - } + if (mAdjustScrollerOnUpEvent) { + if (mFlingScroller.isFinished() && mAdjustScroller.isFinished()) { + postAdjustScrollerCommand(0); } } else { - ensureScrollWheelAdjusted(); + postAdjustScrollerCommand(SHOW_INPUT_CONTROLS_DELAY_MILLIS); } - onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } mVelocityTracker.recycle(); mVelocityTracker = null; - } break; + mLastUpEventTimeMillis = ev.getEventTime(); + break; } return true; } @@ -874,6 +891,12 @@ public class NumberPicker extends LinearLayout { public boolean dispatchTouchEvent(MotionEvent event) { final int action = event.getActionMasked(); switch (action) { + case MotionEvent.ACTION_MOVE: + if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) { + removeAllCallbacks(); + forceCompleteChangeCurrentByOneViaScroll(); + } + break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: removeAllCallbacks(); @@ -884,75 +907,27 @@ public class NumberPicker extends LinearLayout { @Override public boolean dispatchKeyEvent(KeyEvent event) { - final int keyCode = event.getKeyCode(); - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_ENTER: - removeAllCallbacks(); - break; + int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) { + removeAllCallbacks(); } return super.dispatchKeyEvent(event); } @Override public boolean dispatchTrackballEvent(MotionEvent event) { - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - removeAllCallbacks(); - break; + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { + removeAllCallbacks(); } return super.dispatchTrackballEvent(event); } - @Override - protected boolean dispatchHoverEvent(MotionEvent event) { - if (!mHasSelectorWheel) { - return super.dispatchHoverEvent(event); - } - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - final int eventY = (int) event.getY(); - final int hoveredVirtualViewId; - if (eventY < mTopSelectionDividerTop) { - hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_DECREMENT; - } else if (eventY > mBottomSelectionDividerBottom) { - hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INCREMENT; - } else { - hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT; - } - final int action = event.getActionMasked(); - AccessibilityNodeProviderImpl provider = - (AccessibilityNodeProviderImpl) getAccessibilityNodeProvider(); - switch (action) { - case MotionEvent.ACTION_HOVER_ENTER: { - provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId, - AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); - mLastHoveredChildVirtualViewId = hoveredVirtualViewId; - } break; - case MotionEvent.ACTION_HOVER_MOVE: { - if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId - && mLastHoveredChildVirtualViewId != View.NO_ID) { - provider.sendAccessibilityEventForVirtualView( - mLastHoveredChildVirtualViewId, - AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); - provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId, - AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); - mLastHoveredChildVirtualViewId = hoveredVirtualViewId; - } - } break; - case MotionEvent.ACTION_HOVER_EXIT: { - provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId, - AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); - mLastHoveredChildVirtualViewId = View.NO_ID; - } break; - } - } - return false; - } - @Override public void computeScroll() { + if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) { + return; + } Scroller scroller = mFlingScroller; if (scroller.isFinished()) { scroller = mAdjustScroller; @@ -977,17 +952,16 @@ public class NumberPicker extends LinearLayout { @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); - if (!mHasSelectorWheel) { - mIncrementButton.setEnabled(enabled); - } - if (!mHasSelectorWheel) { - mDecrementButton.setEnabled(enabled); - } + mIncrementButton.setEnabled(enabled); + mDecrementButton.setEnabled(enabled); mInputText.setEnabled(enabled); } @Override public void scrollBy(int x, int y) { + if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) { + return; + } int[] selectorIndices = mSelectorIndices; if (!mWrapSelectorWheel && y > 0 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) { @@ -1003,7 +977,7 @@ public class NumberPicker extends LinearLayout { while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) { mCurrentScrollOffset -= mSelectorElementHeight; decrementSelectorIndices(selectorIndices); - setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true); + changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]); if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) { mCurrentScrollOffset = mInitialScrollOffset; } @@ -1011,7 +985,7 @@ public class NumberPicker extends LinearLayout { while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) { mCurrentScrollOffset += mSelectorElementHeight; incrementSelectorIndices(selectorIndices); - setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true); + changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]); if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) { mCurrentScrollOffset = mInitialScrollOffset; } @@ -1050,7 +1024,8 @@ public class NumberPicker extends LinearLayout { * * @param formatter The formatter object. If formatter isnull,
* {@link String#valueOf(int)} will be used.
- *@see #setDisplayedValues(String[])
+ *
+ * @see #setDisplayedValues(String[])
*/
public void setFormatter(Formatter formatter) {
if (formatter == mFormatter) {
@@ -1093,35 +1068,26 @@ public class NumberPicker extends LinearLayout {
if (mValue == value) {
return;
}
- setValueInternal(value, false);
+ if (value < mMinValue) {
+ value = mWrapSelectorWheel ? mMaxValue : mMinValue;
+ }
+ if (value > mMaxValue) {
+ value = mWrapSelectorWheel ? mMinValue : mMaxValue;
+ }
+ mValue = value;
initializeSelectorWheelIndices();
+ updateInputTextView();
+ updateIncrementAndDecrementButtonsVisibilityState();
invalidate();
}
/**
- * Shows the soft input for its input text.
- */
- private void showSoftInput() {
- InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
- if (inputMethodManager != null) {
- if (mHasSelectorWheel) {
- mInputText.setVisibility(View.VISIBLE);
- }
- mInputText.requestFocus();
- inputMethodManager.showSoftInput(mInputText, 0);
- }
- }
-
- /**
- * Hides the soft input if it is active for the input text.
+ * Hides the soft input of it is active for the input text.
*/
private void hideSoftInput() {
InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
if (inputMethodManager != null && inputMethodManager.isActive(mInputText)) {
inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- if (mHasSelectorWheel) {
- mInputText.setVisibility(View.INVISIBLE);
- }
}
}
@@ -1185,23 +1151,23 @@ public class NumberPicker extends LinearLayout {
* wrap around the {@link NumberPicker#getMinValue()} and
* {@link NumberPicker#getMaxValue()} values.
* - * By default if the range (max - min) is more than {@link #SELECTOR_WHEEL_ITEM_COUNT} - * (the number of items shown on the selector wheel) the selector wheel - * wrapping is enabled. + * 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. *
*- * Note: If the number of items, i.e. the range ( - * {@link #getMaxValue()} - {@link #getMinValue()}) is less than - * {@link #SELECTOR_WHEEL_ITEM_COUNT}, the selector wheel will not wrap. - * Hence, in such a case calling this method is a NOP. + * Note: If the number of items, i.e. the range + * ({@link #getMaxValue()} - {@link #getMinValue()}) is less than + * {@link #SELECTOR_WHEEL_ITEM_COUNT}, the selector wheel will not + * wrap. Hence, in such a case calling this method is a NOP. *
- * * @param wrapSelectorWheel Whether to wrap. */ public void setWrapSelectorWheel(boolean wrapSelectorWheel) { final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length; if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) { mWrapSelectorWheel = wrapSelectorWheel; + updateIncrementAndDecrementButtonsVisibilityState(); } } @@ -1258,7 +1224,6 @@ public class NumberPicker extends LinearLayout { initializeSelectorWheelIndices(); updateInputTextView(); tryComputeMaxWidth(); - invalidate(); } /** @@ -1291,7 +1256,6 @@ public class NumberPicker extends LinearLayout { initializeSelectorWheelIndices(); updateInputTextView(); tryComputeMaxWidth(); - invalidate(); } /** @@ -1335,50 +1299,103 @@ public class NumberPicker extends LinearLayout { 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 (!mHasSelectorWheel) { - super.onDraw(canvas); + if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) { return; } + float x = (mRight - mLeft) / 2; 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 int[] selectorIndices = mSelectorIndices; for (int i = 0; i < selectorIndices.length; i++) { int selectorIndex = selectorIndices[i]; String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex); - // Do not draw the middle item if input is visible since the input - // is shown only if the wheel is static and it covers the middle - // item. Otherwise, if the user starts editing the text via the - // IME he may see a dimmed version of the old value intermixed - // with the new one. + // Do not draw the middle item if input is visible since the input is shown only + // if the wheel is static and it covers the middle item. Otherwise, if the user + // starts editing the text via the IME he may see a dimmed version of the old + // value intermixed with the new one. if (i != SELECTOR_MIDDLE_ITEM_INDEX || mInputText.getVisibility() != VISIBLE) { canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint); } y += mSelectorElementHeight; } - // draw the selection dividers + // draw the selection dividers (only if scrolling and drawable specified) if (mSelectionDivider != null) { // draw the top divider - int topOfTopDivider = mTopSelectionDividerTop; + int topOfTopDivider = + (getHeight() - mSelectorElementHeight - mSelectionDividerHeight) / 2; int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight; mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider); mSelectionDivider.draw(canvas); // draw the bottom divider - int bottomOfBottomDivider = mBottomSelectionDividerBottom; - int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight; + int topOfBottomDivider = topOfTopDivider + mSelectorElementHeight; + int bottomOfBottomDivider = bottomOfTopDivider + mSelectorElementHeight; mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider); mSelectionDivider.draw(canvas); } + + canvas.restoreToCount(restoreCount); } @Override @@ -1391,20 +1408,12 @@ public class NumberPicker extends LinearLayout { public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(NumberPicker.class.getName()); - event.setScrollable(true); - event.setScrollY((mMinValue + mValue) * mSelectorElementHeight); - event.setMaxScrollY((mMaxValue - mMinValue) * mSelectorElementHeight); } @Override - public AccessibilityNodeProvider getAccessibilityNodeProvider() { - if (!mHasSelectorWheel) { - return super.getAccessibilityNodeProvider(); - } - if (mAccessibilityNodeProvider == null) { - mAccessibilityNodeProvider = new AccessibilityNodeProviderImpl(); - } - return mAccessibilityNodeProvider; + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(NumberPicker.class.getName()); } /** @@ -1433,17 +1442,17 @@ public class NumberPicker extends LinearLayout { } /** - * 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. + * 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) { + 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); @@ -1453,8 +1462,8 @@ public class NumberPicker extends LinearLayout { } /** - * Resets the selector indices and clear the cached string representation of - * these indices. + * Resets the selector indices and clear the cached + * string representation of these indices. */ private void initializeSelectorWheelIndices() { mSelectorIndexToStringCache.clear(); @@ -1471,44 +1480,39 @@ public class NumberPicker extends LinearLayout { } /** - * Sets the current value of this NumberPicker. + * 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. - * @param notifyChange Whether to notify if the current value changed. + * @param current the new value of the NumberPicker */ - private void setValueInternal(int current, boolean notifyChange) { + private void changeCurrent(int current) { if (mValue == current) { return; } // Wrap around the values if we go past the start or end if (mWrapSelectorWheel) { current = getWrappedSelectorIndex(current); - } else { - current = Math.max(current, mMinValue); - current = Math.min(current, mMaxValue); } int previous = mValue; - mValue = current; - updateInputTextView(); - if (notifyChange) { - notifyChange(previous, current); - } + setValue(current); + notifyChange(previous, current); } /** * Changes the current value by one which is increment or * decrement based on the passes argument. - * decrement the current value. * * @param increment True to increment, false to decrement. */ - private void changeValueByOne(boolean increment) { - if (mHasSelectorWheel) { + private void changeCurrentByOne(boolean increment) { + if (mFlingable) { + mDimSelectorWheelAnimator.cancel(); mInputText.setVisibility(View.INVISIBLE); - if (!moveToFinalScrollerPosition(mFlingScroller)) { - moveToFinalScrollerPosition(mAdjustScroller); - } + mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA); mPreviousScrollerY = 0; + forceCompleteChangeCurrentByOneViaScroll(); if (increment) { mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, CHANGE_CURRENT_BY_ONE_SCROLL_DURATION); @@ -1519,26 +1523,81 @@ public class NumberPicker extends LinearLayout { invalidate(); } else { if (increment) { - setValueInternal(mValue + 1, true); + changeCurrent(mValue + 1); } else { - setValueInternal(mValue - 1, true); + 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 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);
+ mInputText.setContentDescription(text);
+ mInputText.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ mInputText.setContentDescription(null);
+ }
+ }
+
private void initializeSelectorWheel() {
initializeSelectorWheelIndices();
int[] selectorIndices = mSelectorIndices;
int totalTextHeight = selectorIndices.length * mTextSize;
float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
- float textGapCount = selectorIndices.length;
+ float textGapCount = selectorIndices.length - 1;
mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
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();
- mInitialScrollOffset = editTextTextPosition
- - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
+ mInitialScrollOffset = editTextTextPosition -
+ (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
mCurrentScrollOffset = mInitialScrollOffset;
updateInputTextView();
}
@@ -1553,14 +1612,16 @@ public class NumberPicker extends LinearLayout {
*/
private void onScrollerFinished(Scroller scroller) {
if (scroller == mFlingScroller) {
- if (!ensureScrollWheelAdjusted()) {
+ if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
+ postAdjustScrollerCommand(0);
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+ } else {
updateInputTextView();
+ fadeSelectorWheel(mShowInputControlsAnimimationDuration);
}
- onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
} else {
- if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
- updateInputTextView();
- }
+ updateInputTextView();
+ showInputControls(mShowInputControlsAnimimationDuration);
}
}
@@ -1592,6 +1653,56 @@ public class NumberPicker extends LinearLayout {
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.
+ */
+ 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.
*/
@@ -1638,7 +1749,8 @@ public class NumberPicker extends LinearLayout {
/**
* Ensures we have a cached string representation of the given
- * selectorIndex to avoid multiple instantiations of the same string.
+ * selectorIndex
+ * to avoid multiple instantiations of the same string.
*/
private void ensureCachedScrollSelectorValue(int selectorIndex) {
SparseArrayselectionStart
- * to selectionEnd.
+ * to
+ * selectionEnd.
*/
private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
if (mSetSelectionCommand == null) {
@@ -1818,6 +1902,20 @@ public class NumberPicker extends LinearLayout {
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.
@@ -1836,8 +1934,8 @@ public class NumberPicker extends LinearLayout {
}
@Override
- public CharSequence filter(
- CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
+ 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) {
@@ -1882,27 +1980,6 @@ public class NumberPicker extends LinearLayout {
}
}
- /**
- * Ensures that the scroll wheel is adjusted i.e. there is no offset and the
- * middle element is in the middle of the widget.
- *
- * @return Whether an adjustment has been made.
- */
- private boolean ensureScrollWheelAdjusted() {
- // adjust to the closest value
- int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
- if (deltaY != 0) {
- mPreviousScrollerY = 0;
- if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
- deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
- }
- mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
- invalidate();
- return true;
- }
- return false;
- }
-
/**
* Command for setting the input text selection.
*/
@@ -1916,19 +1993,40 @@ public class NumberPicker extends LinearLayout {
}
}
+ /**
+ * 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;
+ if (mInitialScrollOffset == mCurrentScrollOffset) {
+ updateInputTextView();
+ showInputControls(mShowInputControlsAnimimationDuration);
+ return;
+ }
+ // 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 setStep(boolean increment) {
+ private void setIncrement(boolean increment) {
mIncrement = increment;
}
- @Override
public void run() {
- changeValueByOne(mIncrement);
+ changeCurrentByOne(mIncrement);
postDelayed(this, mLongPressUpdateInterval);
}
}
@@ -1950,248 +2048,4 @@ public class NumberPicker extends LinearLayout {
}
}
}
-
- /**
- * Command for beginning soft input on long press.
- */
- class BeginSoftInputOnLongPressCommand implements Runnable {
-
- @Override
- public void run() {
- showSoftInput();
- mIngonreMoveEvents = true;
- }
- }
-
- class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider {
- private static final int VIRTUAL_VIEW_ID_INCREMENT = 1;
-
- private static final int VIRTUAL_VIEW_ID_INPUT = 2;
-
- private static final int VIRTUAL_VIEW_ID_DECREMENT = 3;
-
- private final Rect mTempRect = new Rect();
-
- private final int[] mTempArray = new int[2];
-
- @Override
- public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
- switch (virtualViewId) {
- case View.NO_ID:
- return createAccessibilityNodeInfoForNumberPicker( mScrollX, mScrollY,
- mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
- case VIRTUAL_VIEW_ID_DECREMENT:
- return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_DECREMENT,
- getVirtualDecrementButtonText(), mScrollX, mScrollY,
- mScrollX + (mRight - mLeft),
- mTopSelectionDividerTop + mSelectionDividerHeight);
- case VIRTUAL_VIEW_ID_INPUT:
- return createAccessibiltyNodeInfoForInputText();
- case VIRTUAL_VIEW_ID_INCREMENT:
- return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_INCREMENT,
- getVirtualIncrementButtonText(), mScrollX,
- mBottomSelectionDividerBottom - mSelectionDividerHeight,
- mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
- }
- return super.createAccessibilityNodeInfo(virtualViewId);
- }
-
- @Override
- public List