验证码: 看不清楚,换一张 查询 注册会员,免验证
  • {{ basic.site_slogan }}
  • 打开微信扫一扫,
    您还可以在这里找到我们哟

    关注我们

Android自定义控件之小说书架怎么实现

阅读:537 来源:乙速云 作者:代码code

Android自定义控件之小说书架怎么实现

      功能分析

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

      代码实现

      小说的展示

      我这里是使用RecyclerView实现,只不过layoutManager是使用GridLayoutManager,代码如下:

      小说拖拽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);
                  }
              }
              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 books;
          private BookshelfAdapter bookshelfAdapter;
          public BookShelfItemTouchHelper(List 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);
                  }
              }
              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);
              }
          }
      }
    分享到:
    *特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: hlamps#outlook.com (#换成@)。
    相关文章
    {{ v.title }}
    {{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
    你可能感兴趣
    推荐阅读 更多>