RecyclerView自定义列表拖拽删除

幻昼 2021年07月23日 197次浏览

基础使用

  1. 定义Activity和item布局

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp">
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </RelativeLayout>
    
    <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="270dp"
        android:layout_height="330dp"
        android:clickable="true"
        android:foreground="?android:attr/selectableItemBackground"
        app:cardBackgroundColor="#393F4E"
        app:cardCornerRadius="16dp"
        app:cardPreventCornerOverlap="false">
    
    
        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/iv"
            android:layout_width="170dp"
            android:layout_height="200dp"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"
            android:src="@mipmap/ic_launcher" />
    
        <TextView
            android:id="@+id/tvName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="210dp"
            android:paddingTop="10dp"
            android:text="名字"
            android:textColor="@color/white" />
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|center_horizontal"
            android:layout_marginBottom="20dp"
            android:background="#F05079"
            android:text="喜欢" />
    
        <TextView
            android:id="@+id/tvPrecent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right|bottom"
            android:layout_margin="3dp"
            android:textColor="@android:color/white"
            tools:text="1/13" />
    
    
    </androidx.cardview.widget.CardView>
    
  2. 设置Adapter和ViewHolder

    这里使用一个通用模板,不参照按照日常使用那样就行,可跳过

    封装的Adapter

    public abstract class UniversalAdapter<T> extends RecyclerView.Adapter<ViewHolder> {
        protected Context mContext;
        protected int mLayoutId;
        protected List<T> mDatas;
        protected LayoutInflater mInflater;
        protected ViewGroup mRv;
        private OnItemClickListener mOnItemClickListener;
    
        public UniversalAdapter setOnItemClickListener(OnItemClickListener onItemClickListener) {
            this.mOnItemClickListener = onItemClickListener;
            return this;
        }
    
        public OnItemClickListener getmOnItemClickListener() {
            return this.mOnItemClickListener;
        }
    
        public UniversalAdapter(Context context, List<T> datas, int layoutId) {
            this.mContext = context;
            this.mInflater = LayoutInflater.from(context);
            this.mLayoutId = layoutId;
            this.mDatas = datas;
        }
    
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            ViewHolder viewHolder = ViewHolder.get(this.mContext, (View)null, parent, this.mLayoutId);
            if(null == this.mRv) {
                this.mRv = parent;
            }
    
            return viewHolder;
        }
    
        protected int getPosition(RecyclerView.ViewHolder viewHolder) {
            return viewHolder.getAdapterPosition();
        }
    
        protected boolean isEnabled(int viewType) {
            return true;
        }
    
        /** @deprecated */
        @Deprecated
        protected void setListener(final ViewGroup parent, final ViewHolder viewHolder, int viewType) {
            if(this.isEnabled(viewType)) {
                viewHolder.itemView.setOnClickListener(new OnClickListener() {
                    public void onClick(View v) {
                        if(UniversalAdapter.this.mOnItemClickListener != null) {
                            int position = UniversalAdapter.this.getPosition(viewHolder);
                            UniversalAdapter.this.mOnItemClickListener.onItemClick(parent, v, UniversalAdapter.this.mDatas.get(position), position);
                        }
    
                    }
                });
                viewHolder.itemView.setOnLongClickListener(new OnLongClickListener() {
                    public boolean onLongClick(View v) {
                        if(UniversalAdapter.this.mOnItemClickListener != null) {
                            int position = UniversalAdapter.this.getPosition(viewHolder);
                            return UniversalAdapter.this.mOnItemClickListener.onItemLongClick(parent, v, UniversalAdapter.this.mDatas.get(position), position);
                        } else {
                            return false;
                        }
                    }
                });
            }
        }
    
        public void onBindViewHolder(ViewHolder holder, int position) {
            this.setListener(position, holder);
            this.convert(holder, this.mDatas.get(position));
        }
    
        protected void setListener(final int position, final ViewHolder viewHolder) {
            if(this.isEnabled(this.getItemViewType(position))) {
                viewHolder.itemView.setOnClickListener(new OnClickListener() {
                    public void onClick(View v) {
                        if(UniversalAdapter.this.mOnItemClickListener != null) {
                            UniversalAdapter.this.mOnItemClickListener.onItemClick(UniversalAdapter.this.mRv, v, UniversalAdapter.this.mDatas.get(position), position);
                        }
    
                    }
                });
                viewHolder.itemView.setOnLongClickListener(new OnLongClickListener() {
                    public boolean onLongClick(View v) {
                        if(UniversalAdapter.this.mOnItemClickListener != null) {
                            int position = UniversalAdapter.this.getPosition(viewHolder);
                            return UniversalAdapter.this.mOnItemClickListener.onItemLongClick(UniversalAdapter.this.mRv, v, UniversalAdapter.this.mDatas.get(position), position);
                        } else {
                            return false;
                        }
                    }
                });
            }
        }
    
        // 将 var2的数据 显示在ViewHolder 的对应 View 中
        public abstract void convert(ViewHolder var1, T var2);
    
        public int getItemCount() {
            return this.mDatas != null?this.mDatas.size():0;
        }
    
        public void setDatas(List<T> list) {
            if(this.mDatas != null) {
                if(null != list) {
                    ArrayList temp = new ArrayList();
                    temp.addAll(list);
                    this.mDatas.clear();
                    this.mDatas.addAll(temp);
                } else {
                    this.mDatas.clear();
                }
            } else {
                this.mDatas = list;
            }
    
            this.notifyDataSetChanged();
        }
    
        public void remove(int i) {
            if(null != this.mDatas && this.mDatas.size() > i && i > -1) {
                this.mDatas.remove(i);
                this.notifyDataSetChanged();
            }
    
        }
    
        public void addDatas(List<T> list) {
            if(null != list) {
                ArrayList temp = new ArrayList();
                temp.addAll(list);
                if(this.mDatas != null) {
                    this.mDatas.addAll(temp);
                } else {
                    this.mDatas = temp;
                }
    
                this.notifyDataSetChanged();
            }
    
        }
    
        public List<T> getDatas() {
            return this.mDatas;
        }
    
        public T getItem(int position) {
            return position > -1 && null != this.mDatas && this.mDatas.size() > position?this.mDatas.get(position):null;
        }
    }
    

    Adapter要用到的Listener

    public interface OnItemClickListener<T> {
        void onItemClick(ViewGroup var1, View var2, T var3, int var4);
    
        boolean onItemLongClick(ViewGroup var1, View var2, T var3, int var4);
    }
    

    封装的ViewHolder

    public class ViewHolder extends RecyclerView.ViewHolder {
        private SparseArray<View> mViews = new SparseArray();
        private int mLayoutId;
    
        public ViewHolder(View itemView) {
            super(itemView);
        }
    
        public static ViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId) {
            if (convertView == null) {
                View holder2 = LayoutInflater.from(context).inflate(layoutId, parent, false);
                ViewHolder holder1 = new ViewHolder(holder2);
                holder1.mLayoutId = layoutId;
                return holder1;
            } else {
                ViewHolder holder = (ViewHolder) convertView.getTag();
                return holder;
            }
        }
    
        public int getLayoutId() {
            return this.mLayoutId;
        }
    
        public <T extends View> T getView(int viewId) {
            View view = (View) this.mViews.get(viewId);
            if (view == null) {
                view = this.itemView.findViewById(viewId);
                this.mViews.put(viewId, view);
            }
    
            return (T) view;
        }
    
        public ViewHolder setText(int viewId, CharSequence text) {
            TextView tv = (TextView) this.getView(viewId);
            tv.setText(text);
            return this;
        }
    
        public ViewHolder setSelected(int viewId, boolean selected) {
            View v = this.getView(viewId);
            v.setSelected(selected);
            return this;
        }
    
        public ViewHolder setImageResource(int viewId, int resId) {
            ImageView view = (ImageView) this.getView(viewId);
            view.setImageResource(resId);
            return this;
        }
    
        public ViewHolder setImageBitmap(int viewId, Bitmap bitmap) {
            ImageView view = (ImageView) this.getView(viewId);
            view.setImageBitmap(bitmap);
            return this;
        }
    
        public ViewHolder setImageDrawable(int viewId, Drawable drawable) {
            ImageView view = (ImageView) this.getView(viewId);
            view.setImageDrawable(drawable);
            return this;
        }
    
        public ViewHolder setBackgroundColor(int viewId, int color) {
            View view = this.getView(viewId);
            view.setBackgroundColor(color);
            return this;
        }
    
        public ViewHolder setBackgroundRes(int viewId, int backgroundRes) {
            View view = this.getView(viewId);
            view.setBackgroundResource(backgroundRes);
            return this;
        }
    
        public ViewHolder setTextColor(int viewId, int textColor) {
            TextView view = (TextView) this.getView(viewId);
            view.setTextColor(textColor);
            return this;
        }
    
        public ViewHolder setTextColorRes(int viewId, int textColorRes) {
            TextView view = (TextView) this.getView(viewId);
            view.setTextColor(this.itemView.getContext().getResources().getColor(textColorRes));
            return this;
        }
    
        @SuppressLint({"NewApi"})
        public ViewHolder setAlpha(int viewId, float value) {
            if (VERSION.SDK_INT >= 11) {
                this.getView(viewId).setAlpha(value);
            } else {
                AlphaAnimation alpha = new AlphaAnimation(value, value);
                alpha.setDuration(0L);
                alpha.setFillAfter(true);
                this.getView(viewId).startAnimation(alpha);
            }
    
            return this;
        }
    
        public ViewHolder setVisible(int viewId, boolean visible) {
            View view = this.getView(viewId);
            view.setVisibility(visible ? View.VISIBLE : View.GONE);
            return this;
        }
    
        public ViewHolder linkify(int viewId) {
            TextView view = (TextView) this.getView(viewId);
            Linkify.addLinks(view, Linkify.ALL);
            return this;
        }
    
        public ViewHolder setTypeface(Typeface typeface, int... viewIds) {
            int[] var3 = viewIds;
            int var4 = viewIds.length;
    
            for (int var5 = 0; var5 < var4; ++var5) {
                int viewId = var3[var5];
                TextView view = (TextView) this.getView(viewId);
                view.setTypeface(typeface);
                view.setPaintFlags(view.getPaintFlags() | 128);
            }
    
            return this;
        }
    
        public ViewHolder setProgress(int viewId, int progress) {
            ProgressBar view = (ProgressBar) this.getView(viewId);
            view.setProgress(progress);
            return this;
        }
    
        public ViewHolder setProgress(int viewId, int progress, int max) {
            ProgressBar view = (ProgressBar) this.getView(viewId);
            view.setMax(max);
            view.setProgress(progress);
            return this;
        }
    
        public ViewHolder setMax(int viewId, int max) {
            ProgressBar view = (ProgressBar) this.getView(viewId);
            view.setMax(max);
            return this;
        }
    
        public ViewHolder setRating(int viewId, float rating) {
            RatingBar view = (RatingBar) this.getView(viewId);
            view.setRating(rating);
            return this;
        }
    
        public ViewHolder setRating(int viewId, float rating, int max) {
            RatingBar view = (RatingBar) this.getView(viewId);
            view.setMax(max);
            view.setRating(rating);
            return this;
        }
    
        public ViewHolder setTag(int viewId, Object tag) {
            View view = this.getView(viewId);
            view.setTag(tag);
            return this;
        }
    
        public ViewHolder setTag(int viewId, int key, Object tag) {
            View view = this.getView(viewId);
            view.setTag(key, tag);
            return this;
        }
    
        public ViewHolder setChecked(int viewId, boolean checked) {
            Checkable view = (Checkable) this.getView(viewId);
            view.setChecked(checked);
            return this;
        }
    
        public ViewHolder setOnClickListener(int viewId, OnClickListener listener) {
            View view = this.getView(viewId);
            view.setOnClickListener(listener);
            return this;
        }
    
        public ViewHolder setOnTouchListener(int viewId, OnTouchListener listener) {
            View view = this.getView(viewId);
            view.setOnTouchListener(listener);
            return this;
        }
    
        public ViewHolder setOnLongClickListener(int viewId, OnLongClickListener listener) {
            View view = this.getView(viewId);
            view.setOnLongClickListener(listener);
            return this;
        }
    
        public void setItemVisible(boolean visible) {
            if (null != this.itemView) {
                if (visible) {
                    if (null != this.itemView.getLayoutParams()) {
                        this.itemView.getLayoutParams().width = -1;
                        this.itemView.getLayoutParams().height = -2;
                    } else {
                        this.itemView.setLayoutParams(new LayoutParams(-1, -2));
                    }
    
                    this.itemView.setVisibility(View.VISIBLE);
                } else {
                    if (null != this.itemView.getLayoutParams()) {
                        this.itemView.getLayoutParams().width = -1;
                        this.itemView.getLayoutParams().height = 1;
                    } else {
                        this.itemView.setLayoutParams(new LayoutParams(-1, 1));
                    }
    
                    this.itemView.setVisibility(View.GONE);
                }
            }
    
        }
    
        public void setHItemVisible(boolean visible) {
            if (null != this.itemView) {
                if (visible) {
                    if (null != this.itemView.getLayoutParams()) {
                        this.itemView.getLayoutParams().width = -2;
                        this.itemView.getLayoutParams().height = -2;
                    } else {
                        this.itemView.setLayoutParams(new LayoutParams(-1, -2));
                    }
    
                    this.itemView.setVisibility(View.VISIBLE);
                } else {
                    if (null != this.itemView.getLayoutParams()) {
                        this.itemView.getLayoutParams().width = -1;
                        this.itemView.getLayoutParams().height = 1;
                    } else {
                        this.itemView.setLayoutParams(new LayoutParams(-1, 1));
                    }
    
                    this.itemView.setVisibility(View.GONE);
                }
            }
    
        }
    }
    
  3. 创建

            adapter = object : UniversalAdapter<SwipeCardBean>(this, mDatas, R.layout.item_swipe_card) {
                override fun convert(holder: ViewHolder?, bean: SwipeCardBean?) {
                    holder?.run {
                        setText(R.id.tvName, bean!!.name)
                        setText(R.id.tvPrecent, "${bean.position}/${mDatas.size}")
                        Glide.with(this@MainActivity)
                            .load(bean.url)
                            .into(getView(R.id.iv) as ImageView)
                    }
                }
            }
    
            rv.adapter = adapter
    

自定义LayouManager

  1. 重写generateDefaultLayoutParams方法

    为itemView创建layout parameters

        override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
            return RecyclerView.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            )
        }
    
  2. 重写onLayoutChildren方法

    用来布局itemView

      override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
            // 回收复用
            detachAndScrapAttachedViews(recycler!!)
    
            // 8个
            // 0 1 2 3 4 5 6 7
            val bottomPosition = if (itemCount < CardConfig.MAX_SHOW_COUNT) {
                0
            } else {
                itemCount - CardConfig.MAX_SHOW_COUNT
            }
    
            for (i in bottomPosition until itemCount) {
                // 复用
                val view = recycler.getViewForPosition(i)
                addView(view)
    
                measureChildWithMargins(view, 0, 0)
    
                // 求padding大小
                val widthSpace = width - getDecoratedMeasuredWidth(view)
                val heightSpace = height - getDecoratedMeasuredHeight(view)
    
                //布局
                layoutDecoratedWithMargins(
                    view,
                    widthSpace / 2,
                    heightSpace / 2,
                    widthSpace / 2 + getDecoratedMeasuredWidth(view),
                    heightSpace / 2 + getDecoratedMeasuredHeight(view)
                )
    
                val level = itemCount - i - 1
    
                if (level > 0) {
                    if (level < CardConfig.MAX_SHOW_COUNT - 1) {
                        view.translationY = (CardConfig.TRANS_Y_GAP * level).toFloat()
                        view.scaleX = 1 - CardConfig.SCALE_GAP * level
                        view.scaleY = 1 - CardConfig.SCALE_GAP * level
                    } else {
                        // 把第四个弄得跟第三个一样
                        view.translationY = (CardConfig.TRANS_Y_GAP * (level - 1)).toFloat()
                        view.scaleX = 1 - CardConfig.SCALE_GAP * (level - 1)
                        view.scaleY = 1 - CardConfig.SCALE_GAP * (level - 1)
                    }
                }
            }
    
    
        }
    
  3. 使用

    rv.layoutManager = SwipeCardLayoutManager()
    

自定义ItemTouchHelper的Callback

  1. 重写onMove

    Called when ItemTouchHelper wants to move the dragged item from its old position to the new position. 这里不处理,直接返回false

        override fun onMove(
            recyclerView: RecyclerView,
            viewHolder: RecyclerView.ViewHolder,
            target: RecyclerView.ViewHolder
        ): Boolean {
            return false
        }
    
  2. 重写onSwipe

    Called when a ViewHolder is swiped by the user. 滑出去删除时调用

        override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
            val remove = mDatas.removeAt(viewHolder.layoutPosition)
            mDatas.add(0, remove)
            adapter.notifyDataSetChanged()
        }
    
  3. 重写onChildDraw

    定义怎么布局子view

     override fun onChildDraw(
            c: Canvas,
            recyclerView: RecyclerView,
            viewHolder: RecyclerView.ViewHolder,
            dX: Float,
            dY: Float,
            actionState: Int,
            isCurrentlyActive: Boolean
        ) {
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
            Log.i("jintag", "onChildDraw: actionState: $actionState")
    
            val maxDistance = recyclerView.width * 0.5f
            val distance = sqrt(dX * dX + dY * dY)
            var fraction = distance / maxDistance.toDouble()
    
            if (fraction > 1) {
                fraction = 1.0
            }
    
            val itemCount = recyclerView.childCount
    
            for (i in 0 until itemCount) {
                val view = recyclerView.getChildAt(i)
                val level = itemCount - i - 1
    
                if (level > 0) {
                    view.translationY =
                        (CardConfig.TRANS_Y_GAP * level - fraction * CardConfig.TRANS_Y_GAP).toFloat()
                    view.scaleX =
                        (1 - CardConfig.SCALE_GAP * level + fraction * CardConfig.SCALE_GAP).toFloat()
                    view.scaleY =
                        (1 - CardConfig.SCALE_GAP * level + fraction * CardConfig.SCALE_GAP).toFloat()
                }
            }
        }
    
  4. 使用

            // 拖拽删除
            val callback = SwipeCardCallback(rv, adapter, mDatas)
            val helper = ItemTouchHelper(callback)
            helper.attachToRecyclerView(rv)