RecyclerView多次添加项目 [英] RecyclerView adds items multiple times

查看:39
本文介绍了RecyclerView多次添加项目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将RecyclerViewCardLayout一起使用,并且CardLayout包含多个组件.为了填充RecyclerView,我使用了Adapter,但是我的问题是,有时当我添加新项目时,该项目会多次添加到RecyclerView中.但这并不是始终可复制的行为(所以也许与线程有关?).

I use a RecyclerView with a CardLayout and the CardLayout contains multiple components. For filling the RecyclerView I use an Adapter but my problem is, that sometimes when I add new items the item is added multiple times into the RecyclerView. But it is no behavior which is always reproducible (so maybe it has to do with threading?).

我不知道原因:/对于每一个提示或建议,我都会感到很高兴.

I don't know the reason :/ I would be very glad for each hint or advice.

下面的代码是一个接近"工作的TodoManager,唯一的问题是上面说明的问题.

The code below is a "nearly" working TodoManager, the only problem is the one explained above.

我的MainActivity:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static String fileName = "todos.ser";

    private ArrayList<Todo> todos;
    private RecyclerViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);

        updateTodos();
        adapter = new RecyclerViewAdapter(todos, getWindow());

        final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.todos);
        assert recyclerView != null;
        final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setHasFixedSize(true);
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        recyclerView.setAdapter(adapter);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        assert fab != null;
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                adapter.addTodo(new Todo(""), todos.size());
                int count = adapter.getItemCount();
                recyclerView.smoothScrollToPosition(count - 1);
            }
        });
    }

    private void updateTodos() {
        todos = new ArrayList<>();
        todos.add(new Todo(""));
    }

    @Override
    public void onStop() {
        super.onStop();

        Log.i(TAG, "save todos=" + todos.size());
        // Save logic
    }
}

Todo:

public class Todo implements Serializable {

    // The creationDate is not used at the moment
    private Date creationDate;
    private String description;
    private boolean isChecked;

    public Todo(String description) {
        this.description = description;
        this.creationDate = new Date();
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public boolean isChecked() {
        return isChecked;
    }

    public void setIsChecked(boolean isChecked) {
        this.isChecked = isChecked;
    }

    public Date getCreationDate() {
        return creationDate;
    }
}

RecyclerViewAdapter:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.TodoViewHolder> {

    private static final String TAG = "RecyclerViewAdapter";

    private final List<Todo> todos;
    private final Window window;

    public RecyclerViewAdapter(List<Todo> todos, Window window) {
        this.todos = todos;
        this.window = window;
    }

    public class TodoViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, CompoundButton.OnCheckedChangeListener, TextWatcher {
        CheckBox cbDone;
        EditText tvDescription;
        FloatingActionButton btnDelete;

        public TodoViewHolder(View itemView) {
            super(itemView);

            cbDone = (CheckBox)itemView.findViewById(R.id.cbDone);
            tvDescription = (EditText) itemView.findViewById(R.id.tvDescription);
            btnDelete = (FloatingActionButton) itemView.findViewById(R.id.btnDelete);

            tvDescription.addTextChangedListener(this);
            cbDone.setOnCheckedChangeListener(this);
            btnDelete.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            Log.i(TAG, "onClick called: remove todo.");
            removeTodo(getAdapterPosition());
        }

        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            Log.i(TAG, "onCheckedChanged called: isDone=" + isChecked);
            Todo todo = getTodo(getAdapterPosition());
            todo.setIsChecked(isChecked);
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // Do nothing
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // Do nothing
        }

        @Override
        public void afterTextChanged(Editable s) {
            Log.i(TAG, "afterTextChanged called: text=" + s.toString());
            Todo todo = getTodo(getAdapterPosition());
            todo.setDescription(s.toString());
        }
    }

    public void addTodo(Todo todo, int position) {
        if (position < 0 || position > todos.size()) {
            Log.e(TAG, " add: index=" + position);
        } else {
            todos.add(position, todo);
            notifyItemInserted(position);
        }
    }

    public void removeTodo(int position) {
        if (position < 0 || position >= todos.size()) {
            Log.e(TAG, "remove: index=" + position);
        } else {
            todos.remove(position);
            notifyItemRemoved(position);
        }
    }

    public Todo getTodo(int position) {
        Todo todo = null;
        if (position < 0 || position >= todos.size()) {
            Log.e(TAG, "get: index=" + position);
        } else {
            todo = todos.get(position);
        }
        return todo;
    }

    @Override
    public TodoViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.todo_layout, viewGroup, false);
        return new TodoViewHolder(view);
    }

    @Override
    public void onBindViewHolder(TodoViewHolder holder, int position) {
        holder.cbDone.setChecked(todos.get(position).isChecked());
        holder.tvDescription.setText(todos.get(position).getDescription());

        if(holder.tvDescription.requestFocus()) {
            window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
        }
    }

    @Override
    public int getItemCount() {
        return todos.size();
    }
}

main_layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">

    <RelativeLayout
        android:id="@+id/header_layout"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:background="@color/colorPrimary"
        android:layout_alignParentTop="true">

        <TextView
            android:id="@+id/header"
            android:layout_height="match_parent"
            android:text="@string/todomanager"
            android:textColor="@android:color/white"
            android:layout_width="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_marginStart="@dimen/margin_header_text"
            android:textSize="@dimen/header_text_size"
            android:gravity="center_vertical" />

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="@dimen/margin_header_button"
            app:backgroundTint="@color/colorAccent"
            android:tint="@android:color/white"
            app:fabSize="mini"
            android:src="@android:drawable/ic_input_add"
            android:layout_alignParentEnd="true" />
    </RelativeLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/todos"
        android:layout_below="@id/header_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical" />

</RelativeLayout> 

CardView的布局:

<android.support.v7.widget.CardView
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/todo"
    android:layout_below="@id/header_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    card_view:cardElevation="5dp"
    android:layout_marginLeft="10dp"
    android:layout_marginRight="10dp"
    android:layout_marginBottom="5dp"
    android:layout_marginTop="5dp">

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="match_parent">

        <CheckBox
            android:id="@+id/cbDone"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_centerVertical="true" />

        <EditText
            android:id="@+id/tvDescription"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_toStartOf="@+id/btnDelete"
            android:layout_toEndOf="@+id/cbDone"
            android:hint="@string/insert_description"
            android:background="@android:color/transparent"
            android:padding="@dimen/padding_todo_text"
            android:layout_centerVertical="true"
            />

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/btnDelete"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/margin_todo_button"
            app:fabSize="mini"
            app:backgroundTint="@color/colorAccent"
            android:tint="@android:color/white"
            android:src="@android:drawable/ic_menu_delete"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true" />
    </RelativeLayout>
</android.support.v7.widget.CardView>

样式需要以下代码: dimens.xml:

The following code is needed for the styling: dimens.xml:

<resources>
    <dimen name="app_bar_height">60dp</dimen>
    <dimen name="fab_margin">16dp</dimen>
    <dimen name="text_margin">16dp</dimen>
    <dimen name="margin_header_text">10sp</dimen>
    <dimen name="header_text_size">24sp</dimen>
    <dimen name="margin_header_button">10sp</dimen>
    <dimen name="padding_todo_text">10dp</dimen>
    <dimen name="margin_todo_button">10sp</dimen>
</resources>

colors.xml:

<resources>
    <color name="colorPrimary">#212121</color>
    <color name="colorPrimaryDark">#000000</color>
    <color name="colorAccent">#009688</color>
</resources>

strings.xml:

<resources>
    <string name="todomanager">Todos</string>
    <string name="insert_description">insert description ...</string>
</resources>

以下屏幕截图显示了错误的行为.左边的是正确的,中间的是正确的,但是当开始滚动时,视图中会重复一些条目.

The following screenshots displays the wrong behavior. The left one is correct, the middle one is still correct but when there starts scrolling some entries are duplicated in the view.

经过一些测试,我发现这些条目是相同的条目,因此当我单击一次删除条目时,所有重复的条目也会消失.问题开始了,直到我看不到某些条目,并且滚动显示的条目重复时,问题才开始滚动.

After some tests I discovered that these entries are the same entries, so when I click once on the delete of an entry all duplicated entries also disappear. And the problem starts whit scrolling as far as I have some entries out of view and I scroll around the displayed entries are duplicated.

我还发现它是在添加第12个元素时开始的:

I also discovered that it starts when I add the 12th element:

添加第12个元素之前的logcat输出:

The logcat output before the 12th element was added:

I/MainActivity: fab.onClick count=[7]
I/RecyclerViewAdapter: afterTextChanged called: text= position=[6]
I/RecyclerViewAdapter: afterTextChanged called: text=t position=[6]
I/RecyclerViewAdapter: afterTextChanged called: text=te position=[6]
I/RecyclerViewAdapter: afterTextChanged called: text=tes position=[6]
I/RecyclerViewAdapter: afterTextChanged called: text=test position=[6]
I/RecyclerViewAdapter: afterTextChanged called: text=test5 position=[6]
I/RecyclerViewAdapter: addTodo position=[7] count=[7]
I/MainActivity: fab.onClick count=[8]
I/RecyclerViewAdapter: afterTextChanged called: text= position=[7]
I/RecyclerViewAdapter: afterTextChanged called: text=t position=[7]
I/RecyclerViewAdapter: afterTextChanged called: text=te position=[7]
I/RecyclerViewAdapter: afterTextChanged called: text=tes position=[7]
I/RecyclerViewAdapter: afterTextChanged called: text=test position=[7]
I/RecyclerViewAdapter: afterTextChanged called: text=test6 position=[7]
I/RecyclerViewAdapter: addTodo position=[8] count=[8]
I/MainActivity: fab.onClick count=[9]
I/RecyclerViewAdapter: afterTextChanged called: text= position=[8]
I/RecyclerViewAdapter: afterTextChanged called: text=t position=[8]
I/RecyclerViewAdapter: afterTextChanged called: text=te position=[8]
I/RecyclerViewAdapter: afterTextChanged called: text=tes position=[8]
I/RecyclerViewAdapter: afterTextChanged called: text=test position=[8]
I/RecyclerViewAdapter: afterTextChanged called: text=test7 position=[8]
I/RecyclerViewAdapter: addTodo position=[9] count=[9]

添加第10个项目时,第一个奇怪的行为开始,因为存在不应该存在的afterTextChanged调用以及GC空闲:

The first strange behavior starts when adding the 10th item, because there are afterTextChanged calls which should not be there and also the GC free:

I/MainActivity: fab.onClick count=[10]
I/RecyclerViewAdapter: afterTextChanged called: text= position=[9]
I/RecyclerViewAdapter: afterTextChanged called: text=t position=[9]
I/RecyclerViewAdapter: afterTextChanged called: text=te position=[9]
I/RecyclerViewAdapter: afterTextChanged called: text=tes position=[9]
I/RecyclerViewAdapter: afterTextChanged called: text=test position=[9]
I/RecyclerViewAdapter: afterTextChanged called: text=test8 position=[9]
I/RecyclerViewAdapter: afterTextChanged called: text=test3 position=[4]
I/RecyclerViewAdapter: afterTextChanged called: text=test1 position=[2]
I/RecyclerViewAdapter: afterTextChanged called: text=training position=[1]
I/RecyclerViewAdapter: afterTextChanged called: text=work position=[0]
I/art: Background partial concurrent mark sweep GC freed 52541(3MB) AllocSpace objects, 1(52KB) LOS objects, 35% free, 28MB/44MB, paused 7.486ms total 62.227ms
I/RecyclerViewAdapter: addTodo position=[10] count=[10]

当添加第11和第12个项目时,afterTextChanged调用会爆炸,并且奇怪的行为开始:

And when adding the 11th and 12th item the afterTextChanged calls explode and the strange behavior starts:

I/MainActivity: fab.onClick count=[11]
I/RecyclerViewAdapter: afterTextChanged called: text= position=[10]
I/RecyclerViewAdapter: afterTextChanged called: text=t position=[10]
I/RecyclerViewAdapter: afterTextChanged called: text=te position=[10]
I/RecyclerViewAdapter: afterTextChanged called: text=tes position=[10]
I/RecyclerViewAdapter: afterTextChanged called: text=test position=[10]
I/RecyclerViewAdapter: afterTextChanged called: text=test9 position=[10]
I/RecyclerViewAdapter: afterTextChanged called: text=test3 position=[4]
I/RecyclerViewAdapter: afterTextChanged called: text=test1 position=[2]
I/RecyclerViewAdapter: afterTextChanged called: text=training position=[1]
I/RecyclerViewAdapter: afterTextChanged called: text=work position=[0]
I/RecyclerViewAdapter: afterTextChanged called: text=work position=[0]
I/RecyclerViewAdapter: afterTextChanged called: text=test7 position=[8]
I/RecyclerViewAdapter: afterTextChanged called: text=test9 position=[10]
I/RecyclerViewAdapter: afterTextChanged called: text=training position=[1]
I/RecyclerViewAdapter: addTodo position=[11] count=[11]
I/MainActivity: fab.onClick count=[12]
I/RecyclerViewAdapter: afterTextChanged called: text= position=[11]
I/RecyclerViewAdapter: afterTextChanged called: text=t position=[11]
I/RecyclerViewAdapter: afterTextChanged called: text=te position=[11]
I/RecyclerViewAdapter: afterTextChanged called: text=tes position=[11]
I/RecyclerViewAdapter: afterTextChanged called: text=test position=[11]
I/RecyclerViewAdapter: afterTextChanged called: text=test1 position=[11]
I/RecyclerViewAdapter: afterTextChanged called: text=test10 position=[11]
I/RecyclerViewAdapter: afterTextChanged called: text=test5 position=[6]
I/RecyclerViewAdapter: afterTextChanged called: text=test3 position=[4]
I/RecyclerViewAdapter: afterTextChanged called: text=test2 position=[3]
I/RecyclerViewAdapter: afterTextChanged called: text=test1 position=[2]
I/RecyclerViewAdapter: afterTextChanged called: text=training position=[1]
I/RecyclerViewAdapter: afterTextChanged called: text=work position=[0]
I/RecyclerViewAdapter: afterTextChanged called: text=work position=[0]
I/RecyclerViewAdapter: afterTextChanged called: text=training position=[1]
I/RecyclerViewAdapter: afterTextChanged called: text=work position=[0]
I/RecyclerViewAdapter: afterTextChanged called: text=test6 position=[7]
I/RecyclerViewAdapter: afterTextChanged called: text=test8 position=[9]
I/RecyclerViewAdapter: afterTextChanged called: text=test9 position=[10]
I/RecyclerViewAdapter: afterTextChanged called: text=test10 position=[11]
I/RecyclerViewAdapter: afterTextChanged called: text=test10 position=[11]
I/RecyclerViewAdapter: afterTextChanged called: text=test9 position=[10]
I/RecyclerViewAdapter: afterTextChanged called: text=test10 position=[11]
I/RecyclerViewAdapter: afterTextChanged called: text=test4 position=[5]
I/RecyclerViewAdapter: afterTextChanged called: text=test2 position=[3]
I/RecyclerViewAdapter: afterTextChanged called: text=test1 position=[2]
I/RecyclerViewAdapter: afterTextChanged called: text=training position=[1]
I/RecyclerViewAdapter: afterTextChanged called: text=work position=[0]
I/RecyclerViewAdapter: afterTextChanged called: text=work position=[0]
I/RecyclerViewAdapter: afterTextChanged called: text=training position=[1]
I/RecyclerViewAdapter: afterTextChanged called: text=work position=[0]
I/RecyclerViewAdapter: afterTextChanged called: text=test1 position=[2]
I/RecyclerViewAdapter: afterTextChanged called: text=training position=[1]
I/RecyclerViewAdapter: afterTextChanged called: text=work position=[0]
I/RecyclerViewAdapter: afterTextChanged called: text=work position=[0]

我发现的另一个奇怪行为是我的数据正确,当我进入编辑模式时,数据看起来不正确,而当我离开编辑模式时,它又看起来还不错.

Another strange behavior which I found is that my data is correct and when I go into the edit mode the data looks wrong and when I leave edit mode it looks okay again.

推荐答案

问题出在onBindViewHolder方法中,焦点已设置,因此,当我删除焦点代码时,它会起作用.

The problem was in the onBindViewHolder method the focus was set, so when I remove the focus code it works.

@Override
public void onBindViewHolder(TodoViewHolder holder, int position) {
     holder.cbDone.setChecked(todos.get(position).isChecked());
     holder.tvDescription.setText(todos.get(position).getDescription());

     // Remove the following code
     // if(holder.tvDescription.requestFocus()) {
     //    window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
     // }
}

这篇关于RecyclerView多次添加项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆