RecyclerView Adapter 上 TextWatchers 方法的无限循环

时间:2021-01-05 21:13:46

标签: android android-recyclerview android-edittext

我试图在 RecyclerView 的单个元素上实现将整数转换为十进制,反之亦然的简单功能(它对我更高级的问题的简化),在每个用户输入上都存在。问题是,当我在 cons* 内部调用 public String formatDuration(Duration duration) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("h:mm.SSS"); return LocalTime.ofNanoOfDay(duration.toNanos()).format(formatter); } 时,它最终会陷入 RecyclerView.Adapter.notifyItemChanged() 方法的无限循环中。有没有办法实现这种行为?我想更新我在 TextWatcher.afterTextChanged() 中的原始项目。

代码如下:

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> 的方法。我在 TextWatchers 之前禁用它们,然后在 ViewHolder 中启用它们。

notifyItemChanged

它对我有用。

我也将通话更改为 onBindViewHolder 来自

public void disableWatchers() { integer.removeTextChangedListener(integerWatcher); decimal.removeTextChangedListener(decimalWatcher); } public void enableWatchers() { integer.addTextChangedListener(integerWatcher); decimal.addTextChangedListener(decimalWatcher); } notifyItemChanged

从 UI 线程调用此方法使 TestElementAdapter.this.recyclerView.post(() -> notifyItemChanged(position)) 响应不够快。它也可以正常工作,但仅适用于所有元素都可以适合我的 TestElementAdapter.this.notifyItemChanged(position) 的数量,如果有更多元素我收到异常:EditText。有什么办法可以让 RecyclerView 中的 java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling... 离开并避免这个错误?

更新的适配器代码:

TestElementAdapter.this.notifyItemChanged(position)

1 个答案:

答案 0 :(得分:1)

listernes 重复的原因是您调用 notifyItemChanged() 添加一个新的监听器,同时在调用 notifyItemChanged() 后保持当前项的当前监听器处于活动状态。

要解决此问题,您需要在使用 notifyItemChanged() 调用 editText.removeTextChangedListener() 之前禁用侦听器,然后使用 editText.addTextChangedListener() 重新启用它们。

然后你得到了

<块引用>

IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling

这是一个 common issue,您可以通过发布 recyclerView 更改主线程循环使用来解决该问题。

但是请确保您在此帖子中添加了侦听器以在 recyclerView 更改后进行同步,这是更改:

@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()));
    
    // remove the listeners
    viewHolder.disableWatchers();

    recyclerView.post(() -> {
        TestElementAdapter.this.notifyItemChanged(position)
        // add the listeners
        viewHolder.enableWatchers();
    });

    System.out.println("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;
        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();
            
            recyclerView.post(() -> {
                TestElementAdapter.this.notifyItemChanged(position)
                viewHolder.enableWatchers();
            });

            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);
        }
    }
}

相关问题