1b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette/*
2b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * Copyright (C) 2015 The Android Open Source Project
3b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette *
4b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * Licensed under the Apache License, Version 2.0 (the "License");
5b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * you may not use this file except in compliance with the License.
6b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * You may obtain a copy of the License at
7b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette *
8b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette *      http://www.apache.org/licenses/LICENSE-2.0
9b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette *
10b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * Unless required by applicable law or agreed to in writing, software
11b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * distributed under the License is distributed on an "AS IS" BASIS,
12b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * See the License for the specific language governing permissions and
14b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * limitations under the License.
15b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette */
16b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
17b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverettepackage com.android.internal.widget;
18b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
19b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viveretteimport android.content.Context;
20b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viveretteimport android.graphics.Rect;
21b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viveretteimport android.util.AttributeSet;
22b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viveretteimport android.util.StateSet;
23b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viveretteimport android.view.KeyEvent;
24b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viveretteimport android.widget.TextView;
25b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
26b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette/**
27b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * Extension of TextView that can handle displaying and inputting a range of
28b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * numbers.
29b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * <p>
30b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * Clients of this view should never call {@link #setText(CharSequence)} or
31b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * {@link #setHint(CharSequence)} directly. Instead, they should call
32b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette * {@link #setValue(int)} to modify the currently displayed value.
33b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette */
34b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverettepublic class NumericTextView extends TextView {
35b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private static final int RADIX = 10;
36b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private static final double LOG_RADIX = Math.log(RADIX);
37b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
38b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private int mMinValue = 0;
39b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private int mMaxValue = 99;
40b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
41b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    /** Number of digits in the maximum value. */
42b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private int mMaxCount = 2;
43b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
44b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private boolean mShowLeadingZeroes = true;
45b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
46b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private int mValue;
47b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
48b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    /** Number of digits entered during editing mode. */
49b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private int mCount;
50b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
51b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    /** Used to restore the value after an aborted edit. */
52b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private int mPreviousValue;
53b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
54b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private OnValueChangedListener mListener;
55b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
56b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public NumericTextView(Context context, AttributeSet attrs) {
57b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        super(context, attrs);
58b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
59b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        // Generate the hint text color based on disabled state.
60b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        final int textColorDisabled = getTextColors().getColorForState(StateSet.get(0), 0);
61b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        setHintTextColor(textColorDisabled);
62b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
63b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        setFocusable(true);
64b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
65b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
66b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    @Override
67b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
68b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        super.onFocusChanged(focused, direction, previouslyFocusedRect);
69b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
70b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        if (focused) {
71b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            mPreviousValue = mValue;
72b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            mValue = 0;
73b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            mCount = 0;
74b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
75b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            // Transfer current text to hint.
76b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            setHint(getText());
77b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            setText("");
78b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        } else {
79b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            if (mCount == 0) {
80b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                // No digits were entered, revert to previous value.
81b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                mValue = mPreviousValue;
82b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
83b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                setText(getHint());
84b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                setHint("");
85b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            }
86b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
87b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            // Ensure the committed value is within range.
88b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            if (mValue < mMinValue) {
89b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                mValue = mMinValue;
90b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            }
91b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
92b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            setValue(mValue);
93b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
94b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            if (mListener != null) {
95b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                mListener.onValueChanged(this, mValue, true, true);
96b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            }
97b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        }
98b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
99b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
100b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    /**
101b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * Sets the currently displayed value.
102b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * <p>
103b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * The specified {@code value} must be within the range specified by
104b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * {@link #setRange(int, int)} (e.g. between {@link #getRangeMinimum()}
105b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * and {@link #getRangeMaximum()}).
106b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     *
107b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * @param value the value to display
108b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     */
109b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public final void setValue(int value) {
110b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        if (mValue != value) {
111b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            mValue = value;
112b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
113b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            updateDisplayedValue();
114b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        }
115b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
116b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
117b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    /**
118b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * Returns the currently displayed value.
119b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * <p>
120b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * If the value is currently being edited, returns the live value which may
121b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * not be within the range specified by {@link #setRange(int, int)}.
122b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     *
123b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * @return the currently displayed value
124b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     */
125b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public final int getValue() {
126b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        return mValue;
127b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
128b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
129b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    /**
130b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * Sets the valid range (inclusive).
131b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     *
132b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * @param minValue the minimum valid value (inclusive)
133b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * @param maxValue the maximum valid value (inclusive)
134b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     */
135b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public final void setRange(int minValue, int maxValue) {
136b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        if (mMinValue != minValue) {
137b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            mMinValue = minValue;
138b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        }
139b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
140b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        if (mMaxValue != maxValue) {
141b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            mMaxValue = maxValue;
142b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            mMaxCount = 1 + (int) (Math.log(maxValue) / LOG_RADIX);
143b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
144b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            updateMinimumWidth();
145b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            updateDisplayedValue();
146b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        }
147b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
148b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
149b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    /**
150b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * @return the minimum value value (inclusive)
151b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     */
152b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public final int getRangeMinimum() {
153b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        return mMinValue;
154b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
155b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
156b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    /**
157b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * @return the maximum value value (inclusive)
158b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     */
159b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public final int getRangeMaximum() {
160b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        return mMaxValue;
161b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
162b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
163b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    /**
164b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * Sets whether this view shows leading zeroes.
165b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * <p>
166b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * When leading zeroes are shown, the displayed value will be padded
167b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * with zeroes to the width of the maximum value as specified by
168b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * {@link #setRange(int, int)} (see also {@link #getRangeMaximum()}.
169b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * <p>
170b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * For example, with leading zeroes shown, a maximum of 99 and value of
171b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * 9 would display "09". A maximum of 100 and a value of 9 would display
172b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * "009". With leading zeroes hidden, both cases would show "9".
173b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     *
174b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * @param showLeadingZeroes {@code true} to show leading zeroes,
175b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     *                          {@code false} to hide them
176b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     */
177b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public final void setShowLeadingZeroes(boolean showLeadingZeroes) {
178b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        if (mShowLeadingZeroes != showLeadingZeroes) {
179b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            mShowLeadingZeroes = showLeadingZeroes;
180b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
181b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            updateDisplayedValue();
182b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        }
183b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
184b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
185b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public final boolean getShowLeadingZeroes() {
186b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        return mShowLeadingZeroes;
187b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
188b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
189b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    /**
190b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * Computes the display value and updates the text of the view.
191b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * <p>
192b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * This method should be called whenever the current value or display
193b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * properties (leading zeroes, max digits) change.
194b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     */
195b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private void updateDisplayedValue() {
196b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        final String format;
197b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        if (mShowLeadingZeroes) {
198b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            format = "%0" + mMaxCount + "d";
199b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        } else {
200b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            format = "%d";
201b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        }
202b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
203b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        // Always use String.format() rather than Integer.toString()
204b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        // to obtain correctly localized values.
205b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        setText(String.format(format, mValue));
206b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
207b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
208b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    /**
209b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * Computes the minimum width in pixels required to display all possible
210b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * values and updates the minimum width of the view.
211b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * <p>
212b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     * This method should be called whenever the maximum value changes.
213b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette     */
214b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private void updateMinimumWidth() {
215b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        final CharSequence previousText = getText();
216b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        int maxWidth = 0;
217b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
218b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        for (int i = 0; i < mMaxValue; i++) {
219b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            setText(String.format("%0" + mMaxCount + "d", i));
220b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
221b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
222b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            final int width = getMeasuredWidth();
223b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            if (width > maxWidth) {
224b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                maxWidth = width;
225b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            }
226b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        }
227b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
228b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        setText(previousText);
229b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        setMinWidth(maxWidth);
230b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        setMinimumWidth(maxWidth);
231b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
232b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
233b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public final void setOnDigitEnteredListener(OnValueChangedListener listener) {
234b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        mListener = listener;
235b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
236b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
237b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public final OnValueChangedListener getOnDigitEnteredListener() {
238b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        return mListener;
239b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
240b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
241b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    @Override
242b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public boolean onKeyDown(int keyCode, KeyEvent event) {
243b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        return isKeyCodeNumeric(keyCode)
244b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                || (keyCode == KeyEvent.KEYCODE_DEL)
245b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                || super.onKeyDown(keyCode, event);
246b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
247b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
248b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    @Override
249b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
250b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        return isKeyCodeNumeric(keyCode)
251b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                || (keyCode == KeyEvent.KEYCODE_DEL)
252b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                || super.onKeyMultiple(keyCode, repeatCount, event);
253b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
254b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
255b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    @Override
256b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public boolean onKeyUp(int keyCode, KeyEvent event) {
257b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        return handleKeyUp(keyCode)
258b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                || super.onKeyUp(keyCode, event);
259b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
260b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
261b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private boolean handleKeyUp(int keyCode) {
262b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        if (keyCode == KeyEvent.KEYCODE_DEL) {
263b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            // Backspace removes the least-significant digit, if available.
264b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            if (mCount > 0) {
265b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                mValue /= RADIX;
266b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                mCount--;
267b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            }
268b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        } else if (isKeyCodeNumeric(keyCode)) {
269b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            if (mCount < mMaxCount) {
270b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                final int keyValue = numericKeyCodeToInt(keyCode);
271b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                final int newValue = mValue * RADIX + keyValue;
272b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                if (newValue <= mMaxValue) {
273b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                    mValue = newValue;
274b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                    mCount++;
275b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                }
276b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            }
277b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        } else {
278b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            return false;
279b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        }
280b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
281b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        final String formattedValue;
282b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        if (mCount > 0) {
283b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            // If the user types 01, we should always show the leading 0 even if
284b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            // getShowLeadingZeroes() is false. Preserve typed leading zeroes by
285b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            // using the number of digits entered as the format width.
286b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            formattedValue = String.format("%0" + mCount + "d", mValue);
287b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        } else {
288b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            formattedValue = "";
289b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        }
290b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
291b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        setText(formattedValue);
292b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
293b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        if (mListener != null) {
294b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            final boolean isValid = mValue >= mMinValue;
295b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            final boolean isFinished = mCount >= mMaxCount || mValue * RADIX > mMaxValue;
296b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette            mListener.onValueChanged(this, mValue, isValid, isFinished);
297b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        }
298b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
299b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        return true;
300b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
301b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
302b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private static boolean isKeyCodeNumeric(int keyCode) {
303b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        return keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
304b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
305b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
306b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
307b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette                || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9;
308b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
309b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
310b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    private static int numericKeyCodeToInt(int keyCode) {
311b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        return keyCode - KeyEvent.KEYCODE_0;
312b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
313b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette
314b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    public interface OnValueChangedListener {
315b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        /**
316b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette         * Called when the value displayed by {@code view} changes.
317b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette         *
318b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette         * @param view the view whose value changed
319b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette         * @param value the new value
320b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette         * @param isValid {@code true} if the value is valid (e.g. within the
321b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette         *                range specified by {@link #setRange(int, int)}),
322b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette         *                {@code false} otherwise
323b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette         * @param isFinished {@code true} if the no more digits may be entered,
324b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette         *                   {@code false} if more digits may be entered
325b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette         */
326b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette        void onValueChanged(NumericTextView view, int value, boolean isValid, boolean isFinished);
327b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette    }
328b3f24639902e71d4da3b2aa4eff25e75e9ce7711Alan Viverette}
329