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