How to handle swipe-to-remove on recyclerView correctly?

筅森魡賤 提交于 2020-02-04 03:58:06

问题


Background

I'm trying to allow to swipe to remove items of the recycler view, but for some reason it doesn't always play nicely, showing empty spaces instead of the cards.

I've made the code handle both flinging and moving the item, to trigger the animation of the swiping, and when the swiping animation ends, the item is removed from the dataset and notifies the adapter too.

Maybe it's because I'm new to RecyclerView, but I can't find what's missing.

The code

 public class MainActivity extends ActionBarActivity
    {
    private RecyclerView mRecyclerView;
    private LinearLayoutManager mLayoutManager;
    private MyAdapter mAdapter;
    private static final int DATA_COUNT=100;
    private ArrayList<String> mDataSet;

    @Override
    protected void onCreate(final Bundle savedInstanceState)
      {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      mRecyclerView=(RecyclerView)findViewById(R.id.my_recycler_view);
      mRecyclerView.setHasFixedSize(true);
      mLayoutManager=new LinearLayoutManager(this);
  // TODO in case we use GridLayoutManager, consider using this: http://stackoverflow.com/q/26869312/878126
      mRecyclerView.setLayoutManager(mLayoutManager);
      mDataSet=new ArrayList<String>(DATA_COUNT);
      for(int i=0;i<DATA_COUNT;++i)
        mDataSet.add(Integer.toString(i));
      mAdapter=new MyAdapter(mDataSet);
      mRecyclerView.setAdapter(mAdapter);
      }

    // ///////////////////////////////////////////////////////////////
  // MyAdapter//
  // ///////////
    public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
      {
      private final ArrayList<String> mDataset;

      public class ItemViewType
        {
        private static final int HEADER=0, ITEM=1;
        }

      public MyAdapter(final ArrayList<String> myDataset)
        {
        mDataset=myDataset;
        }

      @Override
      public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent,final int viewType)
        {
        final RecyclerView.ViewHolder holder;
        final View rootView;
        switch(viewType)
          {
          case ItemViewType.HEADER:
            rootView=LayoutInflater.from(parent.getContext()).inflate(R.layout.header,parent,false);
            holder=new HeaderViewHoler(rootView);
            break;
          case ItemViewType.ITEM:
            rootView=LayoutInflater.from(parent.getContext()).inflate(R.layout.card,parent,false);
            holder=new ItemViewHolder(rootView);
            rootView.setAlpha(1);
            rootView.setTranslationX(0);
            rootView.setTranslationY(0);
            handleSwiping(rootView,holder);
            break;
          default:
            holder=null;
            break;
          }
        return holder;
        }

      private void handleSwiping(final View rootView,final RecyclerView.ViewHolder holder)
        {
        final GestureDetectorCompat gestureDetector=new GestureDetectorCompat(rootView.getContext(),
            new GestureDetector.OnGestureListener()
            {
            ...      
            @Override
            public boolean onFling(final MotionEvent e1,final MotionEvent e2,final float velocityX,
                                   final float velocityY)
              {
              final int viewSwipeThreshold=rootView.getWidth()/4;
              if(velocityX<-viewSwipeThreshold)
                {
                onSwipe(rootView,holder.getPosition(),false);
                return true;
                }
              else if(velocityX>viewSwipeThreshold)
                {
                onSwipe(rootView,holder.getPosition(),true);
                return true;
                }
              return false;
              }
            });
        rootView.setOnTouchListener(new View.OnTouchListener()
        {
        private final float originalX=0;
        private final float originalY=0;
        private float startMoveX=0;
        private float startMoveY=0;

        @Override
        public boolean onTouch(final View view,final MotionEvent event)
          {
          final int viewSwipeHorizontalThreshold=rootView.getWidth()/3;
          final int viewSwipeVerticalThreshold=view.getContext().getResources()
              .getDimensionPixelSize(R.dimen.vertical_swipe_threshold);
          if(gestureDetector.onTouchEvent(event))
            return true;
          final float x=event.getRawX(), y=event.getRawY();
          final float deltaX=x-startMoveX, deltaY=y-startMoveY;
          switch(event.getAction()&MotionEvent.ACTION_MASK)
            {
            case MotionEvent.ACTION_DOWN:
              startMoveX=x;
              startMoveY=y;
              break;
            case MotionEvent.ACTION_UP:
              if(Math.abs(deltaX)<viewSwipeHorizontalThreshold)
                {
                rootView.animate().translationX(originalX).translationY(originalY).alpha(1).start();
                if(Math.abs(deltaY)<viewSwipeHorizontalThreshold)
                  rootView.performClick();
                }
              else if(deltaX<0)
                onSwipe(rootView,holder.getPosition(),true);
              else
                onSwipe(rootView,holder.getPosition(),false);
              break;
            case MotionEvent.ACTION_CANCEL:
              if(Math.abs(deltaX)<viewSwipeHorizontalThreshold
                  ||Math.abs(deltaY)<viewSwipeVerticalThreshold)
                rootView.animate().translationX(originalX).translationY(originalY).alpha(1).start();
              else if(deltaX<0)
                onSwipe(rootView,holder.getPosition(),true);
              else
                onSwipe(rootView,holder.getPosition(),false);
              break;
            case MotionEvent.ACTION_POINTER_DOWN:
              break;
            case MotionEvent.ACTION_POINTER_UP:
              break;
            case MotionEvent.ACTION_MOVE:
              rootView.setAlpha(Math.max(Math.min((255-Math.abs(deltaX))/255f,1.0f),0.1f));
              rootView.setTranslationX(deltaX);
              break;
            }
          return true;
          }
        });

        }

      @Override
      public void onBindViewHolder(final RecyclerView.ViewHolder holder,final int position)
        {
        final int itemViewType=getItemViewType(position);
        final View rootView=holder.itemView;
        rootView.setAlpha(1);
        rootView.setTranslationX(0);
        rootView.setTranslationY(0);
        }

      private void onSwipe(final View rootView,final int position,final boolean isToLeft)
        {
        ViewPropertyAnimator animator;
        if(isToLeft)
          animator=rootView.animate().translationX(-rootView.getWidth());
        else
          animator=rootView.animate().translationX(rootView.getWidth());
        animator.setListener(new Animator.AnimatorListener()
        {
        @Override
        public void onAnimationStart(Animator animation)
          {
          }

        @Override
        public void onAnimationEnd(Animator animation)
          {
          rootView.setAlpha(1);
          mDataset.remove(position);
          notifyItemRemoved(position);
          }

        @Override
        public void onAnimationCancel(Animator animation)
          {
          }

        @Override
        public void onAnimationRepeat(Animator animation)
          {
          }
        });
        animator.start();
        }

      @Override
      public int getItemCount()
        {
        return mDataset.size()+1;
        }

      @Override
      public int getItemViewType(final int position)
        {
        return position==0?ItemViewType.HEADER:ItemViewType.ITEM;
        }
      }

  // ///////////////////////////////////////
  // HeaderViewHoler //
  // //////////////////

    public static class HeaderViewHoler extends RecyclerView.ViewHolder
      {
      public TextView mTextView;

      public HeaderViewHoler(final View v)
        {
        super(v);
        }
      }

    // ///////////////////////////////////////
  // ItemViewHolder //
  // /////////////////
    public static class ItemViewHolder extends RecyclerView.ViewHolder
      {

      public ItemViewHolder(final View rootView)
        {
        super(rootView);
        rootView.setAlpha(1);
        rootView.setTranslationX(0);
        rootView.setTranslationY(0);
        }
      }
    }

The question

What is wrong in what I did? How come it sometimes works well and sometimes doesn't?

Is there maybe a better solution for the swipe-to-remove handling?


回答1:


You cannot access the position parameter in the callback because RecyclerView will not rebind a ViewHolder just because its position has changed. Removing an item changes the position of all items below it so all of your position references for those items will be obsolete.

Instead, you can use ViewHolder#getPosition to get the up to date position at the time of the user action.

In addition to that, do not add the gesture listener and touch listener in onBind, instead, add them when you create the ViewHolder. This way, you'll avoid creating a new object each time an item is rebound.

Update for the comment. Suggested changes:

@Override
    public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
        RecyclerView.ViewHolder holder = null;
        View rootView;
        switch (viewType) {
        case ItemViewType.HEADER:
            rootView = LayoutInflater.from(parent.getContext()).inflate(R.layout.header, parent, false);
            holder = new HeaderViewHoler(rootView);
            break;
        case ItemViewType.ITEM:
            rootView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card, parent, false);
            holder = new ItemViewHolder(rootView);
            //initialize gesture detector and touch listener, replace position with getPosiiton
        }
        return holder;
    }


来源:https://stackoverflow.com/questions/27708503/how-to-handle-swipe-to-remove-on-recyclerview-correctly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!