I'm trying to create a numberpicker in Android but the wheel only increase by 1. I want to increase by 0.1. I've looked up on the net but I've found a formated array of floats disabling the wheel. Please help and sorry for the grammar, I'm learning.
You can create your own DecimalPicker view based on ElegantNumberButton. Add following classes to your project:
/path/to/your/DecimalPicker.java
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
import ru.alanov.cashbox.R;
import ru.alanov.cashbox.Utils;
public class DecimalPicker extends RelativeLayout {
private Context context;
private AttributeSet attrs;
private int styleAttr;
private OnClickListener mListener;
private double initialNumber, finalNumber, lastNumber, currentNumber;
private EditText editText;
private String format;
private OnValueChangeListener onValueChangeListener;
public DecimalPicker(Context context) {
super(context);
this.context = context;
initView();
}
public DecimalPicker(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
this.attrs = attrs;
initView();
}
public DecimalPicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
this.attrs = attrs;
this.styleAttr = defStyleAttr;
initView();
}
private void initView(){
inflate(context, R.layout.decimal_picker, this);
final Resources res = getResources();
final int defaultColor = res.getColor(R.color.colorPrimary);
final int defaultTextColor = res.getColor(R.color.colorText);
final Drawable defaultDrawable = res.getDrawable(R.drawable.decimal_picker_shape);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DecimalPicker, styleAttr, 0);
initialNumber = a.getInt(R.styleable.DecimalPicker_initialNumber, 0);
finalNumber = a.getInt(R.styleable.DecimalPicker_finalNumber, Integer.MAX_VALUE);
float textSize = a.getDimension(R.styleable.DecimalPicker_textSize, 24);
int color = a.getColor(R.styleable.DecimalPicker_backGroundColor,defaultColor);
int textColor = a.getColor(R.styleable.DecimalPicker_textColor,defaultTextColor);
Drawable drawable = a.getDrawable(R.styleable.DecimalPicker_backgroundDrawable);
Button buttonMinus = (Button) findViewById(R.id.subtract_btn);
Button buttonPlus = (Button) findViewById(R.id.add_btn);
editText = (EditText) findViewById(R.id.number_counter);
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_NEXT) {
String num = ((EditText) v).getText().toString();
setNumber(num, true);
}
return false;
}
});
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String value = s.toString().trim();
double valueDouble = -1;
try {
valueDouble = parseDouble(value.isEmpty() ? "0" : value);
} catch (NumberFormatException e) {
e.printStackTrace();
}
if (valueDouble >= 0){
lastNumber = currentNumber;
currentNumber = valueDouble;
callListener(DecimalPicker.this);
}
}
});
LinearLayout mLayout = (LinearLayout) findViewById(R.id.decimal_picker_layout);
buttonMinus.setTextColor(textColor);
buttonPlus.setTextColor(textColor);
editText.setTextColor(textColor);
buttonMinus.setTextSize(textSize);
buttonPlus.setTextSize(textSize);
editText.setTextSize(textSize);
if (drawable == null){
drawable = defaultDrawable;
}
assert drawable != null;
drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC));
if (Build.VERSION.SDK_INT > 16)
mLayout.setBackground(drawable);
else
mLayout.setBackgroundDrawable(drawable);
editText.setText(String.valueOf(initialNumber));
currentNumber = initialNumber;
lastNumber = initialNumber;
buttonMinus.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View mView) {
double num = parseDouble(editText.getText().toString());
setNumber(String.valueOf(num - 1)/*, true*/);
}
});
buttonPlus.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View mView) {
double num = parseDouble(editText.getText().toString());
setNumber(String.valueOf(num + 1)/*, true*/);
}
});
a.recycle();
}
private void callListener(View view){
if (mListener != null)
mListener.onClick(view);
if (onValueChangeListener != null && lastNumber != currentNumber)
onValueChangeListener.onValueChange(this, lastNumber, currentNumber);
}
public String getNumber(){
return String.valueOf(currentNumber);
}
public void setNumber(String number) {
try {
double n = parseDouble(number);
if (n > finalNumber)
n = finalNumber;
if (n < initialNumber)
n = initialNumber;
if (format != null) {
String num = String.format(Utils.getCurrentLocale(getContext()), format, n);
num = removeTrailingZeroes(num);
editText.setText(num);
} else
editText.setText(String.valueOf(number));
lastNumber = currentNumber;
currentNumber = parseDouble(editText.getText().toString());
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
private double parseDouble(String str) throws NumberFormatException {
return Double.parseDouble(str.replace(",","."));
}
private String removeTrailingZeroes(String num) {
NumberFormat nf = NumberFormat.getInstance();
if (nf instanceof DecimalFormat) {
DecimalFormatSymbols sym = ((DecimalFormat) nf).getDecimalFormatSymbols();
char decSeparator = sym.getDecimalSeparator();
String[] split = num.split((decSeparator == '.' ? "\\" : "") + String.valueOf(decSeparator));
if (split.length == 2 && split[1].replace("0", "").isEmpty())
num = split[0];
}
return num;
}
public void setNumber(String number, boolean notifyListener){
setNumber(number);
if (notifyListener)
callListener(this);
}
public void setOnClickListener(OnClickListener onClickListener) {
mListener = onClickListener;
}
public void setOnValueChangeListener(OnValueChangeListener onValueChangeListener){
this.onValueChangeListener = onValueChangeListener;
}
public interface OnClickListener {
void onClick(View view);
}
public interface OnValueChangeListener {
void onValueChange(DecimalPicker view, double oldValue, double newValue);
}
public void setRange(Double startingNumber, Double endingNumber) {
initialNumber = startingNumber;
finalNumber = endingNumber;
}
public void setFormat(String format){
this.format = format;
}
}
/layout/decimal_picker.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:id="@+id/decimal_picker_layout"
android:layout_height="wrap_content">
<Button
android:id="@+id/subtract_btn"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:text="-"
android:background="@android:color/transparent" />
<EditText
android:id="@+id/number_counter"
android:gravity="center"
android:imeOptions="flagNoExtractUi"
android:inputType="numberDecimal"
android:text = "1"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@android:color/transparent" />
<Button
android:id="@+id/add_btn"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:text="+"
android:background="@android:color/transparent" />
</LinearLayout>
/drawable/decimal_picker_shape.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="4dp" />
</shape>
/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DecimalPicker">
<attr name="backGroundColor" format="color"/>
<attr name="initialNumber" format="integer"/>
<attr name="finalNumber" format="integer" />
<attr name="textColor" format="color"/>
<attr name="textSize" format="dimension"/>
<attr name="backgroundDrawable" format="reference"/>
<attr name="decimalFormat" format="reference"/>
</declare-styleable>
</resources>
In /values/colors.xml add <color name="colorText">#FFFFFF</color>
Usage example:
In your activity layout place
<path.to.your.DecimalPicker
android:id="@+id/decimal_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"/>
In your Activity#onCreate()
place
DecimalPicker decimalPicker = (DecimalPicker) view.findViewById(R.id.decimal_picker);
decimalPicker.setFormat("%.3f");//Weight format
decimalPicker.setOnValueChangeListener(new DecimalPicker.OnValueChangeListener() {
@Override
public void onValueChange(DecimalPicker picker, double oldValue, double newValue) {
//Do what you want to handle value change.
}
});
The result of efforts: Click on +/- will change integer part of number. If you want to change fractional part - click on number and edit it by hands. If there is no fractional part you will see a integer part only without zeroes.
You can do it with custom strings:
NumberPicker picker = new NumberPicker(this);
picker.setMinValue(0);
picker.setMaxValue(100);
picker.setDisplayedValues( new String[] { "0.0", "0.1", ..., "10.0" } );
double = picker.getValue() / 10.0;
You can create your own DecimalPicker view based on ElegantNumberButton. Add following classes to your project:
/path/to/your/DecimalPicker.java
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
import ru.alanov.cashbox.R;
import ru.alanov.cashbox.Utils;
public class DecimalPicker extends RelativeLayout {
private Context context;
private AttributeSet attrs;
private int styleAttr;
private OnClickListener mListener;
private double initialNumber, finalNumber, lastNumber, currentNumber;
private EditText editText;
private String format;
private OnValueChangeListener onValueChangeListener;
public DecimalPicker(Context context) {
super(context);
this.context = context;
initView();
}
public DecimalPicker(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
this.attrs = attrs;
initView();
}
public DecimalPicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
this.attrs = attrs;
this.styleAttr = defStyleAttr;
initView();
}
private void initView(){
inflate(context, R.layout.decimal_picker, this);
final Resources res = getResources();
final int defaultColor = res.getColor(R.color.colorPrimary);
final int defaultTextColor = res.getColor(R.color.colorText);
final Drawable defaultDrawable = res.getDrawable(R.drawable.decimal_picker_shape);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DecimalPicker, styleAttr, 0);
initialNumber = a.getInt(R.styleable.DecimalPicker_initialNumber, 0);
finalNumber = a.getInt(R.styleable.DecimalPicker_finalNumber, Integer.MAX_VALUE);
float textSize = a.getDimension(R.styleable.DecimalPicker_textSize, 24);
int color = a.getColor(R.styleable.DecimalPicker_backGroundColor,defaultColor);
int textColor = a.getColor(R.styleable.DecimalPicker_textColor,defaultTextColor);
Drawable drawable = a.getDrawable(R.styleable.DecimalPicker_backgroundDrawable);
Button buttonMinus = (Button) findViewById(R.id.subtract_btn);
Button buttonPlus = (Button) findViewById(R.id.add_btn);
editText = (EditText) findViewById(R.id.number_counter);
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_NEXT) {
String num = ((EditText) v).getText().toString();
setNumber(num, true);
}
return false;
}
});
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String value = s.toString().trim();
double valueDouble = -1;
try {
valueDouble = parseDouble(value.isEmpty() ? "0" : value);
} catch (NumberFormatException e) {
e.printStackTrace();
}
if (valueDouble >= 0){
lastNumber = currentNumber;
currentNumber = valueDouble;
callListener(DecimalPicker.this);
}
}
});
LinearLayout mLayout = (LinearLayout) findViewById(R.id.decimal_picker_layout);
buttonMinus.setTextColor(textColor);
buttonPlus.setTextColor(textColor);
editText.setTextColor(textColor);
buttonMinus.setTextSize(textSize);
buttonPlus.setTextSize(textSize);
editText.setTextSize(textSize);
if (drawable == null){
drawable = defaultDrawable;
}
assert drawable != null;
drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC));
if (Build.VERSION.SDK_INT > 16)
mLayout.setBackground(drawable);
else
mLayout.setBackgroundDrawable(drawable);
editText.setText(String.valueOf(initialNumber));
currentNumber = initialNumber;
lastNumber = initialNumber;
buttonMinus.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View mView) {
double num = parseDouble(editText.getText().toString());
setNumber(String.valueOf(num - 1)/*, true*/);
}
});
buttonPlus.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View mView) {
double num = parseDouble(editText.getText().toString());
setNumber(String.valueOf(num + 1)/*, true*/);
}
});
a.recycle();
}
private void callListener(View view){
if (mListener != null)
mListener.onClick(view);
if (onValueChangeListener != null && lastNumber != currentNumber)
onValueChangeListener.onValueChange(this, lastNumber, currentNumber);
}
public String getNumber(){
return String.valueOf(currentNumber);
}
public void setNumber(String number) {
try {
double n = parseDouble(number);
if (n > finalNumber)
n = finalNumber;
if (n < initialNumber)
n = initialNumber;
if (format != null) {
String num = String.format(Utils.getCurrentLocale(getContext()), format, n);
num = removeTrailingZeroes(num);
editText.setText(num);
} else
editText.setText(String.valueOf(number));
lastNumber = currentNumber;
currentNumber = parseDouble(editText.getText().toString());
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
private double parseDouble(String str) throws NumberFormatException {
return Double.parseDouble(str.replace(",","."));
}
private String removeTrailingZeroes(String num) {
NumberFormat nf = NumberFormat.getInstance();
if (nf instanceof DecimalFormat) {
DecimalFormatSymbols sym = ((DecimalFormat) nf).getDecimalFormatSymbols();
char decSeparator = sym.getDecimalSeparator();
String[] split = num.split((decSeparator == '.' ? "\\" : "") + String.valueOf(decSeparator));
if (split.length == 2 && split[1].replace("0", "").isEmpty())
num = split[0];
}
return num;
}
public void setNumber(String number, boolean notifyListener){
setNumber(number);
if (notifyListener)
callListener(this);
}
public void setOnClickListener(OnClickListener onClickListener) {
mListener = onClickListener;
}
public void setOnValueChangeListener(OnValueChangeListener onValueChangeListener){
this.onValueChangeListener = onValueChangeListener;
}
public interface OnClickListener {
void onClick(View view);
}
public interface OnValueChangeListener {
void onValueChange(DecimalPicker view, double oldValue, double newValue);
}
public void setRange(Double startingNumber, Double endingNumber) {
initialNumber = startingNumber;
finalNumber = endingNumber;
}
public void setFormat(String format){
this.format = format;
}
}
/layout/decimal_picker.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:id="@+id/decimal_picker_layout"
android:layout_height="wrap_content">
<Button
android:id="@+id/subtract_btn"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:text="-"
android:background="@android:color/transparent" />
<EditText
android:id="@+id/number_counter"
android:gravity="center"
android:imeOptions="flagNoExtractUi"
android:inputType="numberDecimal"
android:text = "1"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@android:color/transparent" />
<Button
android:id="@+id/add_btn"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:text="+"
android:background="@android:color/transparent" />
</LinearLayout>
/drawable/decimal_picker_shape.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="4dp" />
</shape>
/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DecimalPicker">
<attr name="backGroundColor" format="color"/>
<attr name="initialNumber" format="integer"/>
<attr name="finalNumber" format="integer" />
<attr name="textColor" format="color"/>
<attr name="textSize" format="dimension"/>
<attr name="backgroundDrawable" format="reference"/>
<attr name="decimalFormat" format="reference"/>
</declare-styleable>
</resources>
In /values/colors.xml add <color name="colorText">#FFFFFF</color>
Usage example:
In your activity layout place
<path.to.your.DecimalPicker
android:id="@+id/decimal_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"/>
In your Activity#onCreate()
place
DecimalPicker decimalPicker = (DecimalPicker) view.findViewById(R.id.decimal_picker);
decimalPicker.setFormat("%.3f");//Weight format
decimalPicker.setOnValueChangeListener(new DecimalPicker.OnValueChangeListener() {
@Override
public void onValueChange(DecimalPicker picker, double oldValue, double newValue) {
//Do what you want to handle value change.
}
});
The result of efforts: Click on +/- will change integer part of number. If you want to change fractional part - click on number and edit it by hands. If there is no fractional part you will see a integer part only without zeroes.
Alternatively you can use this handy NumberPicker
Kotlin extension dialog which scales your Double
values into a fitting Int
range and converts the Int
values back to Double
s before calling any of the callback. It basicallly hides away the fact that NumberPicker
only supports Int
and adds support for Double
!
Here's the Fragment extension you need to copy & paste:
fun Fragment.showNumberPickerDialog(
title: String,
value: Double,
range: ClosedRange<Double>,
stepSize: Double,
formatToString: (Double) -> String,
valueChooseAction: (Double) -> Unit
) {
val numberPicker = NumberPicker(context).apply {
setFormatter { formatToString(it.toDouble() * stepSize) }
wrapSelectorWheel = false
minValue = (range.start / stepSize).toInt()
maxValue = (range.endInclusive / stepSize).toInt()
this.value = (value.toDouble() / stepSize).toInt()
// NOTE: workaround for a bug that rendered the selected value wrong until user scrolled, see also: https://mcmap.net/q/1170853/-android-numberpicker-default-value-not-coming/3451975
(NumberPicker::class.java.getDeclaredField("mInputText").apply { isAccessible = true }.get(this) as EditText).filters = emptyArray()
}
MaterialAlertDialogBuilder(context)
.setTitle(title)
.setView(numberPicker)
.setPositiveButton("OK") { _, _ -> valueChooseAction(numberPicker.value.toDouble() * stepSize) }
.setNeutralButton("Cancel") { _, _ -> /* do nothing, closes dialog automatically */ }
.show()
}
Then use it like this:
showNumberPickerDialog(
title = "Your Weight",
value = 75.0, // in kilograms
range = 10.0 .. 300.0,
formatToString = { "$it kg" },
valueChooseAction = { saveNewWeight(it) }
)
stepSize = 0.1
I would get values that were much longer like 4.5
came out fine, but 4.6
would come out as 4.6000000000000005
, and then 4.7
would be fine but 4.8
wouldn't. I also wasn't exactly sure how to get the NumberPickerDialog
to appear except by using a button
, would be nice to do that from a spinner
or some other input field –
Scabrous © 2022 - 2024 — McMap. All rights reserved.