Back to project page graph-android.
The source code is released under:
GNU General Public License
If you think the Android project graph-android listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * Copyright 2008 The Android Open Source Project * Copyright 2011-2012 Michael Novak <michael.novakjr@gmail.com>. *//from ww w . j a v a2 s . c o m * 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.michaelnovakjr.numberpicker; import android.content.Context; import android.content.res.TypedArray; import android.os.Handler; import android.text.InputFilter; import android.text.InputType; import android.text.Spanned; import android.text.method.NumberKeyListener; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.View.OnLongClickListener; import android.widget.TextView; import android.widget.LinearLayout; import android.widget.EditText; import android.widget.TextView.OnEditorActionListener; import java.util.Locale; import lorian.graph.android.R; /** * This class has been pulled from the Android platform source code, its an internal widget that hasn't been * made public so its included in the project in this fashion for use with the preferences screen; I have made * a few slight modifications to the code here, I simply put a MAX and MIN default in the code but these values * can still be set publicly by calling code. * * @author Google */ public class NumberPicker extends LinearLayout implements OnClickListener, OnEditorActionListener, OnFocusChangeListener, OnLongClickListener { private static final int DEFAULT_MAX = 200; private static final int DEFAULT_MIN = 0; private static final int DEFAULT_VALUE = 0; private static final boolean DEFAULT_WRAP = true; public interface OnChangedListener { void onChanged(NumberPicker picker, int oldVal, int newVal); } public interface Formatter { String toString(int value); } /* * 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(). */ public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = new NumberPicker.Formatter() { final StringBuilder mBuilder = new StringBuilder(); final java.util.Formatter mFmt = new java.util.Formatter(mBuilder); final Object[] mArgs = new Object[1]; @Override public String toString(int value) { mArgs[0] = value; mBuilder.delete(0, mBuilder.length()); mFmt.format("%02d", mArgs); return mFmt.toString(); } }; private final Handler mHandler; private final Runnable mRunnable = new Runnable() { @Override public void run() { if (mIncrement) { changeCurrent(mCurrent + 1); mHandler.postDelayed(this, mSpeed); } else if (mDecrement) { changeCurrent(mCurrent - 1); mHandler.postDelayed(this, mSpeed); } } }; private final EditText mText; private final InputFilter mNumberInputFilter; private String[] mDisplayedValues; protected int mStart; protected int mEnd; protected int mCurrent; protected int mPrevious; protected OnChangedListener mListener; protected Formatter mFormatter; protected boolean mWrap; protected long mSpeed = 300; private int mNumMaxDigitChars; private boolean mIncrement; private boolean mDecrement; public NumberPicker(Context context) { this(context, null); } public NumberPicker(Context context, AttributeSet attrs) { this(context, attrs, 0); } public NumberPicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs); setOrientation(VERTICAL); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.number_picker, this, true); mHandler = new Handler(); InputFilter inputFilter = new NumberPickerInputFilter(); mNumberInputFilter = new NumberRangeKeyListener(); mIncrementButton = (NumberPickerButton) findViewById(R.id.increment); mIncrementButton.setOnClickListener(this); mIncrementButton.setOnLongClickListener(this); mIncrementButton.setNumberPicker(this); mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement); mDecrementButton.setOnClickListener(this); mDecrementButton.setOnLongClickListener(this); mDecrementButton.setNumberPicker(this); mText = (EditText) findViewById(R.id.numpicker_input); mText.setOnFocusChangeListener(this); mText.setOnEditorActionListener(this); mText.setFilters(new InputFilter[] {inputFilter}); if (!isEnabled()) { setEnabled(false); } TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.numberpicker ); mWrap = a.getBoolean( R.styleable.numberpicker_wrap, DEFAULT_WRAP ); int start = a.getInt( R.styleable.numberpicker_startRange, DEFAULT_MIN ); int end = a.getInt( R.styleable.numberpicker_endRange, DEFAULT_MAX ); int current = a.getInt( R.styleable.numberpicker_defaultValue, DEFAULT_VALUE ); a.recycle(); a = null; if (end < start) { final int t = start; start = end; end = t; } setRangeInternal(start, end); setCurrentInternal(constrain(current, start, end)); updateTextInputType(); updateView(); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); mIncrementButton.setEnabled(enabled); mDecrementButton.setEnabled(enabled); mText.setEnabled(enabled); } public void setOnChangeListener(OnChangedListener listener) { mListener = listener; } public void setFormatter(Formatter formatter) { mFormatter = formatter; } /** * Set the range of numbers allowed for the number picker. The current * value will be automatically set to the start. * * @param start the start of the range (inclusive) * @param end the end of the range (inclusive) */ public void setRange(int start, int end) { setRangeInternal(start, end); updateView(); } /** * Specify if numbers should wrap after the edge has been reached. * * @param wrap values */ public void setWrap( boolean wrap ) { mWrap = wrap; } /** * Set the range of numbers allowed for the number picker. The current * value will be automatically set to the start. Also provide a mapping * for values used to display to the user. * * @param start the start of the range (inclusive) * @param end the end of the range (inclusive) * @param displayedValues the values displayed to the user. */ public void setRange(int start, int end, String[] displayedValues) { setRangeInternal(start, end); mDisplayedValues = displayedValues; updateView(); } private void setRangeInternal(int start, int end) { if (end < start) throw new IllegalArgumentException("End value cannot be less than the start value."); mStart = start; mEnd = end; if (mCurrent < start) { mCurrent = start; } else if (mCurrent > end) { mCurrent = end; } mNumMaxDigitChars = Integer.toString(Math.max(Math.abs(mStart), Math.abs(mEnd))).length(); updateTextInputType(); } private boolean isSignedNumberAllowed() { return (mStart < 0); } private void updateTextInputType() { int inputType = InputType.TYPE_CLASS_NUMBER; if (isSignedNumberAllowed()) inputType |= InputType.TYPE_NUMBER_FLAG_SIGNED; mText.setInputType(inputType); } public void setCurrent(int current) { setCurrentInternal(current); updateView(); } public void setCurrentAndNotify(int current) { setCurrentInternal(current); notifyChange(); updateView(); } private void setCurrentInternal(int current) { if (mStart > current) throw new IllegalArgumentException("Current value cannot be less than the range start."); if (mEnd < current) throw new IllegalArgumentException("Current value cannot be greater than the range end."); mCurrent = current; } /** * The speed (in milliseconds) at which the numbers will scroll * when the the +/- buttons are longpressed. Default is 300ms. */ public void setSpeed(long speed) { mSpeed = speed; } @Override public void onClick(View v) { validateInput(mText); if (!mText.hasFocus()) mText.requestFocus(); // now perform the increment/decrement if (R.id.increment == v.getId()) { changeCurrent(mCurrent + 1); } else if (R.id.decrement == v.getId()) { changeCurrent(mCurrent - 1); } } protected String formatNumber(int value) { return (mFormatter != null) ? mFormatter.toString(value) : String.valueOf(value); } protected void changeCurrent(int current) { // Wrap around the values if we go past the start or end if (current > mEnd) { current = mWrap ? mStart : mEnd; } else if (current < mStart) { current = mWrap ? mEnd : mStart; } mPrevious = mCurrent; mCurrent = current; notifyChange(); updateView(); } protected void notifyChange() { if (mListener != null) { mListener.onChanged(this, mPrevious, mCurrent); } } protected void updateView() { int selectPos; /* 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. */ if (mDisplayedValues == null) { int curSelectedPos = mText.getSelectionStart(); int curTextLength = mText.getText().length(); mText.setText(formatNumber(mCurrent)); /* Preserve previous cursor position (right justified) */ selectPos = mText.getText().length(); selectPos -= (curTextLength - Math.max(curSelectedPos, 0)); selectPos = Math.max(selectPos, 0); } else { mText.setText(mDisplayedValues[mCurrent - mStart]); selectPos = mText.getText().length(); } mText.setSelection(selectPos); } private static int constrain(int x, int min, int max) { return Math.min(Math.max(x, min), max); } private void validateCurrentView(CharSequence str) { int val = getSelectedPos(str.toString()); val = constrain(val, mStart, mEnd); if (mCurrent != val) { mPrevious = mCurrent; mCurrent = val; notifyChange(); } updateView(); } @Override public void onFocusChange(View v, boolean hasFocus) { /* When focus is lost check that the text field * has valid values. */ if (!hasFocus) { validateInput(v); } } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (v == mText) { validateInput(v); // Don't return true, let Android handle the soft keyboard } return false; } private void validateInput(View v) { String str = String.valueOf(((TextView) v).getText()); if ("".equals(str)) { // Restore to the old value as we don't allow empty values updateView(); } else { // Check the new value and ensure it's in range validateCurrentView(str); } } /** * We start the long click here but rely on the {@link NumberPickerButton} * to inform us when the long click has ended. */ @Override public boolean onLongClick(View v) { /* The text view may still have focus so clear it's focus which will * trigger the on focus changed and any typed values to be pulled. */ mText.clearFocus(); mText.requestFocus(); if (R.id.increment == v.getId()) { mIncrement = true; mHandler.post(mRunnable); } else if (R.id.decrement == v.getId()) { mDecrement = true; mHandler.post(mRunnable); } return true; } public void cancelIncrement() { mIncrement = false; } public void cancelDecrement() { mDecrement = false; } private static final char[] DIGIT_CHARACTERS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; private static final char[] SIGNED_DIGIT_CHARACTERS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' }; private NumberPickerButton mIncrementButton; private NumberPickerButton mDecrementButton; private class NumberPickerInputFilter implements InputFilter { @Override public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { if (mDisplayedValues == null) { return mNumberInputFilter.filter(source, start, end, dest, dstart, dend); } CharSequence filtered = String.valueOf(source.subSequence(start, end)); String result = String.valueOf(dest.subSequence(0, dstart)) + filtered + dest.subSequence(dend, dest.length()); String str = String.valueOf(result).toLowerCase(Locale.US); for (String val : mDisplayedValues) { val = val.toLowerCase(Locale.US); if (val.startsWith(str)) { return filtered; } } return ""; } } private class NumberRangeKeyListener extends NumberKeyListener { // XXX This doesn't allow for range limits when controlled by a // soft input method! @Override public int getInputType() { if (isSignedNumberAllowed()) return InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED; else return InputType.TYPE_CLASS_NUMBER; } @Override protected char[] getAcceptedChars() { if (isSignedNumberAllowed()) return SIGNED_DIGIT_CHARACTERS; else return DIGIT_CHARACTERS; } @Override public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { 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 ("".equals(result)) { return result; } if (isSignedNumberAllowed()) { /* * Don't allow the sign character if destination * position is not front of the text */ if (filtered.length() > 0 && filtered.charAt(0) == '-') { if (dstart != 0) { return ""; } } final int numSigneChars = countChar(result, '-'); /* Don't allow multiple sign characters */ if (numSigneChars > 1) { return ""; } /* Don't allow any characters before sign character */ if (numSigneChars > 0 && result.charAt(0) != '-') { return ""; } } if (mDisplayedValues == null) { int len = result.length(); if (len > 0 && result.charAt(0) == '-') { if (len > (mNumMaxDigitChars + 1)) { return ""; } } else { if (len > (mNumMaxDigitChars)) { return ""; } } return filtered; } else { final 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 > mEnd) { return ""; } else { return filtered; } } } private int countChar(String str, char c) { final int len = str.length(); int count = 0; int offset = 0; while (offset < len) { final int index = str.indexOf(c, offset); if (index < 0) break; offset += (index + 1); count++; } return count; } } private int getSelectedPos(String str) { if (mDisplayedValues == null) { if (str.equals("-")) { return 0; } try { return Integer.parseInt(str); } catch (NumberFormatException e) { return mCurrent; } } else { for (int i = 0; i < mDisplayedValues.length; i++) { /* Don't force the user to type in jan when ja will do */ str = str.toLowerCase(Locale.US); if (mDisplayedValues[i].toLowerCase(Locale.US).startsWith(str)) { return mStart + 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(str); } catch (NumberFormatException e) { /* Ignore as if it's not a number we don't care */ } } return mStart; } /** * @return the current value. */ public int getCurrent() { return mCurrent; } }