Двухстороннее связывание данных Android не работает с edittext и наблюдаемой переменной - PullRequest
0 голосов
/ 07 сентября 2018

У меня есть RecyclerView, чьи ячейки заполняются через привязку данных. Каждая ячейка представляет товар для корзины продуктов. Каждая ячейка содержит EditText, который отвечает за количество товара в корзине. Количество представлено как observableInt в моей ViewModel. Когда количество изменяется, я хочу выполнить действие, и у меня есть OnPropertyChangedCallback слушатель, установленный для параметра observableInt. Если я использую app: addTextChangedListener = "@ {cartItemVM.quantityInputTextWatcher}" , чтобы получить значение и установить его в наблюдаемое, его слушатель будет вызываться несколько раз (это также потому, что я могу изменить значение EditText от некоторых кнопок + -).

Я столкнулся с двухсторонним связыванием данных, но все еще не могу заставить его работать. Это то, что я до сих пор:

<QuantityEditText
                android:id="@+id/etQuantityInput"
                android:text="@{String.valueOf(cartItemVM.totalInCart)}"
                quantity="@={cartItemVM.totalInCart}"
                onQuantityChange="@{cartItemVM.onQuantityChange}"

Это мой пользовательский EditText:

public class QuantityEditText extends CustomEditText {

    private int quantity;
    private OnQuantityChangeListener onQuantityChangeListener;

    public interface OnQuantityChangeListener {
        void onQuantityChange(QuantityEditText view, int quantity);
    }

Это мой класс ViewModel:

@InverseBindingMethods({
        @InverseBindingMethod(type = QuantityEditText.class, attribute = "quantity")
})
public class ProductInCartObservableViewModel{
    public final ObservableInt totalInCart;

@BindingAdapter(value = {"onQuantityChange", "quantityAttrChanged"},
            requireAll = false)
    public static void setQuantityAttrChanged(QuantityEditText view,
                                        final QuantityEditText.OnQuantityChangeListener listener,
                                        final InverseBindingListener quantityChange) {
        if (quantityChange == null) {
            view.setOnQuantityChangeListener(listener);
        } else {
            view.setOnQuantityChangeListener(new QuantityEditText.OnQuantityChangeListener() {
                @Override
                public void onQuantityChange(QuantityEditText view, int quantity) {
                    if (listener != null) {
                        listener.onQuantityChange(view, quantity);
                    }
                    view.setText(String.valueOf(quantity));
                    quantityChange.onChange();
                }
            });
        }
    }

    @BindingAdapter("quantity")
    public static void setQuantity(QuantityEditText view, int quantity) {
        if (quantity != view.getQuantity()) {
            view.setQuantity(quantity);
        }
    }

    @InverseBindingAdapter(attribute = "quantity")
    public static int getQuantity(QuantityEditText view) {
        int val = 0;
        if (!TextUtils.isEmpty(view.getText().toString())) {
            try {
                val = Integer.valueOf(view.getText().toString());
            } catch (IllegalArgumentException e) {
                Timber.e(e);
            }
        }

        // Won't let the user remove product from cart using the editText
        if (val <= 0) {
            val = 1;
        }

        if (val > 150) {
            val = 150;
        }

        return val;
    }

    public QuantityEditText.OnQuantityChangeListener onQuantityChange = new QuantityEditText.OnQuantityChangeListener() {
        @Override
        public void onQuantityChange(QuantityEditText view, int quantity) {
            if (quantity <= 0) {
                quantity = 0;
            }

            if (quantity > 150) {
                quantity = 150;
            }

            totalInCart.set(quantity);
        }
    };

Реализация взята из разных мест, но я признаю, что не полностью понял процесс, поэтому объяснение также будет высоко оценено.

1 Ответ

0 голосов
/ 11 сентября 2018

Я могу придумать пару способов решения проблемы. Во-первых, следует избегать QuatityEditText и просто устанавливать номер напрямую. Я предпочитаю использовать методы преобразования для настраиваемой двусторонней привязки, потому что их легче реализовать:

@InverseMethod("stringToInt")
public static String intToString(TextView view, int oldValue, int value) {
    NumberFormat numberFormat = getNumberFormat(view);
    try {
        String inView = view.getText().toString();
        int parsed = numberFormat.parse(inView).intValue();
        if (parsed == value) {
            return view.getText().toString();
        }
    } catch (ParseException e) {
        // old number was broken
    }
    return numberFormat.format(value);
}

public static int stringToInt(TextView view, int oldValue, String value) {
    NumberFormat numberFormat = getNumberFormat(view);
    try {
        return numberFormat.parse(value).intValue();
    } catch (ParseException e) {
        view.setError("Improper number format");
        return oldValue;
    }
}

private static NumberFormat getNumberFormat(View view) {
    Resources resources= view.getResources();
    Locale locale = resources.getConfiguration().locale;
    NumberFormat format =
            NumberFormat.getNumberInstance(locale);
    if (format instanceof DecimalFormat) {
        DecimalFormat decimalFormat = (DecimalFormat) format;
        decimalFormat.setGroupingUsed(false);
    }
    return format;
}

и макет имеет:

<EditText ...
            android:id="@+id/etQuantityInput"
            android:inputType="number"
            android:text="@={Converter.intToString(etQuantityInput, cartItemVM.totalInCart, cartItemVM.totalInCart)}"/>

Если вы хотите новый КоличествоEditText, я думаю, что лучше иметь один атрибут, контролирующий его, а не текст строки и количество int:

<QuantityEditText ...
            android:id="@+id/etQuantityInput"
            android:inputType="number"
            app:quantity="@={cartItemVM.totalInCart)}"/>

Вот класс, который я собрал, чтобы количество следовало за текстом:

public class QuantityEditText extends android.support.v7.widget.AppCompatEditText {
    public QuantityEditText(Context context) {
        super(context);
        initTextWatcher();
    }

    public QuantityEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        initTextWatcher();
    }

    public QuantityEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initTextWatcher();
    }

    private int quantity;
    private OnQuantityChangeListener onQuantityChangeListener;

    void initTextWatcher() {
        addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                updateQuantityFromText();
            }
        });
    }

    public void setQuantity(int quantity) {
        if (updateQuantity(quantity)) {
            setText(getNumberFormat().format(quantity));
        }
    }

    public int getQuantity() {
        return quantity;
    }

    private boolean updateQuantity(int newQuantity) {
        if (this.quantity == newQuantity) {
            return false; // nothing to do
        }
        this.quantity = newQuantity;
        if (onQuantityChangeListener != null) {
            onQuantityChangeListener.onQuantityChange(this, quantity);
        }
        return true;
    }

    void updateQuantityFromText() {
        try {
            String inView = getText().toString();
            updateQuantity(getNumberFormat().parse(inView).intValue());
        } catch (ParseException e) {
            // Problem with the string format, so just don't update the quantity
        }
    }

    private NumberFormat getNumberFormat() {
        Resources resources = getResources();
        Locale locale = resources.getConfiguration().locale;
        NumberFormat format =
                NumberFormat.getNumberInstance(locale);
        if (format instanceof DecimalFormat) {
            DecimalFormat decimalFormat = (DecimalFormat) format;
            decimalFormat.setGroupingUsed(false);
        }
        return format;
    }

    public void setOnQuantityChangeListener(OnQuantityChangeListener listener) {
        onQuantityChangeListener = listener;
    }

    public interface OnQuantityChangeListener {
        void onQuantityChange(QuantityEditText view, int quantity);
    }
}

Вам также нужен способ установить InverseBindingListener и настроить двустороннюю привязку данных для количества:

@InverseBindingMethods(
        @InverseBindingMethod(type = QuantityEditText.class, attribute = "quantity")
)
public class BindingAdapters {
    @BindingAdapter("quantityAttrChanged")
    public static void setQuantityAttrChanged(QuantityEditText view,
            final InverseBindingListener quantityChange) {
        QuantityEditText.OnQuantityChangeListener listener = null;
        if (quantityChange != null) {
            listener = new QuantityEditText.OnQuantityChangeListener() {
                @Override
                public void onQuantityChange(QuantityEditText view, int quantity) {
                    quantityChange.onChange();
                }
            };
        }
        view.setOnQuantityChangeListener(listener);
    }
}
...