Android Open Source - Common-Library Number Picker






From Project

Back to project page Common-Library.

License

The source code is released under:

Apache License

If you think the Android project Common-Library listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2008 The Android Open Source Project
 */*from w w  w . ja v a 2s.  c  om*/
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.morgan.library.widget.numberpicker;

import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.NumberKeyListener;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.LayoutInflater.Filter;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.morgan.library.R;

//import android.annotation.Widget;

/**
 * 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.
 * <ul>
 * <li>
 * If the current theme is derived from {@link android.R.style#Theme} the widget
 * presents the current value as an editable input field with an increment
 * button above and a decrement button below. Long pressing the buttons allows
 * for a quick change of the current value. Tapping on the input field allows to
 * type in a desired value.</li>
 * <li>
 * If the current theme is derived from {@link android.R.style#Theme_Holo} or
 * {@link android.R.style#Theme_Holo_Light} the widget presents the current
 * value as an editable input field with a lesser value above and a greater
 * value below. Tapping on the lesser or greater value selects it by animating
 * the number axis up or down to make the chosen value current. Flinging up or
 * down allows for multiple increments or decrements of the current value. Long
 * pressing on the lesser and greater values also allows for a quick change of
 * the current value. Tapping on the current value allows to type in a desired
 * value.</li>
 * </ul>
 * <p>
 * For an example of using this widget, see {@link android.widget.TimePicker}.
 * </p>
 */
// @Widget
public class NumberPicker extends LinearLayout {

  /**
   * The number of items show in the selector wheel.
   */
  private static final int SELECTOR_WHEEL_ITEM_COUNT = 3;

  /**
   * The default update interval during long press.
   */
  private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300;

  /**
   * The index of the middle selector item.
   */
  private static final int SELECTOR_MIDDLE_ITEM_INDEX = SELECTOR_WHEEL_ITEM_COUNT / 2;

  /**
   * The coefficient by which to adjust (divide) the max fling velocity.
   */
  private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 8;

  /**
   * The the duration for adjusting the selector wheel.
   */
  private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;

  /**
   * The duration of scrolling while snapping to a given position.
   */
  private static final int SNAP_SCROLL_DURATION = 300;

  /**
   * The strength of fading in the top and bottom while drawing the selector.
   */
  private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f;

  /**
   * The default unscaled height of the selection divider.
   */
  private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2;

  /**
   * The default unscaled distance between the selection dividers.
   */
  private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48;

  /**
   * The resource id for the default layout.
   */
  private static final int DEFAULT_LAYOUT_RESOURCE_ID = 0;

  /**
   * Constant for unspecified size.
   */
  private static final int SIZE_UNSPECIFIED = -1;

  // The suffix of text
  private String mUnit = "";

  // The length of text
  private int mMaxTextLength;

  private boolean mInputAble = true;

  /**
   * Use a custom NumberPicker formatting callback to use two-digit minutes
   * strings like "01". Keeping a static formatter etc. is the most efficient
   * way to do this; it avoids creating temporary objects on every call to
   * format().
   */
  private static class TwoDigitFormatter implements NumberPicker.Formatter {
    final StringBuilder mBuilder = new StringBuilder();

    char mZeroDigit;
    java.util.Formatter mFmt;

    final Object[] mArgs = new Object[1];

    TwoDigitFormatter() {
      final Locale locale = Locale.getDefault();
      init(locale);
    }

    private void init(Locale locale) {
      mFmt = createFormatter(locale);
      mZeroDigit = getZeroDigit(locale);
    }

    public String format(int value) {
      final Locale currentLocale = Locale.getDefault();
      if (mZeroDigit != getZeroDigit(currentLocale)) {
        init(currentLocale);
      }
      mArgs[0] = value;
      mBuilder.delete(0, mBuilder.length());
      mFmt.format("%02d", mArgs);
      return mFmt.toString();
    }

    private static char getZeroDigit(Locale locale) {
      // return LocaleData.get(locale).zeroDigit;
      return new DecimalFormatSymbols(locale).getZeroDigit();
    }

    private java.util.Formatter createFormatter(Locale locale) {
      return new java.util.Formatter(mBuilder, locale);
    }
  }

  private static final TwoDigitFormatter sTwoDigitFormatter = new TwoDigitFormatter();

  /**
   * @hide
   */
  public static final Formatter getTwoDigitFormatter() {
    return sTwoDigitFormatter;
  }

  /**
   * The increment button.
   */
  private final ImageButton mIncrementButton;

  /**
   * The decrement button.
   */
  private final ImageButton mDecrementButton;

  /**
   * The text for showing the current value.
   */
  private final EditText mInputText;

  /**
   * The distance between the two selection dividers.
   */
  private final int mSelectionDividersDistance;

  /**
   * The min height of this widget.
   */
  private final int mMinHeight;

  /**
   * The max height of this widget.
   */
  private final int mMaxHeight;

  /**
   * The max width of this widget.
   */
  private final int mMinWidth;

  /**
   * The max width of this widget.
   */
  private int mMaxWidth;

  /**
   * Flag whether to compute the max width.
   */
  private final boolean mComputeMaxWidth;

  /**
   * The height of the text.
   */
  private 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.
   */
  private String[] mDisplayedValues;

  /**
   * Lower value of the range of numbers allowed for the NumberPicker
   */
  private int mMinValue;

  /**
   * Upper value of the range of numbers allowed for the NumberPicker
   */
  private int mMaxValue;

  /**
   * Current value of this NumberPicker
   */
  private int mValue;

  /**
   * Listener to be notified upon current value change.
   */
  private OnValueChangeListener mOnValueChangeListener;

  /**
   * Listener to be notified upon scroll state change.
   */
  private OnScrollListener mOnScrollListener;

  /**
   * Formatter for for displaying the current value.
   */
  private Formatter mFormatter;

  /**
   * The speed for updating the value form long press.
   */
  private long mLongPressUpdateInterval = DEFAULT_LONG_PRESS_UPDATE_INTERVAL;

  /**
   * Cache for the string representation of selector indices.
   */
  private final SparseArray<String> mSelectorIndexToStringCache = new SparseArray<String>();

  /**
   * The selector indices whose value are show by the selector.
   */
  private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT];

  /**
   * The {@link Paint} for drawing the selector.
   */
  private final Paint mSelectorWheelPaint;

  /**
   * The {@link Drawable} for pressed virtual (increment/decrement) buttons.
   */
  private final Drawable mVirtualButtonPressedDrawable;

  /**
   * The height of a selector element (text + gap).
   */
  private int mSelectorElementHeight;

  /**
   * The initial offset of the scroll selector.
   */
  private int mInitialScrollOffset = Integer.MIN_VALUE;

  /**
   * The current offset of the scroll selector.
   */
  private int mCurrentScrollOffset;

  /**
   * The {@link Scroller} responsible for flinging the selector.
   */
  private final Scroller mFlingScroller;

  /**
   * The {@link Scroller} responsible for adjusting the selector.
   */
  private final Scroller mAdjustScroller;

  /**
   * The previous Y coordinate while scrolling the selector.
   */
  private int mPreviousScrollerY;

  /**
   * Handle to the reusable command for setting the input text selection.
   */
  private SetSelectionCommand mSetSelectionCommand;

  /**
   * Handle to the reusable command for changing the current value from long
   * press by one.
   */
  private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand;

  /**
   * Command for beginning an edit of the current value via IME on long press.
   */
  private BeginSoftInputOnLongPressCommand mBeginSoftInputOnLongPressCommand;

  /**
   * The Y position of the last down event.
   */
  private float mLastDownEventY;

  /**
   * The time of the last down event.
   */
  private long mLastDownEventTime;

  /**
   * The Y position of the last down or move event.
   */
  private float mLastDownOrMoveEventY;

  /**
   * Determines speed during touch scrolling.
   */
  private VelocityTracker mVelocityTracker;

  /**
   * @see ViewConfiguration#getScaledTouchSlop()
   */
  private int mTouchSlop;

  /**
   * @see ViewConfiguration#getScaledMinimumFlingVelocity()
   */
  private int mMinimumFlingVelocity;

  /**
   * @see ViewConfiguration#getScaledMaximumFlingVelocity()
   */
  private int mMaximumFlingVelocity;

  /**
   * Flag whether the selector should wrap around.
   */
  private boolean mWrapSelectorWheel;

  /**
   * The back ground color used to optimize scroller fading.
   */
  private final int mSolidColor;

  /**
   * Flag whether this widget has a selector wheel.
   */
  private final boolean mHasSelectorWheel;

  /**
   * Divider for showing item to be selected while scrolling
   */
  private final Drawable mSelectionDivider;

  /**
   * The height of the selection divider.
   */
  private final int mSelectionDividerHeight;

  /**
   * 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.
   */
  private boolean mIngonreMoveEvents;

  /**
   * Flag whether to show soft input on tap.
   */
  private boolean mShowSoftInputOnTap;

  /**
   * The top of the top selection divider.
   */
  private int mTopSelectionDividerTop;

  /**
   * The bottom of the bottom selection divider.
   */
  private int mBottomSelectionDividerBottom;

  /**
   * The virtual id of the last hovered child.
   */
  private int mLastHoveredChildVirtualViewId;

  /**
   * Whether the increment virtual button is pressed.
   */
  private boolean mIncrementVirtualButtonPressed;

  /**
   * Whether the decrement virtual button is pressed.
   */
  private boolean mDecrementVirtualButtonPressed;

  /**
   * Provider to report to clients the semantic structure of this widget.
   */
  private SupportAccessibilityNodeProvider mAccessibilityNodeProvider;

  /**
   * Helper class for managing pressed state of the virtual buttons.
   */
  private final PressedStateHelper mPressedStateHelper;

  /**
   * The keycode of the last handled DPAD down event.
   */
  private int mLastHandledDownDpadKeyCode = -1;

  /**
   * Interface to listen for changes of the current value.
   */
  public interface OnValueChangeListener {

    /**
     * Called upon a change of the current value.
     * 
     * @param picker
     *            The NumberPicker associated with this listener.
     * @param oldVal
     *            The previous value.
     * @param newVal
     *            The new value.
     */
    void onValueChange(NumberPicker picker, int oldVal, int newVal);
  }

  /**
   * Interface to listen for the picker scroll state.
   */
  public interface OnScrollListener {

    /**
     * The view is not scrolling.
     */
    public static int SCROLL_STATE_IDLE = 0;

    /**
     * The user is scrolling using touch, and his finger is still on the
     * screen.
     */
    public static int SCROLL_STATE_TOUCH_SCROLL = 1;

    /**
     * The user had previously been scrolling using touch and performed a
     * fling.
     */
    public static int SCROLL_STATE_FLING = 2;

    /**
     * Callback invoked while the number picker scroll state has changed.
     * 
     * @param view
     *            The view whose scroll state is being reported.
     * @param scrollState
     *            The current scroll state. One of
     *            {@link #SCROLL_STATE_IDLE},
     *            {@link #SCROLL_STATE_TOUCH_SCROLL} or
     *            {@link #SCROLL_STATE_IDLE}.
     */
    public void onScrollStateChange(NumberPicker view, int scrollState);
  }

  /**
   * Interface used to format current value into a string for presentation.
   */
  public interface Formatter {

    /**
     * Formats a string representation of the current value.
     * 
     * @param value
     *            The currently selected value.
     * @return A formatted string representation.
     */
    public String format(int value);
  }

  /**
   * Create a new number picker.
   * 
   * @param context
   *            The application environment.
   */
  public NumberPicker(Context context) {
    this(context, null);
  }

  /**
   * Create a new number picker.
   * 
   * @param context
   *            The application environment.
   * @param attrs
   *            A collection of attributes.
   */
  public NumberPicker(Context context, AttributeSet attrs) {
    this(context, attrs, R.attr.numberPickerStyle);
  }

  /**
   * Create a new number picker
   * 
   * @param context
   *            the application environment.
   * @param attrs
   *            a collection of attributes.
   * @param defStyle
   *            The default style to apply to this view.
   */
  public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs);

    // 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);

    mSolidColor = attributesArray.getColor(
        R.styleable.NumberPicker_solidColor, 0);

    mSelectionDivider = attributesArray
        .getDrawable(R.styleable.NumberPicker_selectionDivider);

    final 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);

    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);
    if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED
        && mMinWidth > mMaxWidth) {
      throw new IllegalArgumentException("minWidth > maxWidth");
    }

    mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);

    mVirtualButtonPressedDrawable = attributesArray
        .getDrawable(R.styleable.NumberPicker_virtualButtonPressedDrawable);

    attributesArray.recycle();

    mPressedStateHelper = new PressedStateHelper();

    // 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);

    LayoutInflater inflater = (LayoutInflater) getContext()
        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    inflater.inflate(layoutResId, this, true);

    OnClickListener onClickListener = new OnClickListener() {
      public void onClick(View v) {
        hideSoftInput();
        mInputText.clearFocus();
        if (v.getId() == R.id.np__increment) {
          changeValueByOne(true);
        } else {
          changeValueByOne(false);
        }
      }
    };

    OnLongClickListener onLongClickListener = new OnLongClickListener() {
      public boolean onLongClick(View v) {
        hideSoftInput();
        mInputText.clearFocus();
        if (v.getId() == R.id.np__increment) {
          postChangeCurrentByOneFromLongPress(true, 0);
        } else {
          postChangeCurrentByOneFromLongPress(false, 0);
        }
        return true;
      }
    };

    TextWatcher watcher = new TextWatcher() {

      @Override
      public void onTextChanged(CharSequence s, int start, int before,
          int count) {
      }

      @Override
      public void beforeTextChanged(CharSequence s, int start, int count,
          int after) {
      }

      @Override
      public void afterTextChanged(Editable s) {
        String result = s.toString();
        if (null != result && !"".equals(result) && !"".equals(mUnit)
            && !result.endsWith(mUnit)) {
          s.append(mUnit);
          mInputText.setSelection(result.length());
        }
      }
    };

    // increment button
    if (!mHasSelectorWheel) {
      mIncrementButton = (ImageButton) findViewById(R.id.np__increment);
      mIncrementButton.setOnClickListener(onClickListener);
      mIncrementButton.setOnLongClickListener(onLongClickListener);
    } else {
      mIncrementButton = null;
    }

    // decrement button
    if (!mHasSelectorWheel) {
      mDecrementButton = (ImageButton) findViewById(R.id.np__decrement);
      mDecrementButton.setOnClickListener(onClickListener);
      mDecrementButton.setOnLongClickListener(onLongClickListener);
    } else {
      mDecrementButton = null;
    }

    // input text
    mInputText = (EditText) findViewById(R.id.np__numberpicker_input);
    mInputText.setOnFocusChangeListener(new OnFocusChangeListener() {
      public void onFocusChange(View v, boolean hasFocus) {
        if (hasFocus) {
          mInputText.selectAll();
        } else {
          mInputText.setSelection(0, 0);
          validateInputTextView(v);
        }
      }
    });
    mInputText.setFilters(new InputFilter[] { new InputTextFilter() });
    mInputText.addTextChangedListener(watcher);
    mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
    mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE);

    // initialize constants
    ViewConfiguration configuration = ViewConfiguration.get(context);
    mTouchSlop = configuration.getScaledTouchSlop();
    mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
    mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity()
        / SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;
    mTextSize = (int) mInputText.getTextSize();

    // create the selector wheel paint
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setTextAlign(Align.CENTER);
    paint.setTextSize(mTextSize);
    paint.setTypeface(mInputText.getTypeface());
    ColorStateList colors = mInputText.getTextColors();
    int color = colors.getColorForState(ENABLED_STATE_SET, Color.WHITE);
    paint.setColor(color);
    mSelectorWheelPaint = paint;

    // create the fling and adjust scrollers
    mFlingScroller = new Scroller(getContext(), null, true);
    mAdjustScroller = new Scroller(getContext(),
        new DecelerateInterpolator(2.5f));

    updateInputTextView();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
      // If not explicitly specified this view is important for
      // accessibility.
      if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
      }
    }
    mInputText.setVisibility(INVISIBLE);
  }

  @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();

    // Input text centered horizontally.
    final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
    final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
    final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
    final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
    final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
    final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
    mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);

    if (changed) {
      // 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);
    super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
    // Flag if we are measured with width or height less than the respective
    // min.
    final int widthSize = resolveSizeAndStateRespectingMinSize(mMinWidth,
        getMeasuredWidth(), widthMeasureSpec);
    final int heightSize = resolveSizeAndStateRespectingMinSize(mMinHeight,
        getMeasuredHeight(), heightMeasureSpec);
    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()) {
      return false;
    }
    final int action = event.getAction() & MotionEvent.ACTION_MASK;
    switch (action) {
    case MotionEvent.ACTION_DOWN: {
      removeAllCallbacks();
      mInputText.setVisibility(View.INVISIBLE);
      mLastDownOrMoveEventY = mLastDownEventY = event.getY();
      mLastDownEventTime = event.getEventTime();
      mIngonreMoveEvents = false;
      mShowSoftInputOnTap = false;
      // Handle pressed state before any state change.
      if (mLastDownEventY < mTopSelectionDividerTop) {
        if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
          mPressedStateHelper
              .buttonPressDelayed(PressedStateHelper.BUTTON_DECREMENT);
        }
      } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
        if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
          mPressedStateHelper
              .buttonPressDelayed(PressedStateHelper.BUTTON_INCREMENT);
        }
      }
      // Make sure we support flinging inside scrollables.
      getParent().requestDisallowInterceptTouchEvent(true);
      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) {
        hideSoftInput();
        postChangeCurrentByOneFromLongPress(false,
            ViewConfiguration.getLongPressTimeout());
      } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
        hideSoftInput();
        postChangeCurrentByOneFromLongPress(true,
            ViewConfiguration.getLongPressTimeout());
      } else {
        mShowSoftInputOnTap = true;
        postBeginSoftInputOnLongPressCommand();
      }
      return true;
    }
    }
    return false;
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (!isEnabled() || !mHasSelectorWheel) {
      return false;
    }
    if (mVelocityTracker == null) {
      mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(event);
    int action = event.getAction() & MotionEvent.ACTION_MASK;
    switch (action) {
    case MotionEvent.ACTION_MOVE: {
      if (mIngonreMoveEvents) {
        break;
      }
      float currentMoveY = event.getY();
      if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
        int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
        if (deltaDownY > mTouchSlop) {
          removeAllCallbacks();
          onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
        }
      } else {
        int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY));
        scrollBy(0, deltaMoveY);
        invalidate();
      }
      mLastDownOrMoveEventY = currentMoveY;
    }
      break;
    case MotionEvent.ACTION_UP: {
      removeBeginSoftInputCommand();
      removeChangeCurrentByOneFromLongPress();
      mPressedStateHelper.cancel();
      VelocityTracker velocityTracker = mVelocityTracker;
      velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
      int initialVelocity = (int) velocityTracker.getYVelocity();
      if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
        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;
        long tapTimeout = ViewConfiguration.getTapTimeout();
        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);
              mPressedStateHelper
                  .buttonTapped(PressedStateHelper.BUTTON_INCREMENT);
            } else if (selectorIndexOffset < 0) {
              changeValueByOne(false);
              mPressedStateHelper
                  .buttonTapped(PressedStateHelper.BUTTON_DECREMENT);
            }
          }
        } else {
          ensureScrollWheelAdjusted();
        }
        onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
      }
      mVelocityTracker.recycle();
      mVelocityTracker = null;
    }
      break;
    }
    return true;
  }

  @Override
  public boolean dispatchTouchEvent(MotionEvent event) {
    final int action = event.getAction() & MotionEvent.ACTION_MASK;
    switch (action) {
    case MotionEvent.ACTION_CANCEL:
    case MotionEvent.ACTION_UP:
      removeAllCallbacks();
      break;
    }
    return super.dispatchTouchEvent(event);
  }

  @Override
  public boolean dispatchKeyEvent(KeyEvent event) {
    final int keyCode = event.getKeyCode();
    switch (keyCode) {
    case KeyEvent.KEYCODE_DPAD_CENTER:
    case KeyEvent.KEYCODE_ENTER:
      removeAllCallbacks();
      break;
    case KeyEvent.KEYCODE_DPAD_DOWN:
    case KeyEvent.KEYCODE_DPAD_UP:
      if (!mHasSelectorWheel) {
        break;
      }
      switch (event.getAction()) {
      case KeyEvent.ACTION_DOWN:
        if (mWrapSelectorWheel
            || (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) ? getValue() < getMaxValue()
            : getValue() > getMinValue()) {
          requestFocus();
          mLastHandledDownDpadKeyCode = keyCode;
          removeAllCallbacks();
          if (mFlingScroller.isFinished()) {
            changeValueByOne(keyCode == KeyEvent.KEYCODE_DPAD_DOWN);
          }
          return true;
        }
        break;
      case KeyEvent.ACTION_UP:
        if (mLastHandledDownDpadKeyCode == keyCode) {
          mLastHandledDownDpadKeyCode = -1;
          return true;
        }
        break;
      }
    }
    return super.dispatchKeyEvent(event);
  }

  @Override
  public boolean dispatchTrackballEvent(MotionEvent event) {
    final int action = event.getAction() & MotionEvent.ACTION_MASK;
    switch (action) {
    case MotionEvent.ACTION_CANCEL:
    case MotionEvent.ACTION_UP:
      removeAllCallbacks();
      break;
    }
    return super.dispatchTrackballEvent(event);
  }

  @Override
  protected boolean dispatchHoverEvent(MotionEvent event) {
    if (!mHasSelectorWheel) {
      return super.dispatchHoverEvent(event);
    }

    if (((AccessibilityManager) getContext().getSystemService(
        Context.ACCESSIBILITY_SERVICE)).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.getAction() & MotionEvent.ACTION_MASK;
      SupportAccessibilityNodeProvider provider = getSupportAccessibilityNodeProvider();

      switch (action) {
      case MotionEvent.ACTION_HOVER_ENTER: {
        provider.sendAccessibilityEventForVirtualView(
            hoveredVirtualViewId,
            AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
        mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
        provider.performAction(hoveredVirtualViewId,
            AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
      }
        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;
          provider.performAction(hoveredVirtualViewId,
              AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS,
              null);
        }
      }
        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() {
    Scroller scroller = mFlingScroller;
    if (scroller.isFinished()) {
      scroller = mAdjustScroller;
      if (scroller.isFinished()) {
        return;
      }
    }
    scroller.computeScrollOffset();
    int currentScrollerY = scroller.getCurrY();
    if (mPreviousScrollerY == 0) {
      mPreviousScrollerY = scroller.getStartY();
    }
    scrollBy(0, currentScrollerY - mPreviousScrollerY);
    mPreviousScrollerY = currentScrollerY;
    if (scroller.isFinished()) {
      onScrollerFinished(scroller);
    } else {
      invalidate();
    }
  }

  @Override
  public void setEnabled(boolean enabled) {
    super.setEnabled(enabled);
    if (!mHasSelectorWheel) {
      mIncrementButton.setEnabled(enabled);
    }
    if (!mHasSelectorWheel) {
      mDecrementButton.setEnabled(enabled);
    }
    mInputText.setEnabled(enabled);
  }

  @Override
  public void scrollBy(int x, int y) {
    int[] selectorIndices = mSelectorIndices;
    if (!mWrapSelectorWheel && y > 0
        && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
      mCurrentScrollOffset = mInitialScrollOffset;
      return;
    }
    if (!mWrapSelectorWheel && y < 0
        && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
      mCurrentScrollOffset = mInitialScrollOffset;
      return;
    }
    mCurrentScrollOffset += y;
    while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
      mCurrentScrollOffset -= mSelectorElementHeight;
      decrementSelectorIndices(selectorIndices);
      setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
      if (!mWrapSelectorWheel
          && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
        mCurrentScrollOffset = mInitialScrollOffset;
      }
    }
    while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
      mCurrentScrollOffset += mSelectorElementHeight;
      incrementSelectorIndices(selectorIndices);
      setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
      if (!mWrapSelectorWheel
          && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
        mCurrentScrollOffset = mInitialScrollOffset;
      }
    }
  }

  @Override
  public int getSolidColor() {
    return mSolidColor;
  }

  /**
   * Sets the listener to be notified on change of the current value.
   * 
   * @param onValueChangedListener
   *            The listener.
   */
  public void setOnValueChangedListener(
      OnValueChangeListener onValueChangedListener) {
    mOnValueChangeListener = onValueChangedListener;
  }

  /**
   * Set listener to be notified for scroll state changes.
   * 
   * @param onScrollListener
   *            The listener.
   */
  public void setOnScrollListener(OnScrollListener onScrollListener) {
    mOnScrollListener = onScrollListener;
  }

  /**
   * Set the formatter to be used for formatting the current value.
   * <p>
   * Note: If you have provided alternative values for the values this
   * formatter is never invoked.
   * </p>
   * 
   * @param formatter
   *            The formatter object. If formatter is <code>null</code>,
   *            {@link String#valueOf(int)} will be used.
   * @see #setDisplayedValues(String[])
   */
  public void setFormatter(Formatter formatter) {
    if (formatter == mFormatter) {
      return;
    }
    mFormatter = formatter;
    initializeSelectorWheelIndices();
    updateInputTextView();
  }

  /**
   * Set the current value for the number picker.
   * <p>
   * If the argument is less than the {@link NumberPicker#getMinValue()} and
   * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
   * current value is set to the {@link NumberPicker#getMinValue()} value.
   * </p>
   * <p>
   * If the argument is less than the {@link NumberPicker#getMinValue()} and
   * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
   * current value is set to the {@link NumberPicker#getMaxValue()} value.
   * </p>
   * <p>
   * If the argument is less than the {@link NumberPicker#getMaxValue()} and
   * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
   * current value is set to the {@link NumberPicker#getMaxValue()} value.
   * </p>
   * <p>
   * If the argument is less than the {@link NumberPicker#getMaxValue()} and
   * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
   * current value is set to the {@link NumberPicker#getMinValue()} value.
   * </p>
   * 
   * @param value
   *            The current value.
   * @see #setWrapSelectorWheel(boolean)
   * @see #setMinValue(int)
   * @see #setMaxValue(int)
   */
  public void setValue(int value) {
    setValueInternal(value, false);
  }

  /**
   * Shows the soft input for its input text.
   */
  private void showSoftInput() {
    if (!mInputAble) {
      return;
    }
    InputMethodManager inputMethodManager = (InputMethodManager) getContext()
        .getSystemService(Context.INPUT_METHOD_SERVICE);
    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.
   */
  private void hideSoftInput() {
    InputMethodManager inputMethodManager = (InputMethodManager) getContext()
        .getSystemService(Context.INPUT_METHOD_SERVICE);
    if (inputMethodManager != null
        && inputMethodManager.isActive(mInputText)) {
      inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
      if (mHasSelectorWheel) {
        mInputText.setVisibility(View.INVISIBLE);
      }
    }
  }

  /**
   * Computes the max width if no such specified as an attribute.
   */
  private void tryComputeMaxWidth() {
    if (!mComputeMaxWidth) {
      return;
    }
    int maxTextWidth = 0;
    if (mDisplayedValues == null) {
      float maxDigitWidth = 0;
      for (int i = 0; i <= 9; i++) {
        final float digitWidth = mSelectorWheelPaint
            .measureText(formatNumberWithLocale(i));
        if (digitWidth > maxDigitWidth) {
          maxDigitWidth = digitWidth;
        }
      }
      int numberOfDigits = 0;
      int current = mMaxValue;
      while (current > 0) {
        numberOfDigits++;
        current = current / 10;
      }
      maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
    } else {
      final int valueCount = mDisplayedValues.length;
      for (int i = 0; i < valueCount; i++) {
        final float textWidth = mSelectorWheelPaint
            .measureText(mDisplayedValues[i]);
        if (textWidth > maxTextWidth) {
          maxTextWidth = (int) textWidth;
        }
      }
    }
    maxTextWidth += mInputText.getPaddingLeft()
        + mInputText.getPaddingRight();
    if (mMaxWidth != maxTextWidth) {
      if (maxTextWidth > mMinWidth) {
        mMaxWidth = maxTextWidth;
      } else {
        mMaxWidth = mMinWidth;
      }
      invalidate();
    }
  }

  /**
   * Gets whether the selector wheel wraps when reaching the min/max value.
   * 
   * @return True if the selector wheel wraps.
   * 
   * @see #getMinValue()
   * @see #getMaxValue()
   */
  public boolean getWrapSelectorWheel() {
    return mWrapSelectorWheel;
  }

  /**
   * Sets whether the selector wheel shown during flinging/scrolling should
   * wrap around the {@link NumberPicker#getMinValue()} and
   * {@link NumberPicker#getMaxValue()} values.
   * <p>
   * By default if the range (max - min) is more than the number of items
   * shown on the selector wheel the selector wheel wrapping is enabled.
   * </p>
   * <p>
   * <strong>Note:</strong> If the number of items, i.e. the range (
   * {@link #getMaxValue()} - {@link #getMinValue()}) is less than the number
   * of items shown on the selector wheel, the selector wheel will not wrap.
   * Hence, in such a case calling this method is a NOP.
   * </p>
   * 
   * @param wrapSelectorWheel
   *            Whether to wrap.
   */
  public void setWrapSelectorWheel(boolean wrapSelectorWheel) {
    final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length;
    if ((!wrapSelectorWheel || wrappingAllowed)
        && wrapSelectorWheel != mWrapSelectorWheel) {
      mWrapSelectorWheel = wrapSelectorWheel;
    }
  }

  /**
   * Sets the speed at which the numbers be incremented and decremented when
   * the up and down buttons are long pressed respectively.
   * <p>
   * The default value is 300 ms.
   * </p>
   * 
   * @param intervalMillis
   *            The speed (in milliseconds) at which the numbers will be
   *            incremented and decremented.
   */
  public void setOnLongPressUpdateInterval(long intervalMillis) {
    mLongPressUpdateInterval = intervalMillis;
  }

  /**
   * Returns the value of the picker.
   * 
   * @return The value.
   */
  public int getValue() {
    return mValue;
  }

  /**
   * Returns the min value of the picker.
   * 
   * @return The min value
   */
  public int getMinValue() {
    return mMinValue;
  }

  /**
   * Sets the min value of the picker.
   * 
   * @param minValue
   *            The min value inclusive.
   * 
   *            <strong>Note:</strong> The length of the displayed values
   *            array set via {@link #setDisplayedValues(String[])} must be
   *            equal to the range of selectable numbers which is equal to
   *            {@link #getMaxValue()} - {@link #getMinValue()} + 1.
   */
  public void setMinValue(int minValue) {
    if (mMinValue == minValue) {
      return;
    }
    if (minValue < 0) {
      throw new IllegalArgumentException("minValue must be >= 0");
    }
    mMinValue = minValue;
    if (mMinValue > mValue) {
      mValue = mMinValue;
    }
    boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
    setWrapSelectorWheel(wrapSelectorWheel);
    initializeSelectorWheelIndices();
    updateInputTextView();
    tryComputeMaxWidth();
    invalidate();
  }

  /**
   * Returns the max value of the picker.
   * 
   * @return The max value.
   */
  public int getMaxValue() {
    return mMaxValue;
  }

  /**
   * Sets the max value of the picker.
   * 
   * @param maxValue
   *            The max value inclusive.
   * 
   *            <strong>Note:</strong> The length of the displayed values
   *            array set via {@link #setDisplayedValues(String[])} must be
   *            equal to the range of selectable numbers which is equal to
   *            {@link #getMaxValue()} - {@link #getMinValue()} + 1.
   */
  public void setMaxValue(int maxValue) {
    if (mMaxValue == maxValue) {
      return;
    }
    if (maxValue < 0) {
      throw new IllegalArgumentException("maxValue must be >= 0");
    }
    mMaxValue = maxValue;
    if (mMaxValue < mValue) {
      mValue = mMaxValue;
    }
    boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
    setWrapSelectorWheel(wrapSelectorWheel);
    initializeSelectorWheelIndices();
    updateInputTextView();
    tryComputeMaxWidth();
    invalidate();
  }

  /**
   * Gets the values to be displayed instead of string values.
   * 
   * @return The displayed values.
   */
  public String[] getDisplayedValues() {
    return mDisplayedValues;
  }

  /**
   * Sets the values to be displayed.
   * 
   * @param displayedValues
   *            The displayed values.
   * 
   *            <strong>Note:</strong> The length of the displayed values
   *            array must be equal to the range of selectable numbers which
   *            is equal to {@link #getMaxValue()} - {@link #getMinValue()} +
   *            1.
   */
  public void setDisplayedValues(String[] displayedValues) {
    if (mDisplayedValues == displayedValues) {
      return;
    }
    mDisplayedValues = displayedValues;
    if (mDisplayedValues != null) {
      // Allow text entry rather than strictly numeric entry.
      mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT
          | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
    } else {
      mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
    }
    updateInputTextView();
    initializeSelectorWheelIndices();
    tryComputeMaxWidth();
  }

  @Override
  protected float getTopFadingEdgeStrength() {
    return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
  }

  @Override
  protected float getBottomFadingEdgeStrength() {
    return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
  }

  @Override
  protected void onDetachedFromWindow() {
    removeAllCallbacks();
  }

  @Override
  protected void onDraw(Canvas canvas) {
    if (!mHasSelectorWheel) {
      super.onDraw(canvas);
      return;
    }
    float x = (getRight() - getLeft()) / 2;
    float y = mCurrentScrollOffset;

    // draw the virtual buttons pressed state if needed
    if (mVirtualButtonPressedDrawable != null
        && mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
      if (mDecrementVirtualButtonPressed) {
        // mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
        mVirtualButtonPressedDrawable
            .setState(PRESSED_ENABLED_STATE_SET);
        mVirtualButtonPressedDrawable.setBounds(0, 0, getRight(),
            mTopSelectionDividerTop);
        mVirtualButtonPressedDrawable.draw(canvas);
      }
      if (mIncrementVirtualButtonPressed) {
        // mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
        mVirtualButtonPressedDrawable
            .setState(PRESSED_ENABLED_STATE_SET);
        mVirtualButtonPressedDrawable.setBounds(0,
            mBottomSelectionDividerBottom, getRight(), getBottom());
        mVirtualButtonPressedDrawable.draw(canvas);
      }
    }

    // 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.
      if (i != SELECTOR_MIDDLE_ITEM_INDEX
          || mInputText.getVisibility() != VISIBLE) {
        if (!"".equals(scrollSelectorValue)) {
          scrollSelectorValue += mUnit;
          if (mMaxTextLength == 0
              || scrollSelectorValue.length() <= mMaxTextLength) {
            canvas.drawText(scrollSelectorValue, x, y,
                mSelectorWheelPaint);
          } else {
            String text = scrollSelectorValue;
            int firstY = (int) (y - mTextSize / 2);
            canvas.drawText(text.substring(0, mMaxTextLength), x,
                firstY, mSelectorWheelPaint);
            int secondY = (int) (y + mTextSize / 2);
            canvas.drawText(text.substring(mMaxTextLength), x,
                secondY, mSelectorWheelPaint);
          }
        }
      }
      y += mSelectorElementHeight;
    }

    // draw the selection dividers
    if (mSelectionDivider != null) {
      // draw the top divider
      int topOfTopDivider = mTopSelectionDividerTop;
      int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
      mSelectionDivider.setBounds(0, topOfTopDivider, getRight(),
          bottomOfTopDivider);
      mSelectionDivider.draw(canvas);

      // draw the bottom divider
      int bottomOfBottomDivider = mBottomSelectionDividerBottom;
      int topOfBottomDivider = bottomOfBottomDivider
          - mSelectionDividerHeight;
      mSelectionDivider.setBounds(0, topOfBottomDivider, getRight(),
          bottomOfBottomDivider);
      mSelectionDivider.draw(canvas);
    }
  }

  @Override
  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 SupportAccessibilityNodeProvider();
    }
    return mAccessibilityNodeProvider.mProvider;
  }

  /**
   * Makes a measure spec that tries greedily to use the max value.
   * 
   * @param measureSpec
   *            The measure spec.
   * @param maxSize
   *            The max value for the size.
   * @return A measure spec greedily imposing the max size.
   */
  private int makeMeasureSpec(int measureSpec, int maxSize) {
    if (maxSize == SIZE_UNSPECIFIED) {
      return measureSpec;
    }
    final int size = MeasureSpec.getSize(measureSpec);
    final int mode = MeasureSpec.getMode(measureSpec);
    switch (mode) {
    case MeasureSpec.EXACTLY:
      return measureSpec;
    case MeasureSpec.AT_MOST:
      return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize),
          MeasureSpec.EXACTLY);
    case MeasureSpec.UNSPECIFIED:
      return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY);
    default:
      throw new IllegalArgumentException("Unknown measure mode: " + mode);
    }
  }

  /**
   * Utility to reconcile a desired size and state, with constraints imposed
   * by a MeasureSpec. Tries to respect the min size, unless a different size
   * is imposed by the constraints.
   * 
   * @param minSize
   *            The minimal desired size.
   * @param measuredSize
   *            The currently measured size.
   * @param measureSpec
   *            The current measure spec.
   * @return The resolved size and state.
   */
  private int resolveSizeAndStateRespectingMinSize(int minSize,
      int measuredSize, int measureSpec) {
    if (minSize != SIZE_UNSPECIFIED) {
      final int desiredWidth = Math.max(minSize, measuredSize);
      return resolveSizeAndState(desiredWidth, measureSpec, 0);
    } else {
      return measuredSize;
    }
  }

  /**
   * Utility to reconcile a desired size and state, with constraints imposed
   * by a MeasureSpec. Will take the desired size, unless a different size is
   * imposed by the constraints. The returned value is a compound integer,
   * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
   * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting
   * size is smaller than the size the view wants to be.
   * 
   * @param size
   *            How big the view wants to be
   * @param measureSpec
   *            Constraints imposed by the parent
   * @return Size information bit mask as defined by
   *         {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}
   *         .
   */
  public static int resolveSizeAndState(int size, int measureSpec,
      int childMeasuredState) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
      result = size;
      break;
    case MeasureSpec.AT_MOST:
      if (specSize < size) {
        result = specSize | MEASURED_STATE_TOO_SMALL;
      } else {
        result = size;
      }
      break;
    case MeasureSpec.EXACTLY:
      result = specSize;
      break;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
  }

  /**
   * Resets the selector indices and clear the cached string representation of
   * these indices.
   */
  private void initializeSelectorWheelIndices() {
    mSelectorIndexToStringCache.clear();
    int[] selectorIndices = mSelectorIndices;
    int current = getValue();
    for (int i = 0; i < mSelectorIndices.length; i++) {
      int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
      if (mWrapSelectorWheel) {
        selectorIndex = getWrappedSelectorIndex(selectorIndex);
      }
      selectorIndices[i] = selectorIndex;
      ensureCachedScrollSelectorValue(selectorIndices[i]);
    }
  }

  /**
   * Sets the current value of this NumberPicker.
   * 
   * @param current
   *            The new value of the NumberPicker.
   * @param notifyChange
   *            Whether to notify if the current value changed.
   */
  private void setValueInternal(int current, boolean notifyChange) {
    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);
    }
    initializeSelectorWheelIndices();
    invalidate();
  }

  /**
   * 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) {
      mInputText.setVisibility(View.INVISIBLE);
      if (!moveToFinalScrollerPosition(mFlingScroller)) {
        moveToFinalScrollerPosition(mAdjustScroller);
      }
      mPreviousScrollerY = 0;
      if (increment) {
        mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight,
            SNAP_SCROLL_DURATION);
      } else {
        mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight,
            SNAP_SCROLL_DURATION);
      }
      invalidate();
    } else {
      if (increment) {
        setValueInternal(mValue + 1, true);
      } else {
        setValueInternal(mValue - 1, true);
      }
    }
  }

  private void initializeSelectorWheel() {
    initializeSelectorWheelIndices();
    int[] selectorIndices = mSelectorIndices;
    int totalTextHeight = selectorIndices.length * mTextSize;
    float totalTextGapHeight = (getBottom() - getTop()) - totalTextHeight;
    float textGapCount = selectorIndices.length;
    mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
    mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
    // 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);
    mCurrentScrollOffset = mInitialScrollOffset;
    updateInputTextView();
  }

  private void initializeFadingEdges() {
    setVerticalFadingEdgeEnabled(true);
    setFadingEdgeLength((getBottom() - getTop() - mTextSize) / 2);
  }

  /**
   * Callback invoked upon completion of a given <code>scroller</code>.
   */
  private void onScrollerFinished(Scroller scroller) {
    if (scroller == mFlingScroller) {
      if (!ensureScrollWheelAdjusted()) {
        updateInputTextView();
      }
      onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
    } else {
      if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
        updateInputTextView();
      }
    }
  }

  /**
   * Handles transition to a given <code>scrollState</code>
   */
  private void onScrollStateChange(int scrollState) {
    if (mScrollState == scrollState) {
      return;
    }
    mScrollState = scrollState;
    if (mOnScrollListener != null) {
      mOnScrollListener.onScrollStateChange(this, scrollState);
    }
  }

  /**
   * Flings the selector with the given <code>velocityY</code>.
   */
  private void fling(int velocityY) {
    mPreviousScrollerY = 0;

    if (velocityY > 0) {
      mFlingScroller
          .fling(0, 0, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
    } else {
      mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocityY, 0, 0, 0,
          Integer.MAX_VALUE);
    }

    invalidate();
  }

  /**
   * @return The wrapped index <code>selectorIndex</code> value.
   */
  private int getWrappedSelectorIndex(int selectorIndex) {
    if (selectorIndex > mMaxValue) {
      return mMinValue + (selectorIndex - mMaxValue)
          % (mMaxValue - mMinValue) - 1;
    } else if (selectorIndex < mMinValue) {
      return mMaxValue - (mMinValue - selectorIndex)
          % (mMaxValue - mMinValue) + 1;
    }
    return selectorIndex;
  }

  /**
   * Increments the <code>selectorIndices</code> whose string representations
   * will be displayed in the selector.
   */
  private void incrementSelectorIndices(int[] selectorIndices) {
    for (int i = 0; i < selectorIndices.length - 1; i++) {
      selectorIndices[i] = selectorIndices[i + 1];
    }
    int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
    if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) {
      nextScrollSelectorIndex = mMinValue;
    }
    selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
    ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
  }

  /**
   * Decrements the <code>selectorIndices</code> whose string representations
   * will be displayed in the selector.
   */
  private void decrementSelectorIndices(int[] selectorIndices) {
    for (int i = selectorIndices.length - 1; i > 0; i--) {
      selectorIndices[i] = selectorIndices[i - 1];
    }
    int nextScrollSelectorIndex = selectorIndices[1] - 1;
    if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) {
      nextScrollSelectorIndex = mMaxValue;
    }
    selectorIndices[0] = nextScrollSelectorIndex;
    ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
  }

  /**
   * Ensures we have a cached string representation of the given <code>
   * selectorIndex</code> to avoid multiple instantiations of the same string.
   */
  private void ensureCachedScrollSelectorValue(int selectorIndex) {
    SparseArray<String> cache = mSelectorIndexToStringCache;
    String scrollSelectorValue = cache.get(selectorIndex);
    if (scrollSelectorValue != null) {
      return;
    }
    if (selectorIndex < mMinValue || selectorIndex > mMaxValue) {
      scrollSelectorValue = "";
    } else {
      if (mDisplayedValues != null) {
        if (mDisplayedValues.length == 0) {
          scrollSelectorValue = "";
        } else {
          int displayedValueIndex = selectorIndex - mMinValue;
          scrollSelectorValue = mDisplayedValues[displayedValueIndex];
        }
      } else {
        scrollSelectorValue = formatNumber(selectorIndex);
      }
    }
    cache.put(selectorIndex, scrollSelectorValue);
  }

  private String formatNumber(int value) {
    return (mFormatter != null) ? mFormatter.format(value)
        : formatNumberWithLocale(value);
  }

  private void validateInputTextView(View v) {
    String str = String.valueOf(((TextView) v).getText());
    if (TextUtils.isEmpty(str)) {
      // Restore to the old value as we don't allow empty values
      updateInputTextView();
    } else {
      // Check the new value and ensure it's in range
      str = str.replace(mUnit, "");
      int current = getSelectedPos(str.toString());
      setValueInternal(current, true);
    }
  }

  /**
   * Updates the view of this NumberPicker. If displayValues were specified in
   * the string corresponding to the index specified by the current value will
   * be returned. Otherwise, the formatter specified in {@link #setFormatter}
   * will be used to format the number.
   * 
   * @return Whether the text was updated.
   */
  private boolean updateInputTextView() {
    if (mDisplayedValues != null && mDisplayedValues.length == 0) {
      mInputText.setText("");
      return true;
    }
    /*
     * If we don't have displayed values then use the current number else
     * find the correct value in the displayed values for the current
     * number.
     */
    String text = (mDisplayedValues == null) ? formatNumber(mValue)
        : mDisplayedValues[mValue - mMinValue];
    if (!TextUtils.isEmpty(text)
        && !text.equals(mInputText.getText().toString())) {
      mInputText.setText(text);
      return true;
    }

    return false;
  }

  /**
   * Notifies the listener, if registered, of a change of the value of this
   * NumberPicker.
   */
  private void notifyChange(int previous, int current) {
    if (mOnValueChangeListener != null) {
      mOnValueChangeListener.onValueChange(this, previous, mValue);
    }
  }

  /**
   * Posts a command for changing the current value by one.
   * 
   * @param increment
   *            Whether to increment or decrement the value.
   */
  private void postChangeCurrentByOneFromLongPress(boolean increment,
      long delayMillis) {
    if (mChangeCurrentByOneFromLongPressCommand == null) {
      mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
    } else {
      removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
    }
    mChangeCurrentByOneFromLongPressCommand.setStep(increment);
    postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis);
  }

  /**
   * Removes the command for changing the current value by one.
   */
  private void removeChangeCurrentByOneFromLongPress() {
    if (mChangeCurrentByOneFromLongPressCommand != null) {
      removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
    }
  }

  /**
   * Posts a command for beginning an edit of the current value via IME on
   * long press.
   */
  private void postBeginSoftInputOnLongPressCommand() {
    if (mBeginSoftInputOnLongPressCommand == null) {
      mBeginSoftInputOnLongPressCommand = new BeginSoftInputOnLongPressCommand();
    } else {
      removeCallbacks(mBeginSoftInputOnLongPressCommand);
    }
    postDelayed(mBeginSoftInputOnLongPressCommand,
        ViewConfiguration.getLongPressTimeout());
  }

  /**
   * Removes the command for beginning an edit of the current value via IME.
   */
  private void removeBeginSoftInputCommand() {
    if (mBeginSoftInputOnLongPressCommand != null) {
      removeCallbacks(mBeginSoftInputOnLongPressCommand);
    }
  }

  /**
   * Removes all pending callback from the message queue.
   */
  private void removeAllCallbacks() {
    if (mChangeCurrentByOneFromLongPressCommand != null) {
      removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
    }
    if (mSetSelectionCommand != null) {
      removeCallbacks(mSetSelectionCommand);
    }
    if (mBeginSoftInputOnLongPressCommand != null) {
      removeCallbacks(mBeginSoftInputOnLongPressCommand);
    }
    mPressedStateHelper.cancel();
  }

  /**
   * @return The selected index given its displayed <code>value</code>.
   */
  private int getSelectedPos(String value) {
    if (mDisplayedValues == null) {
      try {
        return Integer.parseInt(value);
      } catch (NumberFormatException e) {
        // Ignore as if it's not a number we don't care
      }
    } else {
      for (int i = 0; i < mDisplayedValues.length; i++) {
        // Don't force the user to type in jan when ja will do
        value = value.toLowerCase();
        if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
          return mMinValue + i;
        }
      }

      /*
       * The user might have typed in a number into the month field i.e.
       * 10 instead of OCT so support that too.
       */
      try {
        return Integer.parseInt(value);
      } catch (NumberFormatException e) {

        // Ignore as if it's not a number we don't care
      }
    }
    return mMinValue;
  }

  /**
   * Posts an {@link SetSelectionCommand} from the given <code>selectionStart
   * </code> to <code>selectionEnd</code>.
   */
  private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
    if (mSetSelectionCommand == null) {
      mSetSelectionCommand = new SetSelectionCommand();
    } else {
      removeCallbacks(mSetSelectionCommand);
    }
    mSetSelectionCommand.mSelectionStart = selectionStart;
    mSetSelectionCommand.mSelectionEnd = selectionEnd;
    post(mSetSelectionCommand);
  }

  /**
   * The numbers accepted by the input text's {@link Filter}
   */
  private static final char[] DIGIT_CHARACTERS = new char[] {
      // Latin digits are the common case
      '0', '1', '2', '3', '4', '5', '6', '7', '8',
      '9',
      // Arabic-Indic
      '\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665',
      '\u0666', '\u0667', '\u0668', '\u0669',
      // Extended Arabic-Indic
      '\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5',
      '\u06f6', '\u06f7', '\u06f8', '\u06f9' };

  /**
   * Filter for accepting only valid indices or prefixes of the string
   * representation of valid indices.
   */
  class InputTextFilter extends NumberKeyListener {

    // XXX This doesn't allow for range limits when controlled by a
    // soft input method!
    public int getInputType() {
      return InputType.TYPE_CLASS_TEXT;
    }

    @Override
    protected char[] getAcceptedChars() {
      return DIGIT_CHARACTERS;
    }

    @Override
    public CharSequence filter(CharSequence source, int start, int end,
        Spanned dest, int dstart, int dend) {
      if (mDisplayedValues == null) {
        if (source.toString().equals(mUnit)) {
          return mUnit;
        }
        CharSequence filtered = super.filter(source, start, end, dest,
            dstart, dend);
        if (filtered == null) {
          filtered = source.subSequence(start, end);
        }

        String result = String.valueOf(dest.subSequence(0, dstart))
            + filtered + dest.subSequence(dend, dest.length());

        if (result.endsWith(mUnit)) {
          result = result.replace(mUnit, "");
        }
        if ("".equals(result)) {
          return result;
        }
        int val = getSelectedPos(result);

        /*
         * Ensure the user can't type in a value greater than the max
         * allowed. We have to allow less than min as the user might
         * want to delete some numbers and then type a new number.
         */
        if (val > mMaxValue) {
          return "";
        } else {
          return filtered;
        }
      } else {
        CharSequence filtered = String.valueOf(source.subSequence(
            start, end));
        if (TextUtils.isEmpty(filtered)) {
          return "";
        }
        String result = String.valueOf(dest.subSequence(0, dstart))
            + filtered + dest.subSequence(dend, dest.length());
        String str = String.valueOf(result).toLowerCase();
        for (String val : mDisplayedValues) {
          String valLowerCase = val.toLowerCase();
          if (valLowerCase.startsWith(str)) {
            postSetSelectionCommand(result.length(), val.length());
            return val.subSequence(dstart, val.length());
          }
        }
        return "";
      }
    }
  }

  /**
   * 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;
  }

  class PressedStateHelper implements Runnable {
    public static final int BUTTON_INCREMENT = 1;
    public static final int BUTTON_DECREMENT = 2;

    private final int MODE_PRESS = 1;
    private final int MODE_TAPPED = 2;

    private int mManagedButton;
    private int mMode;

    public void cancel() {
      mMode = 0;
      mManagedButton = 0;
      NumberPicker.this.removeCallbacks(this);
      if (mIncrementVirtualButtonPressed) {
        mIncrementVirtualButtonPressed = false;
        invalidate(0, mBottomSelectionDividerBottom, getRight(),
            getBottom());
      }
      mDecrementVirtualButtonPressed = false;
      if (mDecrementVirtualButtonPressed) {
        invalidate(0, 0, getRight(), mTopSelectionDividerTop);
      }
    }

    public void buttonPressDelayed(int button) {
      cancel();
      mMode = MODE_PRESS;
      mManagedButton = button;
      NumberPicker.this.postDelayed(this,
          ViewConfiguration.getTapTimeout());
    }

    public void buttonTapped(int button) {
      cancel();
      mMode = MODE_TAPPED;
      mManagedButton = button;
      NumberPicker.this.post(this);
    }

    @Override
    public void run() {
      switch (mMode) {
      case MODE_PRESS: {
        switch (mManagedButton) {
        case BUTTON_INCREMENT: {
          mIncrementVirtualButtonPressed = true;
          invalidate(0, mBottomSelectionDividerBottom, getRight(),
              getBottom());
        }
          break;
        case BUTTON_DECREMENT: {
          mDecrementVirtualButtonPressed = true;
          invalidate(0, 0, getRight(), mTopSelectionDividerTop);
        }
        }
      }
        break;
      case MODE_TAPPED: {
        switch (mManagedButton) {
        case BUTTON_INCREMENT: {
          if (!mIncrementVirtualButtonPressed) {
            NumberPicker.this.postDelayed(this,
                ViewConfiguration.getPressedStateDuration());
          }
          mIncrementVirtualButtonPressed ^= true;
          invalidate(0, mBottomSelectionDividerBottom, getRight(),
              getBottom());
        }
          break;
        case BUTTON_DECREMENT: {
          if (!mDecrementVirtualButtonPressed) {
            NumberPicker.this.postDelayed(this,
                ViewConfiguration.getPressedStateDuration());
          }
          mDecrementVirtualButtonPressed ^= true;
          invalidate(0, 0, getRight(), mTopSelectionDividerTop);
        }
        }
      }
        break;
      }
    }
  }

  /**
   * Command for setting the input text selection.
   */
  class SetSelectionCommand implements Runnable {
    private int mSelectionStart;

    private int mSelectionEnd;

    public void run() {
      if (!"".equals(mInputText.getText().toString())) {
        mInputText.setSelection(mSelectionStart, mSelectionEnd);
      }
    }
  }

  /**
   * Command for changing the current value from a long press by one.
   */
  class ChangeCurrentByOneFromLongPressCommand implements Runnable {
    private boolean mIncrement;

    private void setStep(boolean increment) {
      mIncrement = increment;
    }

    @Override
    public void run() {
      changeValueByOne(mIncrement);
      postDelayed(this, mLongPressUpdateInterval);
    }
  }

  /**
   * @hide
   */
  public static class CustomEditText extends EditText {

    public CustomEditText(Context context, AttributeSet attrs) {
      super(context, attrs);
    }

    @Override
    public void onEditorAction(int actionCode) {
      super.onEditorAction(actionCode);
      if (actionCode == EditorInfo.IME_ACTION_DONE) {
        clearFocus();
        this.setVisibility(INVISIBLE);
      }
    }
  }

  /**
   * Command for beginning soft input on long press.
   */
  class BeginSoftInputOnLongPressCommand implements Runnable {

    @Override
    public void run() {
      showSoftInput();
      mIngonreMoveEvents = true;
    }
  }

  private SupportAccessibilityNodeProvider getSupportAccessibilityNodeProvider() {
    return new SupportAccessibilityNodeProvider();
  }

  class SupportAccessibilityNodeProvider {

    AccessibilityNodeProviderImpl mProvider;

    private SupportAccessibilityNodeProvider() {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        mProvider = new AccessibilityNodeProviderImpl();
      }
    }

    public boolean performAction(int virtualViewId, int action,
        Bundle arguments) {
      if (mProvider != null) {
        return mProvider
            .performAction(virtualViewId, action, arguments);
      }

      return false;
    }

    public void sendAccessibilityEventForVirtualView(int virtualViewId,
        int eventType) {
      if (mProvider != null)
        mProvider.sendAccessibilityEventForVirtualView(virtualViewId,
            eventType);
    }
  }

  /**
   * Class for managing virtual view tree rooted at this picker.
   */
  class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider {
    private static final int UNDEFINED = Integer.MIN_VALUE;

    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];

    private int mAccessibilityFocusedView = UNDEFINED;

    @Override
    public AccessibilityNodeInfo createAccessibilityNodeInfo(
        int virtualViewId) {
      switch (virtualViewId) {
      case View.NO_ID:
        return createAccessibilityNodeInfoForNumberPicker(getScrollX(),
            getScrollY(), getScrollX() + (getRight() - getLeft()),
            getScrollY() + (getBottom() - getTop()));
      case VIRTUAL_VIEW_ID_DECREMENT:
        return createAccessibilityNodeInfoForVirtualButton(
            VIRTUAL_VIEW_ID_DECREMENT,
            getVirtualDecrementButtonText(), getScrollX(),
            getScrollY(), getScrollX() + (getRight() - getLeft()),
            mTopSelectionDividerTop + mSelectionDividerHeight);
      case VIRTUAL_VIEW_ID_INPUT:
        return createAccessibiltyNodeInfoForInputText();
      case VIRTUAL_VIEW_ID_INCREMENT:
        return createAccessibilityNodeInfoForVirtualButton(
            VIRTUAL_VIEW_ID_INCREMENT,
            getVirtualIncrementButtonText(),
            getScrollX(),
            mBottomSelectionDividerBottom - mSelectionDividerHeight,
            getScrollX() + (getRight() - getLeft()), getScrollY()
                + (getBottom() - getTop()));
      }
      return super.createAccessibilityNodeInfo(virtualViewId);
    }

    @Override
    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(
        String searched, int virtualViewId) {
      if (TextUtils.isEmpty(searched)) {
        return Collections.emptyList();
      }
      String searchedLowerCase = searched.toLowerCase();
      List<AccessibilityNodeInfo> result = new ArrayList<AccessibilityNodeInfo>();
      switch (virtualViewId) {
      case View.NO_ID: {
        findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
            VIRTUAL_VIEW_ID_DECREMENT, result);
        findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
            VIRTUAL_VIEW_ID_INPUT, result);
        findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
            VIRTUAL_VIEW_ID_INCREMENT, result);
        return result;
      }
      case VIRTUAL_VIEW_ID_DECREMENT:
      case VIRTUAL_VIEW_ID_INCREMENT:
      case VIRTUAL_VIEW_ID_INPUT: {
        findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
            virtualViewId, result);
        return result;
      }
      }
      return super.findAccessibilityNodeInfosByText(searched,
          virtualViewId);
    }

    @Override
    public boolean performAction(int virtualViewId, int action,
        Bundle arguments) {
      switch (virtualViewId) {
      case View.NO_ID: {
        switch (action) {
        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
          if (mAccessibilityFocusedView != virtualViewId) {
            mAccessibilityFocusedView = virtualViewId;
            // requestAccessibilityFocus();
            performAccessibilityAction(
                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS,
                null);
            return true;
          }
        }
          return false;
        case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
          if (mAccessibilityFocusedView == virtualViewId) {
            mAccessibilityFocusedView = UNDEFINED;
            // clearAccessibilityFocus();
            performAccessibilityAction(
                AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
                null);
            return true;
          }
          return false;
        }
        case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
          if (NumberPicker.this.isEnabled()
              && (getWrapSelectorWheel() || getValue() < getMaxValue())) {
            changeValueByOne(true);
            return true;
          }
        }
          return false;
        case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
          if (NumberPicker.this.isEnabled()
              && (getWrapSelectorWheel() || getValue() > getMinValue())) {
            changeValueByOne(false);
            return true;
          }
        }
          return false;
        }
      }
        break;
      case VIRTUAL_VIEW_ID_INPUT: {
        switch (action) {
        case AccessibilityNodeInfo.ACTION_FOCUS: {
          if (NumberPicker.this.isEnabled()
              && !mInputText.isFocused()) {
            return mInputText.requestFocus();
          }
        }
          break;
        case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
          if (NumberPicker.this.isEnabled() && mInputText.isFocused()) {
            mInputText.clearFocus();
            return true;
          }
          return false;
        }
        case AccessibilityNodeInfo.ACTION_CLICK: {
          if (NumberPicker.this.isEnabled()) {
            showSoftInput();
            return true;
          }
          return false;
        }
        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
          if (mAccessibilityFocusedView != virtualViewId) {
            mAccessibilityFocusedView = virtualViewId;
            sendAccessibilityEventForVirtualView(
                virtualViewId,
                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
            mInputText.invalidate();
            return true;
          }
        }
          return false;
        case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
          if (mAccessibilityFocusedView == virtualViewId) {
            mAccessibilityFocusedView = UNDEFINED;
            sendAccessibilityEventForVirtualView(
                virtualViewId,
                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
            mInputText.invalidate();
            return true;
          }
        }
          return false;
        default: {
          return mInputText.performAccessibilityAction(action,
              arguments);
        }
        }
      }
        return false;
      case VIRTUAL_VIEW_ID_INCREMENT: {
        switch (action) {
        case AccessibilityNodeInfo.ACTION_CLICK: {
          if (NumberPicker.this.isEnabled()) {
            NumberPicker.this.changeValueByOne(true);
            sendAccessibilityEventForVirtualView(virtualViewId,
                AccessibilityEvent.TYPE_VIEW_CLICKED);
            return true;
          }
        }
          return false;
        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
          if (mAccessibilityFocusedView != virtualViewId) {
            mAccessibilityFocusedView = virtualViewId;
            sendAccessibilityEventForVirtualView(
                virtualViewId,
                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
            invalidate(0, mBottomSelectionDividerBottom,
                getRight(), getBottom());
            return true;
          }
        }
          return false;
        case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
          if (mAccessibilityFocusedView == virtualViewId) {
            mAccessibilityFocusedView = UNDEFINED;
            sendAccessibilityEventForVirtualView(
                virtualViewId,
                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
            invalidate(0, mBottomSelectionDividerBottom,
                getRight(), getBottom());
            return true;
          }
        }
          return false;
        }
      }
        return false;
      case VIRTUAL_VIEW_ID_DECREMENT: {
        switch (action) {
        case AccessibilityNodeInfo.ACTION_CLICK: {
          if (NumberPicker.this.isEnabled()) {
            final boolean increment = (virtualViewId == VIRTUAL_VIEW_ID_INCREMENT);
            NumberPicker.this.changeValueByOne(increment);
            sendAccessibilityEventForVirtualView(virtualViewId,
                AccessibilityEvent.TYPE_VIEW_CLICKED);
            return true;
          }
        }
          return false;
        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
          if (mAccessibilityFocusedView != virtualViewId) {
            mAccessibilityFocusedView = virtualViewId;
            sendAccessibilityEventForVirtualView(
                virtualViewId,
                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
            invalidate(0, 0, getRight(), mTopSelectionDividerTop);
            return true;
          }
        }
          return false;
        case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
          if (mAccessibilityFocusedView == virtualViewId) {
            mAccessibilityFocusedView = UNDEFINED;
            sendAccessibilityEventForVirtualView(
                virtualViewId,
                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
            invalidate(0, 0, getRight(), mTopSelectionDividerTop);
            return true;
          }
        }
          return false;
        }
      }
        return false;
      }
      return super.performAction(virtualViewId, action, arguments);
    }

    public void sendAccessibilityEventForVirtualView(int virtualViewId,
        int eventType) {
      switch (virtualViewId) {
      case VIRTUAL_VIEW_ID_DECREMENT: {
        if (hasVirtualDecrementButton()) {
          sendAccessibilityEventForVirtualButton(virtualViewId,
              eventType, getVirtualDecrementButtonText());
        }
      }
        break;
      case VIRTUAL_VIEW_ID_INPUT: {
        sendAccessibilityEventForVirtualText(eventType);
      }
        break;
      case VIRTUAL_VIEW_ID_INCREMENT: {
        if (hasVirtualIncrementButton()) {
          sendAccessibilityEventForVirtualButton(virtualViewId,
              eventType, getVirtualIncrementButtonText());
        }
      }
        break;
      }
    }

    private void sendAccessibilityEventForVirtualText(int eventType) {
      if (((AccessibilityManager) getContext().getSystemService(
          Context.ACCESSIBILITY_SERVICE)).isEnabled()) {
        AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
        mInputText.onInitializeAccessibilityEvent(event);
        mInputText.onPopulateAccessibilityEvent(event);
        event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
        requestSendAccessibilityEvent(NumberPicker.this, event);
      }
    }

    private void sendAccessibilityEventForVirtualButton(int virtualViewId,
        int eventType, String text) {
      if (((AccessibilityManager) getContext().getSystemService(
          Context.ACCESSIBILITY_SERVICE)).isEnabled()) {
        AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
        event.setClassName(Button.class.getName());
        event.setPackageName(getContext().getPackageName());
        event.getText().add(text);
        event.setEnabled(NumberPicker.this.isEnabled());
        event.setSource(NumberPicker.this, virtualViewId);
        requestSendAccessibilityEvent(NumberPicker.this, event);
      }
    }

    private void findAccessibilityNodeInfosByTextInChild(
        String searchedLowerCase, int virtualViewId,
        List<AccessibilityNodeInfo> outResult) {
      switch (virtualViewId) {
      case VIRTUAL_VIEW_ID_DECREMENT: {
        String text = getVirtualDecrementButtonText();
        if (!TextUtils.isEmpty(text)
            && text.toString().toLowerCase()
                .contains(searchedLowerCase)) {
          outResult
              .add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT));
        }
      }
        return;
      case VIRTUAL_VIEW_ID_INPUT: {
        CharSequence text = mInputText.getText();
        if (!TextUtils.isEmpty(text)
            && text.toString().toLowerCase()
                .contains(searchedLowerCase)) {
          outResult
              .add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
          return;
        }
        CharSequence contentDesc = mInputText.getText();
        if (!TextUtils.isEmpty(contentDesc)
            && contentDesc.toString().toLowerCase()
                .contains(searchedLowerCase)) {
          outResult
              .add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
          return;
        }
      }
        break;
      case VIRTUAL_VIEW_ID_INCREMENT: {
        String text = getVirtualIncrementButtonText();
        if (!TextUtils.isEmpty(text)
            && text.toString().toLowerCase()
                .contains(searchedLowerCase)) {
          outResult
              .add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT));
        }
      }
        return;
      }
    }

    private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText() {
      AccessibilityNodeInfo info = mInputText
          .createAccessibilityNodeInfo();
      info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
      if (mAccessibilityFocusedView != VIRTUAL_VIEW_ID_INPUT) {
        info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
      }
      if (mAccessibilityFocusedView == VIRTUAL_VIEW_ID_INPUT) {
        info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
      }
      return info;
    }

    private AccessibilityNodeInfo createAccessibilityNodeInfoForVirtualButton(
        int virtualViewId, String text, int left, int top, int right,
        int bottom) {
      AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
      info.setClassName(Button.class.getName());
      info.setPackageName(getContext().getPackageName());
      info.setSource(NumberPicker.this, virtualViewId);
      info.setParent(NumberPicker.this);
      info.setText(text);
      info.setClickable(true);
      info.setLongClickable(true);
      info.setEnabled(NumberPicker.this.isEnabled());
      Rect boundsInParent = mTempRect;
      boundsInParent.set(left, top, right, bottom);
      // TODO info.setVisibleToUser(isVisibleToUser(boundsInParent));
      info.setBoundsInParent(boundsInParent);
      Rect boundsInScreen = boundsInParent;
      int[] locationOnScreen = mTempArray;
      getLocationOnScreen(locationOnScreen);
      boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
      info.setBoundsInScreen(boundsInScreen);

      if (mAccessibilityFocusedView != virtualViewId) {
        info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
      }
      if (mAccessibilityFocusedView == virtualViewId) {
        info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
      }
      if (NumberPicker.this.isEnabled()) {
        info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
      }

      return info;
    }

    private AccessibilityNodeInfo createAccessibilityNodeInfoForNumberPicker(
        int left, int top, int right, int bottom) {
      AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
      info.setClassName(NumberPicker.class.getName());
      info.setPackageName(getContext().getPackageName());
      info.setSource(NumberPicker.this);

      if (hasVirtualDecrementButton()) {
        info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT);
      }
      info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
      if (hasVirtualIncrementButton()) {
        info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT);
      }

      info.setParent((View) getParentForAccessibility());
      info.setEnabled(NumberPicker.this.isEnabled());
      info.setScrollable(true);

      /**
       * TODO: Figure out compat implementation for this final float
       * applicationScale =
       * getContext().getResources().getCompatibilityInfo
       * ().applicationScale;
       * 
       * Rect boundsInParent = mTempRect; boundsInParent.set(left, top,
       * right, bottom); boundsInParent.scale(applicationScale);
       * info.setBoundsInParent(boundsInParent);
       * 
       * info.setVisibleToUser(isVisibleToUser());
       * 
       * Rect boundsInScreen = boundsInParent; int[] locationOnScreen =
       * mTempArray; getLocationOnScreen(locationOnScreen);
       * boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
       * boundsInScreen.scale(applicationScale);
       * info.setBoundsInScreen(boundsInScreen);
       */

      if (mAccessibilityFocusedView != View.NO_ID) {
        info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
      }
      if (mAccessibilityFocusedView == View.NO_ID) {
        info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
      }
      if (NumberPicker.this.isEnabled()) {
        if (getWrapSelectorWheel() || getValue() < getMaxValue()) {
          info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
        }
        if (getWrapSelectorWheel() || getValue() > getMinValue()) {
          info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
        }
      }

      return info;
    }

    private boolean hasVirtualDecrementButton() {
      return getWrapSelectorWheel() || getValue() > getMinValue();
    }

    private boolean hasVirtualIncrementButton() {
      return getWrapSelectorWheel() || getValue() < getMaxValue();
    }

    private String getVirtualDecrementButtonText() {
      int value = mValue - 1;
      if (mWrapSelectorWheel) {
        value = getWrappedSelectorIndex(value);
      }
      if (value >= mMinValue) {
        return (mDisplayedValues == null) ? formatNumber(value)
            : mDisplayedValues[value - mMinValue];
      }
      return null;
    }

    private String getVirtualIncrementButtonText() {
      int value = mValue + 1;
      if (mWrapSelectorWheel) {
        value = getWrappedSelectorIndex(value);
      }
      if (value <= mMaxValue) {
        return (mDisplayedValues == null) ? formatNumber(value)
            : mDisplayedValues[value - mMinValue];
      }
      return null;
    }
  }

  static private String formatNumberWithLocale(int value) {
    return String.format(Locale.getDefault(), "%d", value);
  }

  public void setUnit(String mUnit) {
    this.mUnit = mUnit;
  }

  public void setMaxTextLength(int mMaxTextLength) {
    this.mMaxTextLength = mMaxTextLength;
  }

  public void setInputAble(boolean mInputAble) {
    this.mInputAble = mInputAble;
  }

  public void clearInputTextFocus() {
    if (mInputText.isFocused()) {
      mInputText.clearFocus();
      mInputText.setVisibility(View.INVISIBLE);
    }
  }
}




Java Source Code List

com.morgan.library.app.APPContext.java
com.morgan.library.app.AppManager.java
com.morgan.library.app.BaseActivity.java
com.morgan.library.app.BaseFragmentActivity.java
com.morgan.library.app.BaseListActivity.java
com.morgan.library.app.BaseScreenShotActivity.java
com.morgan.library.app.CommonApplication.java
com.morgan.library.async.CustomAsyncTask.java
com.morgan.library.async.Destroyable.java
com.morgan.library.async.IFeedback.java
com.morgan.library.async.TaskManager.java
com.morgan.library.model.NetResult.java
com.morgan.library.model.SpinnerItem.java
com.morgan.library.model.WeatherType.java
com.morgan.library.model.Weather.java
com.morgan.library.net.ApiClient.java
com.morgan.library.net.ApiManager.java
com.morgan.library.net.ApiUrl.java
com.morgan.library.net.IApiClient.java
com.morgan.library.net.JsonUtils.java
com.morgan.library.net.MockApiClient.java
com.morgan.library.service.LocationManager.java
com.morgan.library.service.WeatherManager.java
com.morgan.library.snippet.CookieShare.java
com.morgan.library.snippet.CustomService.java
com.morgan.library.snippet.ExceptionCatcher.java
com.morgan.library.snippet.ProxyUsage.java
com.morgan.library.snippet.SocketClient.java
com.morgan.library.snippet.SocketServer.java
com.morgan.library.snippet.UDPClient.java
com.morgan.library.snippet.UDPServer.java
com.morgan.library.snippet.XMLAnalyze.java
com.morgan.library.task.GetWeatherTask.java
com.morgan.library.utils.AppUtils.java
com.morgan.library.utils.DateUtils.java
com.morgan.library.utils.DensityUtils.java
com.morgan.library.utils.FileUtils.java
com.morgan.library.utils.GB2Alpha.java
com.morgan.library.utils.HttpClientUtil.java
com.morgan.library.utils.HttpClientUtils.java
com.morgan.library.utils.HttpURLUtil.java
com.morgan.library.utils.ImageUtils.java
com.morgan.library.utils.KeyBoardUtils.java
com.morgan.library.utils.LocationUtils.java
com.morgan.library.utils.Logger.java
com.morgan.library.utils.NetUtils.java
com.morgan.library.utils.PhoneUtils.java
com.morgan.library.utils.SDCardUtils.java
com.morgan.library.utils.StrUtils.java
com.morgan.library.widget.AutoHideMenuWidget.java
com.morgan.library.widget.BadgeView.java
com.morgan.library.widget.CityPickerWidget.java
com.morgan.library.widget.CustomGridView.java
com.morgan.library.widget.CustomListView.java
com.morgan.library.widget.CustomToast.java
com.morgan.library.widget.CustomViewPager.java
com.morgan.library.widget.DatePickerWidget.java
com.morgan.library.widget.DirectionScrollView.java
com.morgan.library.widget.FallBallView.java
com.morgan.library.widget.HeightPickerWidget.java
com.morgan.library.widget.InterceptPressLayout.java
com.morgan.library.widget.ItalicTextView.java
com.morgan.library.widget.PopupMenuWidget.java
com.morgan.library.widget.PullToRefreshListView.java
com.morgan.library.widget.ScreenShotView.java
com.morgan.library.widget.SexPickerWidget.java
com.morgan.library.widget.SlideUpOpenWidget.java
com.morgan.library.widget.SwipeListView.java
com.morgan.library.widget.TimePickerWidget.java
com.morgan.library.widget.TouchZoomImageView.java
com.morgan.library.widget.WaitScreenWidget.java
com.morgan.library.widget.WebViewDialog.java
com.morgan.library.widget.WeightPickerWidget.java
com.morgan.library.widget.calendar.CalendarGridView.java
com.morgan.library.widget.calendar.CalendarScrollView.java
com.morgan.library.widget.calendar.CalendarViewFlipper.java
com.morgan.library.widget.calendar.CalendarWidget.java
com.morgan.library.widget.numberpicker.NumberPicker.java
com.morgan.library.widget.numberpicker.Scroller.java
com.morgan.library.widget.slidemenu.ScrollDetectorFactory.java
com.morgan.library.widget.slidemenu.ScrollDetectors.java
com.morgan.library.widget.slidemenu.SlideMenu.java