【问题标题】:RecyclerView and ItemTouchHelper swipe to remove issueRecyclerView 和 ItemTouchHelper 滑动以消除问题
【发布时间】:2016-01-12 03:52:57
【问题描述】:

我正在尝试通过 RecyclerView 和 ItemTouchHelper 实现“滑动删除”功能。我有一个奇怪的问题,我一生都找不到这个问题。我从顶部(不是第一个)滑动一个项目,它消失了,到目前为止一切都很好。当我滚动并返回时,在被刷掉的项目上方的行中有一个人工制品。看起来该行未绘制(或者可能 x 已翻译?)。视频显示了问题。

视频步骤:

  1. 我刷掉了第 2 项
  2. 向下滚动到底部
  3. 回来
  4. 第 1 项不再可见
  5. 我再次向下滚动到底部
  6. 我向上滚动
  7. 现在一切都很好
  8. 同样,但从顶部开始第三个(不是第二个)项目,同样的问题
  9. 同样,对于第一个项目,没有问题

相关代码:(整个github示例应用here

public class MainActivity extends AppCompatActivity {

RecyclerView mRecyclerView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
    mRecyclerView.setAdapter(new TestAdapter());
    setUpItemTouchHelper();

}

private void setUpItemTouchHelper() {
    ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {

        @Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
            return false;
        }

        @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
            int swipedPosition = viewHolder.getAdapterPosition();
            TestAdapter adapter = (TestAdapter)mRecyclerView.getAdapter();
            adapter.remove(swipedPosition);
        }

    };
    ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
    itemTouchHelper.attachToRecyclerView(mRecyclerView);
}

static class TestAdapter extends RecyclerView.Adapter {

    List<String> items;

    public TestAdapter() {
        items = new ArrayList<>();
        // this should give us a couple of screens worth
        for (int i=1; i<= 15; i++) {
            items.add("Item " + i);
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new TestViewHolder(parent);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        TestViewHolder viewHolder = (TestViewHolder)holder;
        String item = items.get(position);
        viewHolder.titleTextView.setText(item);
    }

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

    public void remove(int position) {
        if (position < 0 || position >= items.size()) {
            return;
        }
        items.remove(position);
        notifyItemRemoved(position);
    }
}

static class TestViewHolder extends RecyclerView.ViewHolder {

    TextView titleTextView;

    public TestViewHolder(ViewGroup parent) {
        super(LayoutInflater.from(parent.getContext()).inflate(R.layout.row_view, parent, false));
        titleTextView = (TextView) itemView.findViewById(R.id.title_text_view);
    }

}

}

编辑:

我有一个可以消除此故障的 hack,但我仍然想知道原因以及如何才能真正解决此问题。黑客正在调用 notifyDataSetChanged() 但在动画完成之后(否则动画会终止)。基本上我添加了一个 ItemDecorator 并确定动画结束了。

mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {

        boolean running;

        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            if (parent.getItemAnimator().isRunning()) {
                running = true;
            }
            if (running == true && !parent.getItemAnimator().isRunning()) {
                // first time it's not running
                running = false;
                parent.getAdapter().notifyDataSetChanged();
            }
            super.onDraw(c, parent, state);
        }
});

【问题讨论】:

    标签: android android-recyclerview


    【解决方案1】:

    尝试在您的删除方法中添加notifyDataSetChanged()

    public void remove(int position) {
        if (position < 0 || position >= items.size()) {
            return;
        }
        items.remove(position);
        notifyItemRemoved(position);
        notifyDataSetChanged();
    }
    

    notifyItemRemoved(position) 通知 RecyclerView 适配器适配器中的数据已在特定位置被删除。

    notifyDataSetChanged() 通知附加的观察者基础数据已更改,任何反映数据集的视图都应自行刷新。

    更新

    尝试在notifyItemRemoved(position); 之前添加mRecyclerView.removeViewAt(position); 这不会影响动画。

    public class MainActivity extends AppCompatActivity {
    
        RecyclerView mRecyclerView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
            mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
            mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
            mRecyclerView.setAdapter(new TestAdapter());
            setUpItemTouchHelper();
    
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.menu_main, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            if (item.getItemId() == R.id.menu_item_add_5_items) {
                ((TestAdapter)mRecyclerView.getAdapter()).addItems(5);
            }
            return super.onOptionsItemSelected(item);
        }
    
        private void setUpItemTouchHelper() {
            ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
    
                @Override
                public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                    return false;
                }
    
                @Override
                public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
                    int swipedPosition = viewHolder.getAdapterPosition();
                    TestAdapter adapter = (TestAdapter)mRecyclerView.getAdapter();
                    adapter.remove(swipedPosition);
                }
    
            };
            ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
            itemTouchHelper.attachToRecyclerView(mRecyclerView);
        }
    
        class TestAdapter extends RecyclerView.Adapter {
    
            List<String> items;
            int lastInsertedIndex;
    
            public TestAdapter() {
                items = new ArrayList<>();
                lastInsertedIndex = 15;
                // this should give us a couple of screens worth
                for (int i=1; i<= lastInsertedIndex; i++) {
                    items.add("Item " + i);
                }
            }
    
            @Override
            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                return new TestViewHolder(parent);
            }
    
            @Override
            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
                TestViewHolder viewHolder = (TestViewHolder)holder;
                String item = items.get(position);
                viewHolder.titleTextView.setText(item);
            }
    
            @Override
            public int getItemCount() {
                return items.size();
            }
    
            public void addItems(int howMany){
                if (howMany > 0) {
                    for (int i = lastInsertedIndex + 1; i <= lastInsertedIndex + howMany; i++) {
                        items.add("Item " + i);
                        notifyItemInserted(items.size() - 1);
                    }
                    lastInsertedIndex = lastInsertedIndex + howMany;
                }
            }
    
            public void remove(int position) {
                if (position < 0 || position >= items.size()) {
                    return;
                }
                items.remove(position);
                mRecyclerView.removeViewAt(position);
                notifyItemRemoved(position);
            }
        }
    
        static class TestViewHolder extends RecyclerView.ViewHolder {
    
            TextView titleTextView;
    
            public TestViewHolder(ViewGroup parent) {
                super(LayoutInflater.from(parent.getContext()).inflate(R.layout.row_view, parent, false));
                titleTextView = (TextView) itemView.findViewById(R.id.title_text_view);
            }
    
        }
    
    }
    

    【讨论】:

    • 谢谢,但 notifyDataSetChanged() 会弄乱删除动画,所以这不是我正在寻找的解决方案。有效的技巧是等待动画完成,然后调用 notifyDataSetChanged() 但没有回调,我需要猜测。请参阅我的问题的编辑以了解我是如何做到的。不过,这不是我愿意留在生产代码中的东西。
    • 不完全是。适配器中的位置不会将 1 映射到 1 与回收站视图的子项中的位置。您可以在适配器中拥有数百个项目,但您只能拥有 x 个适合一个屏幕的视图以及更多。您的修复程序只需稍作修改即可工作:如果我在 onSwipe() 而不是 removeViewAt(position) 而是 removeView(view) 这样调用它 - mRecyclerView.removeView(viewHolder.itemView);无论如何,这也可能只是一个“黑客”解决方案,这个问题是最近引入的,绝对是一个错误。看我的回答。非常感谢您的帮助!
    • 好吧。这似乎是一个错误。
    【解决方案2】:

    更新: 23.1.1 修复了该错误。不要使用 23.1.0。

    看起来这个错误是回收器视图支持库中的回归。我认为这是 commit 造成的,但仍然没有 100% 了解情况,所以不要坚持。

    该错误在com.android.support:recyclerview-v7 版本23.1.0 中出现,但在23.0.122.2.1 中不出现

    我会尝试找到一个正确的地方来报告它,并将链接发布在这个答案的评论中。

    【讨论】:

      【解决方案3】:

      我想建议您查看该链接,因为它是实现此功能的最简单方法 Drag and swipe with RecyclerView using ItemTouchHelper

      【讨论】:

      • 本文第 2 部分中的到底是什么位置不能从 0 升级到 1 甚至更多...我在 onItemMore 中修复了这个问题,.. fromPosition? toPosition-1: toPosition... 中需要删除 -1
      【解决方案4】:

      我在滑动重新排序时遇到了类似的问题。 RecyclerView 留下了视图的“幽灵”,只是在移动到该位置的视图下方移动。 Random 将“notifyDataSetChanged()”添加到我的 onItemMoved 方法的答案有效,但这会破坏交换动画。当我在短暂延迟后运行 notifyDataSetChanged 时,问题就解决了:

          notifyItemMoved(position1, position2);
          getActivity().getHandler().postDelayed(new Runnable() {
              @Override
              public void run() {
                  notifyDataSetChanged();
              }
          }, 300);
      

      【讨论】:

        【解决方案5】:

        我能够通过在 recyclerview 上再次设置适配器来解决这个问题。从您的列表中删除后,只需....

        RecyclerView.setAdapter(this);

        【讨论】:

          猜你喜欢
          • 2017-02-07
          • 1970-01-01
          • 2019-09-27
          • 2017-12-11
          • 1970-01-01
          • 1970-01-01
          • 2022-01-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多