前言

手机看小说的时分,看到一个很有意思的作用,在UC浏览器切换到小说书架时分,能够在这个界面手指长按一本书拖拽它,当拖拽到其他小说后边时分。能够将其他小说前置,拖拽的小说到该方位上。功用作用大致如下图所示:

功用分析

经过运行图能够看出,该程序主要功用包含
1.按照网格布局展示小说信息
2.手指长按单个小说时,可拖拽该小说,并且手指松开时,将拖拽小说刺进到该方位,其他小说依次向移动
3.选中要删去的小说,点击删去按钮删去
其中有些难度的是小说的拖拽,主要是拖拽需求留意的当地比较多。

代码完成

小说的展示

我这儿是运用RecyclerView完成,只不过layoutManager是运用GridLayoutManager,代码如下:

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/rl_bookshelf"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:spanCount="3"
    app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
    tools:listitem="@layout/item_bookshelf"/>

小说拖拽BookShelfItemTouchHelper完成

RecyclerView想要完成拖拽功用需求写一个继承ItemTouchHelper.Callback的类,这儿把这个类命名为BookShelfItemTouchHelper。假如想要一个RecyclerView能够完成拖拽,能够给这个RecyclerView增加ItemTouchHelper,binding.rlBookshelf是想要增加拖拽作用的RecyclerView,设置代码如下:

val itemTouchHelper = ItemTouchHelper(BookShelfItemTouchHelper(books,adapter))
itemTouchHelper.attachToRecyclerView(binding.rlBookshelf)

关于BookShelfItemTouchHelper这个类的完成,需求重写下面几个办法:

getMovementFlags:能够拖动和滑动的方向,最终经过 makeMovementFlags 办法将拖拽和滑动方向汇总起来。代码里边是设置能够上下左右拖动,不设置滑动。

onMove:这个办法是,当手指移动到某个item上时,会触发这个函数(个人理解这个函数触发机遇是,当手指拖拽item在方针view上停留了一小会),这个办法里边viewHolder参数是手指拖拽item的ViewHolde,target是方针item的ViewHolder。在这儿咱们是把拖拽item放到方针item方位上,并返回true。办法结尾,还需求调用Adapter的notifyItemMoved()办法,告诉RecyclerView这两个item发生了改换,偏重绘。关于托拽item与方针item方位改换,假如咱们把拖动的item方位看为fromPosition,把方针item的方位看为toPosition。咱们需求把拖拽item放到方针item方位上,并比较fromPosition和toPosition巨细来决定,fromPosition与toPositon之间item是前移仍是后移。代码如下:

@Override
    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
        int fromPosition = viewHolder.getAdapterPosition();
        int toPosition = target.getAdapterPosition();   bookshelfAdapter.notifyItemRangeChanged(Math.min(fromPosition,toPosition),Math.abs(fromPosition - toPosition) + 1);
        if (fromPosition < toPosition){
            for (int i = fromPosition;i<toPosition;i++){
                Collections.swap(books,i,i+1);
            }
        }else {
            for (int i = fromPosition; i > toPosition;i--){
                Collections.swap(books,i,i-1);
            }
        }
        bookshelfAdapter.notifyItemMoved(fromPosition,toPosition);
        return true;
    }

onMoved:onMove返回true会触发这个办法,在这个办法里需求调用Adapter的notifyItemRangeChanged()办法来批量更新,item方位改换过程中受影响的数据。

onSelectedChanged():当手指长按选中item时会触发这个办法,这个办法中,我修正了item的背景色并略微扩展item的宽高。

clearView:当手指松开时会触发该办法,在这个办法里边,是恢复item的宽高及背景色。

interpolateOutOfBoundsScroll:这个办法是能够设置翻滚速度,假如不修正的话,会发现拖拽item到顶部或底部时分,向上或下的速度很慢,在这儿设置快一些。

下面是BookShelfItemTouchHelper的完好代码:

public class BookShelfItemTouchHelper extends ItemTouchHelper.Callback {
    private final String TAG = "BookShelfItemTouchHelper";
    private List<Book> books;
    private BookshelfAdapter bookshelfAdapter;
    public BookShelfItemTouchHelper(List<Book> books, BookshelfAdapter bookshelfAdapter) {
        this.books = books;
        this.bookshelfAdapter = bookshelfAdapter;
    }
    @Override
    public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
        int swipeFlags = 0;//不响应滑动方向
        int flags = makeMovementFlags(dragFlags,swipeFlags);
        return flags;
    }
    @Override
    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
        int fromPosition = viewHolder.getAdapterPosition();
        int toPosition = target.getAdapterPosition();
        bookshelfAdapter.notifyItemRangeChanged(Math.min(fromPosition,toPosition),Math.abs(fromPosition - toPosition) + 1);
        if (fromPosition < toPosition){
            for (int i = fromPosition;i<toPosition;i++){
                Collections.swap(books,i,i+1);
            }
        }else {
            for (int i = fromPosition; i > toPosition;i--){
                Collections.swap(books,i,i-1);
            }
        }
        bookshelfAdapter.notifyItemMoved(fromPosition,toPosition);
        return true;
    }
    @Override
    public void onMoved(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, int fromPos, @NonNull RecyclerView.ViewHolder target, int toPos, int x, int y) {
        super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y);
        bookshelfAdapter.notifyItemRangeChanged(Math.min(fromPos, toPos), Math.abs(fromPos - toPos) + 1);
    }
    /** 选中状态改动通知 */
    @Override
    public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
        super.onSelectedChanged(viewHolder, actionState);
        if (actionState == ItemTouchHelper.ACTION_STATE_DRAG){
            int bgColor = viewHolder.itemView.getContext().getResources().getColor(R.color.text_blue);
            viewHolder.itemView.setScaleX(1.2f);
            viewHolder.itemView.setScaleY(1.2f);
            viewHolder.itemView.setBackgroundColor(bgColor);
        }
    }
    /** 手指开释item或者交互动画完毕时调用 viewHolder是开释的item的ViewHolder对象*/
    @Override
    public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        viewHolder.itemView.setScaleX(1.0f);
        viewHolder.itemView.setScaleY(1.0f);
        int color = viewHolder.itemView.getContext().getResources().getColor(R.color.white);
        viewHolder.itemView.setBackgroundColor(color);
    }
    /** 修正翻滚速度 下面是固定了划动速度*/
    @Override
    public int interpolateOutOfBoundsScroll(@NonNull RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll) {
        final int direction = (int) Math.signum(viewSizeOutOfBounds);
        return 30 * direction;
    }
}

小说删去

小说删去相对简单,是在Adapter中界说了一个办法,当点击删去按钮时会调用该办法,该办法内部时会判断当删去数据长度大于0时,从Adapter接收的数据集里边移除删去数据,代码如下:

/** 删去选中的数据 */
fun deleted(){
    if (deletDatas.size>0){
        for (data in deletDatas){
            datas.remove(data)
        }
        deletDatas.clear()
        notifyDataSetChanged()
    }
}

总结

在完成小说书架功用时,遇到一些需求留意的当地,在此记录下。
一开始在完成功用时分,将下面的代码,放到了onMove办法中执行,这样会有一个问题手指拖拽时,手指没有松开,拖拽就完毕,猜测是因为拖动item时,系统会重绘列表,但下面代码会从头排序更新方位,就与拖拽产生抵触,导致拖拽完毕。

bookshelfAdapter.notifyItemRangeChanged(Math.min(fromPos, toPos), Math.abs(fromPos - toPos) + 1);

另一个当地是,我想经过ItemDecoration给ReyclerView增加装修时,在onDrawOver办法给列表每行开头小说增加装修后,在拖动时分发现,不是开头的小说也会出现这个作用,onDarwOver办法如下:

public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    super.onDrawOver(c, parent, state);
    int childCount = parent.getChildCount();
    for (int i=0;i<childCount;i++){
        View child = parent.getChildAt(i);
        if (child!=null && i%row==0){
            int startX = (child.getLeft()+child.getRight())/2 - decorationBmp.getWidth()/2;
            int startY = child.getTop() + child.getPaddingTop() - decorationBmp.getHeight()/2;
            c.drawBitmap(decorationBmp,startX,startY,paint);
        }
    }
}

拖拽时作用图如下所示:

Android自定义控件之小说书架
查阅资料时,没有找到解决办法,只找到拖拽时,会屡次调用ItemDecoration的onDrawOver办法,现在只在拖拽的时分尽量不运用ItemDecoration。
项目代码地址 github。