Android自定义控件之小说书架实现示例详解

 更新时间:2023年04月18日 08:46:40   作者:卤肉拌面  
这篇文章主要为大家介绍了Android自定义控件之小说书架示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪方法

前言

在手机看小说的时候,看到一个很有意思的效果,在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&gt;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&lt;childCount;i++){
        View child = parent.getChildAt(i);
        if (child!=null &amp;&amp; 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);
        }
    }
}

拖拽时效果图如下所示:

查阅资料时,没有找到解决办法,只找到拖拽时,会多次调用ItemDecoration的onDrawOver方法,目前只在拖拽的时候尽量不使用ItemDecoration。

项目代码地址 github

以上就是Android自定义控件之小说书架的详细内容,更多关于Android自定义控件小说书架的资料请关注脚本之家其它相关文章!

相关文章

  • 详解Android应用中preference首选项的编写方法

    详解Android应用中preference首选项的编写方法

    这篇文章主要介绍了Android应用中preference首选项的编写方法,或许Apple将其翻译为'偏好设置'更直观些,即用户对应用的一些个性化调整菜单,需要的朋友可以参考下
    2016-04-04
  • Android Binder进程间通信工具AIDL使用示例深入分析

    Android Binder进程间通信工具AIDL使用示例深入分析

    Binder作为Android 众多的IPC通讯手段之一,在Framework的数据传输中起到极为关键的作用。Binder机制可谓是Android 知识体系里的重中之重,作为偏底层的基础组件,平时我们很少关注它,而它却是无处不在,也是Android 面试易考察的点之一
    2022-11-11
  • 简单实现Android计算器功能

    简单实现Android计算器功能

    这篇文章主要为大家详细介绍了自己动手实现的Android计算器功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-01-01
  • Android 自定义星评空间示例代码

    Android 自定义星评空间示例代码

    这篇文章主要介绍了Android 自定义星评空间的实例代码 ,需要的朋友可以参考下
    2017-06-06
  • Android手势操作识别详解

    Android手势操作识别详解

    这篇文章主要为大家详细介绍了Android手势操作识别,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • Android开发实现长按返回键弹出关机框功能

    Android开发实现长按返回键弹出关机框功能

    这篇文章主要介绍了Android开发实现长按返回键弹出关机框功能,涉及Android针对长按事件的响应与处理相关操作技巧,需要的朋友可以参考下
    2017-09-09
  • Android 使用flow实现倒计时的方式

    Android 使用flow实现倒计时的方式

    这篇文章主要介绍了Android 使用flow实现倒计时的方式,借助Flow这个工具,更加优雅地实现这个需求功能,文末给大家整理了Android 实现倒计时的几种方式,需要的朋友可以参考下
    2022-04-04
  • Android编程实现播放MP3功能示例

    Android编程实现播放MP3功能示例

    这篇文章主要介绍了Android编程实现播放MP3功能,结合实例形式分析了Android播放MP3功能的界面布局与功能实现相关操作技巧,需要的朋友可以参考下
    2017-02-02
  • Android界面刷新的方法分享

    Android界面刷新的方法分享

    Android界面刷新的方法分享,需要的朋友可以参考一下
    2013-05-05
  • Android Studio报:“Attribute application@theme or @ icon ”问题的解决

    Android Studio报:“Attribute application@theme or @ icon ”问题的解

    这篇文章主要给大家介绍了关于Android Studio报:“Attribute application@theme or @ icon ”问题的解决方法,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-12-12

最新评论