【发布时间】:2021-01-05 21:13:46
【问题描述】:
我正在尝试实现将整数转换为十进制的简单功能,反之亦然(它简化了我更高级的问题),在 RecyclerView 的单个元素上,每个用户输入都存在。问题是当我在TextWatcher.afterTextChanged() 内部调用RecyclerView.Adapter.notifyItemChanged() 时,它最终会进入TextWatcher 方法的无限循环。有没有办法实现这种行为?我想更新 TextWatcher.afterTextChanged() 中的原始项目。
代码如下:
public class TestElementAdapter extends RecyclerView.Adapter<TestElementAdapter.TestElementViewHolder> {
private final Context context;
private final RecyclerView recyclerView;
private List<TestElement> testElements;
public TestElementAdapter(Context context, List<TestElement> testElements, RecyclerView recyclerView) {
this.context = context;
this.testElements = testElements;
this.recyclerView = recyclerView;
testElements.add(new TestElement(1, 1.0F));
}
private class MyTextWatcher<T> implements TextWatcher {
int position = 0;
BiConsumer<TestElement, T> consumer;
Function<String, T> mappingFunction;
public MyTextWatcher(BiConsumer<TestElement, T> consumer, Function<String, T> mappingFunction) {
this.consumer = consumer;
this.mappingFunction = mappingFunction;
}
@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) {
if (s == null || s.length() == 0) {
return;
}
TestElement testElement = testElements.get(position);
consumer.accept(testElement, mappingFunction.apply(s.toString()));
TestElementAdapter.this.recyclerView.post(() -> notifyItemChanged(position));
System.out.println("afterTextChanged");
}
public void updatePosition(int adapterPosition) {
this.position = adapterPosition;
}
}
@NonNull
@Override
public TestElementViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.test_element, parent, false);
TestElementViewHolder holder = new TestElementViewHolder(view);
holder.initTextWatchers();
return holder;
}
@Override
public void onBindViewHolder(@NonNull TestElementViewHolder holder, int position) {
TestElement testElement = testElements.get(position);
holder.integer.getText().clear();
holder.decimal.getText().clear();
holder.integer.append(testElement.getInteger() + "");
holder.decimal.append(testElement.getDecimal() + "");
holder.watchers.forEach(watcher -> watcher.updatePosition(holder.getAdapterPosition()));
}
@Override
public int getItemCount() {
return testElements.size();
}
public static class TestElement {
private Integer integer;
private Float decimal;
public TestElement(Integer integer, Float decimal) {
this.integer = integer;
this.decimal = decimal;
}
public Integer getInteger() { return integer; }
public Float getDecimal() { return decimal; }
public void updateByInteger(Integer integer) {
this.integer = integer;
this.decimal = integer * 1.0F;
}
public void updateByDecimal(Float decimal) {
this.decimal = decimal;
this.integer = decimal.intValue();
}
}
public class TestElementViewHolder extends RecyclerView.ViewHolder {
EditText integer;
EditText decimal;
List<MyTextWatcher<?>> watchers = new ArrayList<>();
private MyTextWatcher<Integer> integerWatcher;
private MyTextWatcher<Float> decimalWatcher;
public TestElementViewHolder(@NonNull View view) {
super(view);
integer = view.findViewById(R.id.integerNumber);
decimal = view.findViewById(R.id.decimalNumber);
}
public void initTextWatchers() {
MyTextWatcher<Integer> integerWatcher = new MyTextWatcher<>(this, TestElement::updateByInteger, Integer::parseInt);
integer.addTextChangedListener(integerWatcher);
this.integerWatcher = integerWatcher;
MyTextWatcher<Float> decimalWatcher = new MyTextWatcher<>(this, TestElement::updateByDecimal, Float::parseFloat);
decimal.addTextChangedListener(decimalWatcher);
this.decimalWatcher = decimalWatcher;
watchers.add(this.integerWatcher);
watchers.add(this.decimalWatcher);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Light"
android:orientation="vertical"
android:gravity="top">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" android:id="@+id/linearLayout2">
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="number"
android:padding="8dp"
android:id="@+id/integerNumber"
android:gravity="center"
android:layout_weight="1"
/>
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:padding="8dp"
android:id="@+id/decimalNumber"
android:gravity="center"
android:layout_weight="1"
/>
</LinearLayout>
</LinearLayout>
更新:
我在ViewHolder 中添加了禁用和启用TextWatchers 的方法。我在notifyItemChanged 之前禁用它们,然后在onBindViewHolder 中启用它们。
public void disableWatchers() {
integer.removeTextChangedListener(integerWatcher);
decimal.removeTextChangedListener(decimalWatcher);
}
public void enableWatchers() {
integer.addTextChangedListener(integerWatcher);
decimal.addTextChangedListener(decimalWatcher);
}
它对我有用。
我还把电话改为notifyItemChanged from
TestElementAdapter.this.recyclerView.post(() -> notifyItemChanged(position))
到
TestElementAdapter.this.notifyItemChanged(position).
从 UI 线程调用此方法使 EditText 响应不足。它也可以正常工作,但仅适用于我的RecyclerView 上的所有元素数量,如果有更多元素我得到异常:java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling...。有什么办法可以在TextWatcher.afterTextChanged 中留下电话TestElementAdapter.this.notifyItemChanged(position) 并避免此错误?
更新的适配器代码:
public class TestElementAdapter extends RecyclerView.Adapter<TestElementAdapter.TestElementViewHolder> {
private final Context context;
private final RecyclerView recyclerView;
private List<TestElement> testElements;
public TestElementAdapter(Context context, List<TestElement> testElements, RecyclerView recyclerView) {
this.context = context;
this.testElements = testElements;
this.recyclerView = recyclerView;
IntStream.range(0, 20).forEach(v -> testElements.add(new TestElement(1, 1.0F)));
}
private class MyTextWatcher<T> implements TextWatcher {
int position = 0;
TestElementViewHolder viewHolder;
BiConsumer<TestElement, T> consumer;
Function<String, T> mappingFunction;
public MyTextWatcher(TestElementViewHolder viewHolder, BiConsumer<TestElement, T> consumer, Function<String, T> mappingFunction) {
this.viewHolder = viewHolder;
this.consumer = consumer;
this.mappingFunction = mappingFunction;
}
@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) {
if (s == null || s.length() == 0) {
return;
}
TestElement testElement = testElements.get(position);
consumer.accept(testElement, mappingFunction.apply(s.toString()));
viewHolder.disableWatchers();
TestElementAdapter.this.notifyItemChanged(position);
System.out.println("afterTextChanged");
}
public void updatePosition(int adapterPosition) {
this.position = adapterPosition;
}
}
@NonNull
@Override
public TestElementViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.test_element, parent, false);
TestElementViewHolder holder = new TestElementViewHolder(view);
holder.initTextWatchers();
holder.disableWatchers();
return holder;
}
@Override
public void onBindViewHolder(@NonNull TestElementViewHolder holder, int position) {
TestElement testElement = testElements.get(position);
holder.integer.getText().clear();
holder.decimal.getText().clear();
holder.integer.append(testElement.getInteger() + "");
holder.decimal.append(testElement.getDecimal() + "");
holder.watchers.forEach(watcher -> watcher.updatePosition(holder.getAdapterPosition()));
holder.enableWatchers();
}
@Override
public int getItemCount() {
return testElements.size();
}
public static class TestElement {
private Integer integer;
private Float decimal;
public TestElement(Integer integer, Float decimal) {
this.integer = integer;
this.decimal = decimal;
}
public Integer getInteger() { return integer; }
public Float getDecimal() { return decimal; }
public void updateByInteger(Integer integer) {
this.integer = integer;
this.decimal = integer * 1.0F;
}
public void updateByDecimal(Float decimal) {
this.decimal = decimal;
this.integer = decimal.intValue();
}
}
public class TestElementViewHolder extends RecyclerView.ViewHolder {
EditText integer;
EditText decimal;
List<MyTextWatcher<?>> watchers = new ArrayList<>();
private MyTextWatcher<Integer> integerWatcher;
private MyTextWatcher<Float> decimalWatcher;
public TestElementViewHolder(@NonNull View view) {
super(view);
integer = view.findViewById(R.id.integerNumber);
decimal = view.findViewById(R.id.decimalNumber);
}
public void initTextWatchers() {
MyTextWatcher<Integer> integerWatcher = new MyTextWatcher<>(this, TestElement::updateByInteger, Integer::parseInt);
integer.addTextChangedListener(integerWatcher);
this.integerWatcher = integerWatcher;
MyTextWatcher<Float> decimalWatcher = new MyTextWatcher<>(this, TestElement::updateByDecimal, Float::parseFloat);
decimal.addTextChangedListener(decimalWatcher);
this.decimalWatcher = decimalWatcher;
watchers.add(this.integerWatcher);
watchers.add(this.decimalWatcher);
}
public void disableWatchers() {
integer.removeTextChangedListener(integerWatcher);
decimal.removeTextChangedListener(decimalWatcher);
}
public void enableWatchers() {
integer.addTextChangedListener(integerWatcher);
decimal.addTextChangedListener(decimalWatcher);
}
}
}
【问题讨论】:
-
您可以在调用
notifyItemChanged之前尝试停止观察者监听器并重新启用它们。 -
感谢您的提示,我尝试了此解决方案,它需要对我的代码进行一些更改,但它确实有效。我在调用
notifyItemChanged之前禁用所有TextWatchers,然后在onBindViewHolder中启用它们。另外,我将电话recyclerView.post(() -> notifyItemChanged)更改为TestElementAdapter.this.notifyItemChanged。它适用于所有可以放入 RecyclerView 的元素数量,但是当有更多元素时,我得到IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling,。有什么办法可以避免吗? -
你还在
recyclerView.post消息中打电话给notifyItemChanged吗? -
不,我将其替换为
RecyclerView.Adapter.notifyItemChanged,否则EditText无法正常工作。在recyclerView.post之间完成的所有更改以及消息的实际执行都被丢弃了,因为ViewHolder得到了更新。 -
可以分享这部分的更新代码
标签: android android-recyclerview android-edittext