Android Numberpicker decimals
Asked Answered
R

3

6

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.

Recipience answered 31/5, 2016 at 21:0 Comment(1)
Or you can have 2 Number pickers. One for the units and the other one for the decimals.Alula
U
4

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: enter image description here 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.

Unawares answered 5/9, 2017 at 6:4 Comment(0)
P
6

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;
Pentastich answered 31/5, 2016 at 21:8 Comment(5)
But if the max value is 100, do I have to create a custom string to 100, like 0.1, 0.2...99.8,99.9,100?Recipience
Then I would go with two spinners or even edit field. It would be really bad UX with one spinnerPentastich
That's a solution but, numberpicker can't be formated to increment in decimals?Recipience
I don't get, you already asked it. No it is only for integersPentastich
Ok, I will go with your solution.Recipience
U
4

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: enter image description here 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.

Unawares answered 5/9, 2017 at 6:4 Comment(0)
T
1

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 Doubles 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) }
)
Teide answered 12/1, 2020 at 12:41 Comment(2)
This almost worked for me except when 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 fieldScabrous
@Scabrous Feel free to checkout how I used my code in my project as it's open source: github.com/Flinesoft/FitnessTracker-Android/… Note that I have not experienced any such problems in my project. I hope it helps!Teide

© 2022 - 2024 — McMap. All rights reserved.