Android RecyclerView DefaultItemAnimator - 奇怪的物品移动

时间:2017-06-13 09:37:58

标签: android android-layout animation android-recyclerview

我遇到一个简单的多列布局RecyclerView的问题,它在平板电脑上可以最好地重现。我已经创建了一个带有完整功能源代码的基本示例,可能更容易快速运行它,而不是试图理解我的意思;)

平板电脑(我有网格布局)的主要问题是,当隐藏特定项目并再次显示它们时,项目会被奇怪地重新排列(通过notifyItemRemoved()和notifyItemInserted())。我认为这甚至可以通过删除和插入第一项来重现。布局管理器在顶部插入一个额外的行,并从下面的第一行移动项目以填充它。 (以下示例删除并插入每个第三项,从第一项开始)

我有不同类型的项目(例如红色,绿色和蓝色项目),我想切换特定类型的项目(在示例中,您可以通过单击FloatingActionButton来切换红色项目。)

我想要修复的奇怪行为可以通过单击FAB两次而无需滚动来重现。这将首先过滤红色项目,然后再次显示它们。第二次切换过滤器时,您会注意到上面插入了一行项目,第一行中的项目被移动以填充它。 如果您在动画期间没有注意到,只需在切换过滤器后向上滚动。

我希望第一行保持原样,只有红色项目再次显示。

有人知道如何解决这个问题吗?

以下是活动代码:

File.Delete

这是布局代码:

public class MainActivity extends AppCompatActivity {

private RecyclerView recyclerView;
private TestAdapter adapter;

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

    recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

    ((FloatingActionButton) findViewById(R.id.fab)).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(final View v) {
            toggleFiltered();
        }
    });

    final int columnCount = getColumnCount(300);
    GridLayoutManager layoutManager = new GridLayoutManager(this, columnCount);

    recyclerView.setLayoutManager(layoutManager);
    adapter = new TestAdapter();
    recyclerView.setAdapter(adapter);
    recyclerView.setItemAnimator(new DefaultItemAnimator());
}

private void toggleFiltered() {
    final boolean filtered = adapter.isFiltered();
    adapter.setFiltered(!filtered);
    if(filtered) {
        adapter.notifyItemInserted(0);
        adapter.notifyItemInserted(3);
        adapter.notifyItemInserted(6);
        adapter.notifyItemInserted(9);
        adapter.notifyItemInserted(12);
    } else {
        adapter.notifyItemRemoved(0);
        adapter.notifyItemRemoved(3);
        adapter.notifyItemRemoved(6);
        adapter.notifyItemRemoved(9);
        adapter.notifyItemRemoved(12);
    }
}

int getColumnCount(final int dpThreshold){
    final Display display = getWindowManager().getDefaultDisplay();
    final DisplayMetrics outMetrics = new DisplayMetrics();
    display.getMetrics(outMetrics);

    final float density = getResources().getDisplayMetrics().density;
    final float dpWidth = outMetrics.widthPixels / density;

    return ((int) dpWidth) / dpThreshold;
}

class TestAdapter extends RecyclerView.Adapter {

    public static final int TYPE_RED = 0;
    public static final int TYPE_GREEN = 1;
    public static final int TYPE_BLUE = 2;

    private boolean filtered = false;

    public boolean isFiltered() {
        return filtered;
    }

    public void setFiltered(final boolean filtered) {
        this.filtered = filtered;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
        final View view = new View(parent.getContext());
        view.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 500));

        int color = Color.RED;
        if(viewType == TYPE_GREEN){
            color = Color.GREEN;
        } else if (viewType == TYPE_BLUE){
            color = Color.BLUE;
        }

        return new TestViewHolder(view, color);
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {

    }

    @Override
    public int getItemViewType(final int position) {
        if(!filtered) {
            if ((position + 3) % 3 == 0) {
                return TYPE_RED;
            }
            if ((position + 2) % 3 == 0) {
                return TYPE_GREEN;
            }
        } else {
            if ((position) % 2 == 0) {
                return TYPE_GREEN;
            }
        }
        return TYPE_BLUE;
    }

    @Override
    public int getItemCount() {
        if(filtered) {
            return 10;
        }
        return 15;
    }

    class TestViewHolder extends RecyclerView.ViewHolder {

        public TestViewHolder(final View itemView, final int color) {
            super(itemView);
            itemView.setBackgroundColor(color);
        }
    }
}
}

2 个答案:

答案 0 :(得分:0)

首先,我应该提到问题不在DefaultItemAnimator。如果将动画设置为null,则会有相同的行为。实际上根本没有问题。行为是预期的,因为您只是将项目插入TestAdapter的第一个位置,然后其他项目正在转移,但您的RecyclerView保持在同一位置。

所以您只需在recyclerView.scrollToPosition(0);方法的末尾添加toggleFiltered()行,这样一旦您应用了过滤,它就会向上滚动。它会按你的意愿工作。

答案 1 :(得分:0)

它不奇怪。因为你notifyItemInserted(0)。它是插入项目。不是notifyDataSetChanged()。适配器显示项目继续。它的绿色物品。你notifyItemInserted(0)插入红色,仍然适配器显示第一项是绿色(它是poisition是1)。因为适配器notifyItemInserted(0)在显示第一个项目之前已经是绿色。