我使用RecyclerView
CardLayout
,CardLayout
包含多个组件。为了填写RecyclerView
我使用Adapter
,但我的问题是,有时当我添加新项目时,该项目会多次添加到RecyclerView
中。但这并不总是可以重现的行为(所以它可能与线程有关吗?)。
我不知道原因:/我会很高兴看到每个提示或建议。
以下代码是"几乎"工作TodoManager,唯一的问题是上面解释的问题。
我的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
:
<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>
以下屏幕截图显示了错误的行为。左边的一个是正确的,中间的一个是正确的但是当开始滚动时,一些条目在视图中重复。
经过一些测试后,我发现这些条目是相同的条目,因此当我单击删除一个条目时,所有重复的条目也会消失。并且问题开始白色滚动,只要我有一些条目不在视图中,我滚动显示的条目是重复的。
我还发现它在我添加第12个元素时开始:
添加第12个元素之前的logcat输出:
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
免费:
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
个调用会爆炸并且奇怪的行为开始了:
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]
我发现的另一个奇怪的行为是我的数据是正确的,当我进入编辑模式时,数据看起来是错误的,当我离开编辑模式时,它看起来还不错。
答案 0 :(得分:1)
问题出在焦点设置的onBindViewHolder
方法中,所以当我删除焦点代码时,它会起作用。
@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);
// }
}