RecyclerView通过itemTouchHelper拖放快速拖动时很奇怪

Posted

技术标签:

【中文标题】RecyclerView通过itemTouchHelper拖放快速拖动时很奇怪【英文标题】:RecyclerView drag & drop via itemTouchHelper bahaving strange when dragging fast 【发布时间】:2016-05-18 08:36:29 【问题描述】:

我一直在关注此链接"Drag and Swipe with RecyclerView - by IPaulPro" 的指南,在某些情况下我遇到了一些问题。

所以,我基本上做了他解释的所有事情 + 我在 item 中添加了一个 TextView,它表示 RecyclerView 中 item 的位置,如下所示:

一切看起来都不错,除非我开始快速“弹射”物品,然后我有两个问题:

    它恰好有重复的数字。

    我所做的是在 onItemClear() 方法 - 它以某种方式修复它,但导致 IllegalStateException,抓住了 - 会导致 IndexOutOfBounds 异常。

    有时,当滑动过快时,项目会进入“背景”。仅当项目大小不同时才能看到这一点。

我将把我的整个适配器代码粘贴在下面,其中一定有缺陷。

LayoutInflater inflater;
Context context;
androidEntityQuestionResult androidEntityQuestionResult;
ArrayList<AndroidEntityAnswer> list = new ArrayList<>();
ORDLayoutManagerQuestion ord;
ScreenDimensionsConstants sdc;


public OrderingRecycleAdapter(Context context, AndroidEntityQuestionResult androidEntityQuestionResult, ORDLayoutManagerQuestion ord) 
    inflater = LayoutInflater.from(context);
    this.context = context;
    this.list = androidEntityQuestionResult.getAndroidEntityQuestion().getEntityAnswer();
    this.androidEntityQuestionResult = androidEntityQuestionResult;
    this.ord = ord;
    sdc = new ScreenDimensionsConstants(context);


@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
    View view = inflater.inflate(R.layout.custom_row_ordering_rv, parent, false);
    final RecyclerView.ViewHolder holder = new OrderingViewHolder(view);
    return holder;



@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) 
    if (holder instanceof OrderingViewHolder) 

        ((OrderingViewHolder) holder).answerText.setText(list.get(position).getAnswer().getANSWER_TEXT());
        int currentPosition = position + 1;
        ((OrderingViewHolder) holder).position.setText("#" + currentPosition);
    


@Override
public int getItemCount() 
    return list.size();


@Override
public boolean onItemMove(int fromPosition, int toPosition) 
    if (fromPosition < toPosition) 
        for (int i = fromPosition; i < toPosition; i++) 
            Collections.swap(list, i, i + 1);
        
     else 
        for (int i = fromPosition; i > toPosition; i--) 
            Collections.swap(list, i, i - 1);
        
    
    notifyItemMoved(fromPosition, toPosition);
    notifyItemChanged(fromPosition);
    return true;


@Override
public void onItemDismiss(int position) 



@Override
public void onStartDrag(RecyclerView.ViewHolder viewHolder) 
        ord.getItemTouchHelper().startDrag(viewHolder);


class OrderingViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder 
    private TextView answerText;
    private ImageView pin;
    private TextView position;

    public OrderingViewHolder(View itemView) 
        super(itemView);
        answerText = (TextView) itemView.findViewById(R.id.orderingAnswer);
        answerText.setTextSize(TypedValue.COMPLEX_UNIT_PX, sdc.getHeight() / 40);

        pin = (ImageView) itemView.findViewById(R.id.ordering_pin);
        pin.getLayoutParams().width = sdc.getHeight() / 15;
        pin.getLayoutParams().height = sdc.getHeight() / 15;

        pin.setOnTouchListener(new View.OnTouchListener() 
            @Override
            public boolean onTouch(View v, MotionEvent event) 
                if (MotionEventCompat.getActionMasked(event) ==
                        MotionEvent.ACTION_DOWN) 
                    OrderingRecycleAdapter.this.onStartDrag(OrderingViewHolder.this);
                
                return false;
            
        );
        position = (TextView) itemView.findViewById(R.id.answer_position);
        position.setTextSize(TypedValue.COMPLEX_UNIT_PX, sdc.getHeight() / 40);

    

    @Override
    public void onItemSelected() 
        itemView.setBackgroundResource(R.drawable.menu_item_background_ice_blue);
    

    @Override
    public void onItemClear() 
        itemView.setBackgroundResource(R.drawable.menu_item_background_white);
        int currentPosition = getLayoutPosition() + 1;
        position.setText("#" + currentPosition);
       //notifyDataSetChanged();

    

额外问题

是否有任何与 2 个 RecyclerView 之间的拖放相关的教程或任何信息?

我知道有一个关于 SO 的问题,但没有答案,我在这里可能更幸运。

【问题讨论】:

我也在使用您上面提到的链接中的代码,当我交换得太快时我也遇到了麻烦,但我通过覆盖从 ItemTouchHelper.Callback 扩展的类中的 onSelectedChanged 解决了这个问题。 你用什么代码覆盖了它?我使用了来自github.com/iPaulPro/Android-ItemTouchHelper-Demo/blob/master/… 的代码 您想发布我的代码并使用它吗?或者你想要修复? 我不想看到“我不想要代码,我想要修复”这就是我问的原因。 你能分享一下 Screendimensionsconstants 和 ORDLayoutManagerQuestion 类吗?由于 itemtouchhelper,ORDLayoutManagerQuestion 尤其令人感兴趣。也许您可以尝试在 onbindviewholder 上移动您的触摸监听器。 【参考方案1】:

改变你的 onitemmove 方法

@Override
public boolean onItemMove(int fromPosition, int toPosition) 
    if (fromPosition < toPosition) 
        for (int i = fromPosition; i < toPosition; i++) 
            Collections.swap(list, i, i + 1);
        
     else 
        for (int i = fromPosition; i > toPosition; i--) 
            Collections.swap(list, i, i - 1);
        
    
    notifyItemMoved(fromPosition, toPosition);
    notifyItemChanged(fromPosition);
    return true;

到:

@Override
public boolean onItemMove(int fromPosition, int toPosition) 
    if (fromPosition < toPosition) 
        for (int i = fromPosition; i < toPosition; i++) 
            Collections.swap(list, i, i + 1);
        
     else 
        for (int i = fromPosition; i > toPosition; i--) 
            Collections.swap(list, i, i - 1);
        
    
    notifyItemMoved(fromPosition, toPosition);
    return true;

【讨论】:

不工作,你必须以这种方式逐个位置拖动它,这不是我想要实现的。 不,您不想逐个位置拖动它,只需尝试此代码并告诉我 我的错误,它没有逐个位置移动 - 它可以在列表中移动,但在释放时它会返回其 fromPosition 并且 notifyItemRangeChanged 强制它动态更改悬停项目的值,看起来有点糟糕。 尝试只添加 notifyitemmoved 并从我的回答中删除第二个和第三个通知电话 拥有一个 notifyItemMoved(fromPosition, toPosition) 只会通知正在被拖动的项目,必须有一个关于到达 fromPosition 的项目的通知(在我的情况下是 notifyItemChanged(fromPosition)。什么 notifyItemMoved你要我添加吗?当第一个通知添加 notifyItemMoved(toPosition, fromPosition) 时,它释放时不会改变任何东西,一切都会回到原来的位置。【参考方案2】:

在我看来,您的 onItemMove() 似乎并没有考虑到所有的变化。 当某物移动不止一个项目时(假设在 A 和 B 之间),您正在交换两者之间的所有项目。 但是,您只向 A 和 B 报告更改,而不向其间的其他元素报告。

我建议你写一个交换方法来报告所有的变化:

public boolean swapItems(int fromPosition, int toPosition)
    Collections.swap(list, fromPosition, toPosition);
    notifyItemMoved(fromPosition, toPosition);
    notifyItemMoved(toPosition, fromPosition);
    // And maybe also notifyItemChanged() as the item changes due to the shift
    notifyItemChanged(fromPosition);
    notifyItemChanged(toPosition);

调用此函数而不是 Collections.swap() 并删除其余的通知代码。

【讨论】:

遗憾的是,它不起作用,当使用此代码拖动一个项目时,所有项目都会在拖动时动态更改其值,这看起来很奇怪,当释放时,一个项目会回到原来的位置。 顺便说一句,像我以前做的那样做是不是正确的——只有 A 和 B 项目改变了它们的位置,那些在它们之间的留在原地?也许我应该通知 toPosition (+1 -1) 周围的那些项目,因为它们有时会干扰,我只是找不到它发生的原因(使用一些 if 语句 if toPosition == list.size(); )【参考方案3】:

我知道它已经晚了,因为我看不到正确的答案在这里发布答案。只需在返回 true 之前添加notifyItemRangeChanged(fromPosition,toPosition)

notifyItemChanged(fromPosition);
notifyItemMoved(fromPosition, toPosition);
notifyItemRangeChanged(fromPosition,toPosition)

【讨论】:

以上是关于RecyclerView通过itemTouchHelper拖放快速拖动时很奇怪的主要内容,如果未能解决你的问题,请参考以下文章

Android列表拖动ListView~RecyclerView

Android列表拖动ListView~RecyclerView

Android 通过OnScrollListener来监听RecyclerView的位置

RecyclerView#onMeasure() 没有通过调用 setMeasuredDimension() 设置测量尺寸

通过kotlin ..通过firebase从数据库读取数据到RecyclerView的麻烦

如何通过单击 recyclerview 中的项目来打开新活动 [重复]