package com.codetroopers.betterpickers.numberpicker;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.codetroopers.betterpickers.R;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
public class NumberPicker extends LinearLayout implements Button.OnClickListener,
Button.OnLongClickListener {
protected int mInputSize = 20;
protected final Button mNumbers[] = new Button[10];
protected int mInput[] = new int[mInputSize];
protected int mInputPointer = -1;
protected Button mLeft, mRight;
protected ImageButton mDelete;
protected NumberView mEnteredNumber;
protected final Context mContext;
private TextView mLabel;
private NumberPickerErrorTextView mError;
private int mSign;
private String mLabelText = "";
private Button mSetButton;
private static final int CLICKED_DECIMAL = 10;
public static final int SIGN_POSITIVE = 0;
public static final int SIGN_NEGATIVE = 1;
protected View mDivider;
private ColorStateList mTextColor;
private int mKeyBackgroundResId;
private int mButtonBackgroundResId;
private int mDividerColor;
private int mDeleteDrawableSrcResId;
private int mTheme = -1;
private BigDecimal mMinNumber = null;
private BigDecimal mMaxNumber = null;
/**
* Instantiates a NumberPicker object
*
* @param context the Context required for creation
*/
public NumberPicker(Context context) {
this(context, null);
}
/**
* Instantiates a NumberPicker object
*
* @param context the Context required for creation
* @param attrs additional attributes that define custom colors, selectors, and backgrounds.
*/
public NumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
LayoutInflater layoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(getLayoutId(), this);
// Init defaults
mTextColor = getResources().getColorStateList(R.color.dialog_text_color_holo_dark);
mKeyBackgroundResId = R.drawable.key_background_dark;
mButtonBackgroundResId = R.drawable.button_background_dark;
mDeleteDrawableSrcResId = R.drawable.ic_backspace_dark;
mDividerColor = getResources().getColor(R.color.default_divider_color_dark);
}
protected int getLayoutId() {
return R.layout.number_picker_view;
}
/**
* Change the theme of the Picker
*
* @param themeResId the resource ID of the new style
*/
public void setTheme(int themeResId) {
mTheme = themeResId;
if (mTheme != -1) {
TypedArray a = getContext().obtainStyledAttributes(themeResId, R.styleable.BetterPickersDialogFragment);
mTextColor = a.getColorStateList(R.styleable.BetterPickersDialogFragment_bpTextColor);
mKeyBackgroundResId = a.getResourceId(R.styleable.BetterPickersDialogFragment_bpKeyBackground, mKeyBackgroundResId);
mButtonBackgroundResId = a.getResourceId(R.styleable.BetterPickersDialogFragment_bpButtonBackground, mButtonBackgroundResId);
mDividerColor = a.getColor(R.styleable.BetterPickersDialogFragment_bpDividerColor, mDividerColor);
mDeleteDrawableSrcResId = a.getResourceId(R.styleable.BetterPickersDialogFragment_bpDeleteIcon, mDeleteDrawableSrcResId);
}
restyleViews();
}
private void restyleViews() {
for (Button number : mNumbers) {
if (number != null) {
number.setTextColor(mTextColor);
number.setBackgroundResource(mKeyBackgroundResId);
}
}
if (mDivider != null) {
mDivider.setBackgroundColor(mDividerColor);
}
if (mLeft != null) {
mLeft.setTextColor(mTextColor);
mLeft.setBackgroundResource(mKeyBackgroundResId);
}
if (mRight != null) {
mRight.setTextColor(mTextColor);
mRight.setBackgroundResource(mKeyBackgroundResId);
}
if (mDelete != null) {
mDelete.setBackgroundResource(mButtonBackgroundResId);
mDelete.setImageDrawable(getResources().getDrawable(mDeleteDrawableSrcResId));
}
if (mEnteredNumber != null) {
mEnteredNumber.setTheme(mTheme);
}
if (mLabel != null) {
mLabel.setTextColor(mTextColor);
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mDivider = findViewById(R.id.divider);
mError = (NumberPickerErrorTextView) findViewById(R.id.error);
for (int i = 0; i < mInput.length; i++) {
mInput[i] = -1;
}
View v1 = findViewById(R.id.first);
View v2 = findViewById(R.id.second);
View v3 = findViewById(R.id.third);
View v4 = findViewById(R.id.fourth);
mEnteredNumber = (NumberView) findViewById(R.id.number_text);
mDelete = (ImageButton) findViewById(R.id.delete);
mDelete.setOnClickListener(this);
mDelete.setOnLongClickListener(this);
mNumbers[1] = (Button) v1.findViewById(R.id.key_left);
mNumbers[2] = (Button) v1.findViewById(R.id.key_middle);
mNumbers[3] = (Button) v1.findViewById(R.id.key_right);
mNumbers[4] = (Button) v2.findViewById(R.id.key_left);
mNumbers[5] = (Button) v2.findViewById(R.id.key_middle);
mNumbers[6] = (Button) v2.findViewById(R.id.key_right);
mNumbers[7] = (Button) v3.findViewById(R.id.key_left);
mNumbers[8] = (Button) v3.findViewById(R.id.key_middle);
mNumbers[9] = (Button) v3.findViewById(R.id.key_right);
mLeft = (Button) v4.findViewById(R.id.key_left);
mNumbers[0] = (Button) v4.findViewById(R.id.key_middle);
mRight = (Button) v4.findViewById(R.id.key_right);
setLeftRightEnabled();
for (int i = 0; i < 10; i++) {
mNumbers[i].setOnClickListener(this);
mNumbers[i].setText(String.format("%d", i));
mNumbers[i].setTag(R.id.numbers_key, new Integer(i));
}
updateNumber();
Resources res = mContext.getResources();
mLeft.setText(res.getString(R.string.number_picker_plus_minus));
mRight.setText(res.getString(R.string.number_picker_seperator));
mLeft.setOnClickListener(this);
mRight.setOnClickListener(this);
mLabel = (TextView) findViewById(R.id.label);
mSign = SIGN_POSITIVE;
// Set the correct label state
showLabel();
restyleViews();
updateKeypad();
}
/**
* Using View.GONE, View.VISIBILE, or View.INVISIBLE, set the visibility of the plus/minus indicator
*
* @param visiblity an int using Android's View.* convention
*/
public void setPlusMinusVisibility(int visiblity) {
if (mLeft != null) {
mLeft.setVisibility(visiblity);
}
}
/**
* Using View.GONE, View.VISIBILE, or View.INVISIBLE, set the visibility of the decimal indicator
*
* @param visiblity an int using Android's View.* convention
*/
public void setDecimalVisibility(int visiblity) {
if (mRight != null) {
mRight.setVisibility(visiblity);
}
}
/**
* Set a minimum required number
*
* @param min the minimum required number
*/
public void setMin(BigDecimal min) {
mMinNumber = min;
}
/**
* Set a maximum required number
*
* @param max the maximum required number
*/
public void setMax(BigDecimal max) {
mMaxNumber = max;
}
/**
* Update the delete button to determine whether it is able to be clicked.
*/
public void updateDeleteButton() {
boolean enabled = mInputPointer != -1;
if (mDelete != null) {
mDelete.setEnabled(enabled);
}
}
/**
* Expose the NumberView in order to set errors
*
* @return the NumberView
*/
public NumberPickerErrorTextView getErrorView() {
return mError;
}
@Override
public void onClick(View v) {
v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
mError.hideImmediately();
doOnClick(v);
updateDeleteButton();
}
protected void doOnClick(View v) {
Integer val = (Integer) v.getTag(R.id.numbers_key);
if (val != null) {
// A number was pressed
addClickedNumber(val);
} else if (v == mDelete) {
if (mInputPointer >= 0) {
for (int i = 0; i < mInputPointer; i++) {
mInput[i] = mInput[i + 1];
}
mInput[mInputPointer] = -1;
mInputPointer--;
}
} else if (v == mLeft) {
onLeftClicked();
} else if (v == mRight) {
onRightClicked();
}
updateKeypad();
}
@Override
public boolean onLongClick(View v) {
v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
mError.hideImmediately();
if (v == mDelete) {
mDelete.setPressed(false);
reset();
updateKeypad();
return true;
}
return false;
}
private void updateKeypad() {
// Update state of keypad
// Update the number
updateLeftRightButtons();
updateNumber();
// enable/disable the "set" key
enableSetButton();
// Update the backspace button
updateDeleteButton();
}
/**
* Set the text displayed in the small label
*
* @param labelText the String to set as the label
*/
public void setLabelText(String labelText) {
mLabelText = labelText;
showLabel();
}
private void showLabel() {
if (mLabel != null) {
mLabel.setText(mLabelText);
}
}
/**
* Reset all inputs.
*/
public void reset() {
for (int i = 0; i < mInputSize; i++) {
mInput[i] = -1;
}
mInputPointer = -1;
updateNumber();
}
// Update the number displayed in the picker:
protected void updateNumber() {
String numberString = getEnteredNumberString();
numberString = numberString.replaceAll("\\-", "");
String[] split = numberString.split("\\.");
if (split.length >= 2) {
if (split[0].equals("")) {
mEnteredNumber.setNumber("0", split[1], containsDecimal(),
mSign == SIGN_NEGATIVE);
} else {
mEnteredNumber.setNumber(split[0], split[1], containsDecimal(),
mSign == SIGN_NEGATIVE);
}
} else if (split.length == 1) {
mEnteredNumber.setNumber(split[0], "", containsDecimal(),
mSign == SIGN_NEGATIVE);
} else if (numberString.equals(".")) {
mEnteredNumber.setNumber("0", "", true, mSign == SIGN_NEGATIVE);
}
}
protected void setLeftRightEnabled() {
mLeft.setEnabled(true);
mRight.setEnabled(canAddDecimal());
if (!canAddDecimal()) {
mRight.setContentDescription(null);
}
}
private void addClickedNumber(int val) {
if (mInputPointer < mInputSize - 1) {
// For 0 we need to check if we have a value of zero or not
if (mInput[0] == 0 && mInput[1] == -1 && !containsDecimal() && val != CLICKED_DECIMAL) {
mInput[0] = val;
} else {
for (int i = mInputPointer; i >= 0; i--) {
mInput[i + 1] = mInput[i];
}
mInputPointer++;
mInput[0] = val;
}
}
}
/**
* Clicking on the bottom left button will toggle the sign.
*/
private void onLeftClicked() {
if (mSign == SIGN_POSITIVE) {
mSign = SIGN_NEGATIVE;
} else {
mSign = SIGN_POSITIVE;
}
}
/**
* Clicking on the bottom right button will add a decimal point.
*/
private void onRightClicked() {
if (canAddDecimal()) {
addClickedNumber(CLICKED_DECIMAL);
}
}
private boolean containsDecimal() {
boolean containsDecimal = false;
for (int i : mInput) {
if (i == 10) {
containsDecimal = true;
}
}
return containsDecimal;
}
/**
* Checks if the user allowed to click on the right button.
*
* @return true or false if the user is able to add a decimal or not
*/
private boolean canAddDecimal() {
return !containsDecimal();
}
private String getEnteredNumberString() {
String value = "";
for (int i = mInputPointer; i >= 0; i--) {
if (mInput[i] == -1) {
// Don't add
} else if (mInput[i] == CLICKED_DECIMAL) {
value += ".";
} else {
value += mInput[i];
}
}
return value;
}
/**
* Returns the number inputted by the user
*
* @return a double representing the entered number
*/
public BigDecimal getEnteredNumber() {
String value = "0";
for (int i = mInputPointer; i >= 0; i--) {
if (mInput[i] == -1) {
break;
} else if (mInput[i] == CLICKED_DECIMAL) {
value += ".";
} else {
value += mInput[i];
}
}
if (mSign == SIGN_NEGATIVE) {
value = "-" + value;
}
return new BigDecimal(value);
}
private void updateLeftRightButtons() {
mRight.setEnabled(canAddDecimal());
}
/**
* Enable/disable the "Set" button
*/
private void enableSetButton() {
if (mSetButton == null) {
return;
}
// Nothing entered - disable
if (mInputPointer == -1) {
mSetButton.setEnabled(false);
return;
}
// If the user entered 1 digits or more
mSetButton.setEnabled(mInputPointer >= 0);
}
/**
* Expose the set button to allow communication with the parent Fragment.
*
* @param b the parent Fragment's "Set" button
*/
public void setSetButton(Button b) {
mSetButton = b;
enableSetButton();
}
/**
* Returns the number as currently inputted by the user
*
* @return an String representation of the number with no decimal
*/
public BigInteger getNumber() {
BigDecimal bigDecimal = getEnteredNumber().setScale(0, BigDecimal.ROUND_FLOOR);
return bigDecimal.toBigIntegerExact();
}
/**
* Returns the decimal following the number
*
* @return a double representation of the decimal value
*/
public double getDecimal() {
return getEnteredNumber().remainder(BigDecimal.ONE).doubleValue();
}
/**
* Returns whether the number is positive or negative
*
* @return true or false whether the number is positive or negative
*/
public boolean getIsNegative() {
return mSign == SIGN_NEGATIVE;
}
@Override
public Parcelable onSaveInstanceState() {
final Parcelable parcel = super.onSaveInstanceState();
final SavedState state = new SavedState(parcel);
state.mInput = mInput;
state.mSign = mSign;
state.mInputPointer = mInputPointer;
return state;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
final SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
mInputPointer = savedState.mInputPointer;
mInput = savedState.mInput;
if (mInput == null) {
mInput = new int[mInputSize];
mInputPointer = -1;
}
mSign = savedState.mSign;
updateKeypad();
}
public void setNumber(Integer integerPart, Double decimalPart, Integer mCurrentSign) {
if (mCurrentSign != null) {
mSign = mCurrentSign;
} else {
mSign = SIGN_POSITIVE;
}
if (decimalPart != null) {
String decimalString = doubleToString(decimalPart);
// remove "0." from the string
readAndRightDigits(TextUtils.substring(decimalString, 2, decimalString.length()));
mInputPointer++;
mInput[mInputPointer] = CLICKED_DECIMAL;
}
if (integerPart != null) {
readAndRightDigits(String.valueOf(integerPart));
}
updateKeypad();
}
private void readAndRightDigits(String digitsToRead) {
for (int i = digitsToRead.length() - 1; i >= 0; i--) {
mInputPointer++;
mInput[mInputPointer] = digitsToRead.charAt(i) - '0';
}
}
/**
* Method used to format double and avoid scientific notation x.xE-x (ex: 4.0E-4)
*
* @param value double value to format
* @return string representation of double value
*/
private String doubleToString(double value) {
// Use decimal format to avoid
DecimalFormat format = new DecimalFormat("0.0");
format.setMaximumFractionDigits(Integer.MAX_VALUE);
return format.format(value);
}
private static class SavedState extends BaseSavedState {
int mInputPointer;
int[] mInput;
int mSign;
public SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
mInputPointer = in.readInt();
int size = in.readInt();
if (size > 0) {
mInput = new int[size];
in.readIntArray(mInput);
}
mSign = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mInputPointer);
if (mInput != null) {
dest.writeInt(mInput.length);
dest.writeIntArray(mInput);
} else {
dest.writeInt(0);
}
dest.writeInt(mSign);
}
public static final Creator<SavedState> CREATOR
= new Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}