RecyclerViewAndroid技术知识Android知识

MaterialDesign--(2)让我欢喜让我忧的 Recy

2017-07-17  本文已影响409人  Anonymous___

RecyclerView 是伴随着 android5.x 出来的控件,第一次提出应该是在14年的 Google I/O 大会(猜测,懒得查,反正我不 care 它是什么时候出来的),到现在17年 Google I/O 大会结束正好三年,相信大家都早已经把 RecyclerView 使用到项目当中了。

我们都知道,RecyclerView 的出现,是为了取代 ListView、GridView 而出现的。记得有次面试的时候,面试官问我为什么要使用 RecyclerView,你 RecyclerView 能实现的列表,我 ListView 同样可以实现,我当时是这样回答的:整体上看RecyclerView架构,提供了一种插拔式的体验,高度的解耦,异常的灵活,这段话我随便在网上复制的,大意差不多。好了,扯远了。

对比一下,瞬间感觉 ListView 弱爆了。。。

说了这么多优点,说说缺点,既然 RecyclerView 给我们提供了这么多扩展,可以高度定制,但是上手难度同样增加了不止一个等级。还有条目点击事件,简直丧心病狂,竟然接口都没提供。

好了,我们要开始写代码了。

一、RecyclerView 的基本使用

先实现一个简单的列表:

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycle_view);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setAdapter(new RecyclerView.Adapter() {
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            //创建一个 ViewHolder 并且返回
            return null;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            //在这里给 ViewHolder 绑定数据
        }

        @Override
        public int getItemCount() {
         //控制RecyclerView的条目数
            return 0;
        }
    });

这样我们就使用RecyclerView实现了一个简单的列表,由于代码比较简单,我直接一笔带过了。这里对比我们的 Listview,就多了一行代码recyclerView.setLayoutManager(new LinearLayoutManager(this));,至于这行代码具体有什么作用呢,我们后面再说。

二、封装RecyclerView 的 Adapter

Adapter是 RecycleView 中最重要的一个类,虽然没有特别复杂难理解的代码,但是 RecycleView 的刷新条目、多条目、点击事件都需要在里面处理。而且每一个 RecycleView 的 Adapter 几乎都需要带读写,所以这是一个高频率、代码量稍多的一个类,因此我们在使用的过程中一般会对 Adapter 进行一下封装。

我们在实际项目开发当中,会有很多地方都用到 RecycleView(太长了,下面我用 rv 简称吧),而且数据的结构各有不同,因此,在 BaseAdapter 里面,我们需要用一个泛型 T 去规范数据结构的类型,避免误操作。

这个比较简单,等下直接看代码

这个也简单,抽取了一个 BaseViewHolder,在构造方法里面绑定的。

public abstract class BaseAbstractAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
  protected final String TAG = getClass().getSimpleName();
  protected final Context mContext;
  protected final LayoutInflater mLayoutInflater;

  protected List<T> mDataList = new ArrayList<>();

  public BaseAbstractAdapter(Context context) {
      this.mContext = context;
      this.mLayoutInflater = LayoutInflater.from(mContext);
  }

  public Context getContext() {
      return mContext;
  }
  
  public List<T> getDataList() {
       return mDataList;
  }
  
   public T getItemData(int position) {
      return (position >= 0 && position < mDataList.size()) ? mDataList.get(position) : null;
  }
  
   @Override
  public int getItemCount() {
      return mDataList == null ? 0 : mDataList.size();
  }

  /**
   * 移除某一条记录
   *
   * @param position 移除数据的position
   */
  public void removeItem(int position) {
      if (position >= 0 && position < mDataList.size()) {
          mDataList.remove(position);
          notifyItemRemoved(position);
      }
  }

  /**
  * 添加一条记录
   *
  * @param data     需要加入的数据结构
  * @param position 插入位置
  */
  public void addItem(T data, int position) {
      if (position >= 0 && position <= mDataList.size()) {
          mDataList.add(position, data);
          notifyItemInserted(position);
      }
  }

  /**
   * 添加一条记录
   *
   * @param data 需要加入的数据结构
   */
  public void addItem(T data) {
      addItem(data, mDataList.size());
  }

  /**
   * 移除所有记录
   */
  public void clearItems() {
      int size = mDataList.size();
      if (size > 0) {
          mDataList.clear();
          notifyItemRangeRemoved(0, size);
      }
  }

  /**
   * 批量添加记录
   *
   * @param data     需要加入的数据结构
   * @param position 插入位置
   */
  public void addItems(List<T> data, int position) {
      if (position >= 0 && position <= mDataList.size() && data != null && data.size() > 0) {
          mDataList.addAll(position, data);
          notifyItemRangeChanged(position, data.size());
      }
  }

  /**
   * 批量添加记录
   *
   * @param data 需要加入的数据结构
   */
  public void addItems(List<T> data) {
      addItems(data, mDataList.size());
  }

  @Override
  public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
      if (holder instanceof BaseViewHolder) {
          ((BaseViewHolder) holder).bindViewData(getItemData(position));
      }
  }

  }    
  //不是内部类哦
  public abstract class BaseViewHolder<T> extends RecyclerView.ViewHolder {

  public BaseViewHolder(View itemView) {
      super(itemView);
      ButterKnife.bind(this, itemView);
  }

  public abstract void bindViewData(T data);
  }

好了,代码贴完了,应该没有什么难懂的代码吧,这一套封装基本上可以满足98%以上的单Item 列表了,至于点击事件,并不是所有的列别都需要,根据实际需要自己定义接口回调就行了。

到这里可能有的同学会问,那多**** Item ****的**** rv ****怎么办,在实际开发中,多**** Item ****的**** rv ****情景也不算少,特别是聊天列表,动辄八九种**** Item****。别急,我们慢慢来****~****

rv 的方法中有个抽象方法onCreateViewHolder(ViewGroup parent, int viewType),这个方法是用来创建 ViewHolder 的,ViewHolder 我们可以把它理解成RecycleView 一个 ItemView 的包装类,也就是说一个 ViewHolder 就是一个条目,如果我们需要多条目,那么直接在这里返回不同的条目就行了,方法参数里面正好有个 viewType可以用来控制条目类型。

那么问题来了,这个 viewType值是从哪里来的呢,想知道这个,那就只能去看源码了,我们通过产看 RecyclerView.Adapter 的源码发现,onCreateViewHolder方法是在createViewHolder里面调用

public final VH createViewHolder(ViewGroup parent, int viewType) {
        TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
        final VH holder = onCreateViewHolder(parent, viewType);
        holder.mItemViewType = viewType;
        TraceCompat.endSection();
        return holder;
    }

看到这里,我们只能继续追createViewHolder的调用。然后通过全局搜索,在getViewForPosition方法里面找到了viewType这个参数的来源,里面有一行代码 final int type = mAdapter.getItemViewType(offsetPosition);于是再继续追getItemViewType。

 public int getItemViewType(int position) {
        return 0;
    }

好了,追到这里我也不再赘述了,本来大家都知道重写getItemViewType方法就行了。
回到正题,怎么封装多 ItemAdapter。多 Item 用到的场景一般都是需要给 RV 添加一个头或者添加一个尾,因此,考虑到通用性,我就只做了三种类型条目的扩展,下面直接贴代码:

 public abstract class BaseAbstractMultipleItemAdapter<T> extends BaseAbstractAdapter<T> {
 private static final int ITEM_TYPE_HEADER = 1;
 private static final int ITEM_TYPE_BOTTOM = 2;
 private static final int ITEM_TYPE_CONTENT = 3;

 @IntDef({ITEM_TYPE_HEADER, ITEM_TYPE_BOTTOM})
 @interface ItemType {

 }

 protected int mHeaderCount;//头部View个数
 protected int mBottomCount;//底部View个数

 public BaseAbstractMultipleItemAdapter(Context context) {
     super(context);
 }

 public void setHeaderCount(int headerCount) {
     this.mHeaderCount = headerCount;
 }

 public void setBottomCount(int bottomCount) {
     this.mBottomCount = bottomCount;
 }

 public int getHeaderCount() {
     return mHeaderCount;
 }

 public int getBottomCount() {
     return mBottomCount;
 }

 public boolean isHeaderView(int position) {
     return mHeaderCount != 0 && position < mHeaderCount;
 }

 public boolean isBottomView(int position) {
     return mBottomCount != 0 && position >= (mHeaderCount + super.getItemCount());
 }

 @Override
 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
     if (viewType == ITEM_TYPE_HEADER) {
         return onCreateHeaderView(parent);
     } else if (viewType == ITEM_TYPE_BOTTOM) {
         return onCreateBottomView(parent);
     } else {
         return onCreateContentView(parent, viewType);
     }
 }

 @Override
 public int getItemViewType(int position) {
     if (isHeaderView(position)) {//头部View
         return ITEM_TYPE_HEADER;
     } else if (isBottomView(position)) {//底部View
         return ITEM_TYPE_BOTTOM;
     } else {
         return getContentViewType(position);
     }
 }

 @Override
 public int getItemCount() {
     return mHeaderCount + super.getItemCount() + mBottomCount;
 }

 @Override
 public T getItemData(int position) {
     int index = position - mHeaderCount;
     if (index >= super.getItemCount()) {
         return null;
     }
     return super.getItemData(index);
 }

 /**
  * 移除某一条记录
  *
  * @param position 移除数据的position 如果有Header需要减去Header数量
  */
 public void removeItem(int position) {
     if (position < mDataList.size()) {
         mDataList.remove(position);
         notifyItemRemoved(mHeaderCount + position);
     }
 }

 /**
  * 添加一条记录
  *
  * @param data     需要加入的数据结构
  * @param position 插入数据的位置 如果有Header需要减去Header数量
  */
 public void addItem(T data, int position) {
     if (position <= mDataList.size()) {
         mDataList.add(position, data);
         notifyItemInserted(mHeaderCount + position);
     }
 }


 /**
  * 移除所有记录
  */
 public void clearItems() {
     int size = mDataList.size();
     if (size > 0) {
         mDataList.clear();
         notifyItemRangeRemoved(mHeaderCount, size);
     }
 }


 /**
  * 批量添加记录
  * @param data     需要加入的数据结构
  * @param position 插入数据的位置 如果有Header需要减去Header数量
  */
 public void addItems(List<T> data, int position) {
     if (position <= mDataList.size() && data != null && data.size() > 0) {
         mDataList.addAll(position, data);
         notifyItemRangeChanged(mHeaderCount + position, data.size());
     }
 }

 public int getContentViewType(int position) {
     return ITEM_TYPE_CONTENT;
 }

 public RecyclerView.ViewHolder onCreateHeaderView(ViewGroup parent) {//创建头部View
     return null;
 }

 public abstract RecyclerView.ViewHolder onCreateContentView(ViewGroup parent, int viewType);//创建中间内容View

 public abstract RecyclerView.ViewHolder onCreateBottomView(ViewGroup parent);//创建底部View

}

代码都有注释,应该没有什么看不懂多看几遍不能理解的通过mHeaderCount和mBottomCount分别控制头尾条目数,然后需要什么条目就重写对应的 CreateViewHolder 方法即可,如果 head 或者 bottom 需要绑定数据就在onBindViewHolder里面根据holder 和 position自行绑定。

三、ItemDecoration

Decoration:n.装饰品;装饰,装潢;装饰图案,装饰风格;奖章
顾名思义,条目装饰。

我们在写开发中经常会遇到这个的需求,列表的条目和条目之间需要添加一个间隔线,不知道你们是怎么解决的,反正我之前是直接在条目布局的底部直接写了一个分割线进去。这样写当然也可以,虽然最后一个条目不需要分割线可以通过代码手动隐藏掉,但是真的很 low有木有,做为一个自命不凡的 Coder,怎么写出如此高耦合重复的代码。

ItemDecoration 就可以完美的帮我们解决分割线的问题,当然ItemDecoration的功能可不仅仅如此,一口吃不成胖子,我们一步一步来

使用:mRecycleView.addItemDecoration(new RecyclerView.ItemDecoration() {});然而这只是一个抽象类。。。

首先我们点进源码,看这个类的注释

 /**
 * An ItemDecoration allows the application to add a special drawing and layout offset
 * to specific item views from the adapter's data set. This can be useful for drawing dividers
 * between items, highlights, visual grouping boundaries and more.
 *
 * <p>All ItemDecorations are drawn in the order they were added, before the item
 * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
 * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
 * RecyclerView.State)}.</p>
 */

我英语不怎么好,就不一句一句的翻译了,大意就是:
一个ItemDecoration允许添加一个特殊的图形和布局偏移。比如说涌入绘制项目之间的分割、突出显示、视觉分组边界等等。
所有的 ItemDecoration 的绘制顺序和条目添加顺序一致,后面这句话我翻译不通顺🤦‍♀️。

查看了一下ItemDecoration类的继承关系,发现Google 给我们默认实现的子类就只有一个ItemTouchHelper,这是一个比较特殊的子类,我们在后面会讲。那么既然这样,我们就只能手撸。

手撸之前我们先看一下ItemDecoration类的结构,在这里提取出三个比较关键得方法

 public void onDraw(Canvas c, RecyclerView parent, State state)
 public void onDrawOver(Canvas c, RecyclerView parent, State state)
 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)

源码上面三个方法都有方法说明,但是英语渣的我就不误导大家的英语了,我直接讲方法吧。

可能还有点懵逼,但是知道了这三个方法,我们就可以动手给 RecycleView 设置分割线了,下面是一个设置1px 分割线的代码

 public class DividerDecoration extends RecyclerView.ItemDecoration {

private int dividerHeight;
private Paint dividerPaint;

 public SimpleDividerDecoration(Context context) {
     dividerPaint = new Paint();
     dividerPaint.setColor(Color.RED);
     dividerHeight = 1     
 }


 @Override
 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
     super.getItemOffsets(outRect, view, parent, state);
     outRect.bottom = dividerHeight;
 }

 @Override
 public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
     int childCount = parent.getChildCount();
     int left = parent.getPaddingLeft();
     int right = parent.getWidth() - parent.getPaddingRight();
     for (int i = 0; i < childCount - 1; i++) {
         View view = parent.getChildAt(i);
         float top = view.getBottom();
         float bottom = view.getBottom() + dividerHeight;
         c.drawRect(left, top, right, bottom, dividerPaint);
     }
   }
 }

注意:这里有个坑getItemOffsets()里面最好不要调用 super 方法,因为里面有一个默认实现outRect.set(0, 0, 0, 0);super 方法最先调用还好,如果在最后调用,我们的outRect参数就被重置了。

四、ItemAnimator

顾名思义,条目动画。本来不想写的,RecyclerView 有默认的实现动画,而且列表中根本用不到酷炫的条目动画,无奈被我早起规划的时候,加入了 RecyclerView 的知识点里面,在这里简单讲一下吧。
使用方法如下:

 public void setItemAnimator(ItemAnimator animator) {
    if (mItemAnimator != null) {
        mItemAnimator.endAnimations();
        mItemAnimator.setListener(null);
    }
    mItemAnimator = animator;
    if (mItemAnimator != null) {
        mItemAnimator.setListener(mItemAnimatorListener);
    }
}

从这个方法里面,我们可以 get 到两个重要信息:

好了,那么接下来我们就通过学习DefaultAnimation的实现来自定义 ItemAnimation。

DefaultAnimation 继承自SimpleItemAnimator,通过阅读 SimpleItemAnimator 的注释信息,我们知道它是RecyclerView.ItemAnimator的直接子类,并且添加了ItemHolderInfo(一个简单的数据结构,保存了 Item 的边界信息,用于计算项目动画)来辅助条目动画的执行。因此我们如果要自定义 ItemAnimation 最好继承自SimpleItemAnimator。

好了,到这里,我们算是知道了如何自定义 ItemAnimation,接下来我们只需要顺藤摸瓜就可以了。

要顺藤摸瓜,得先找到藤,我们去看看ItemAnimation的抽象方法,自定义 ItemAnimation 一共有八个相关的方法需要我们去手动实现

 //Item移除回调
 @Override
 public boolean animateRemove(RecyclerView.ViewHolder holder) {
     return false;
 }

 //Item添加回调
 @Override
 public boolean animateAdd(RecyclerView.ViewHolder holder) {
     return false;
 }

 //用于控制添加,移动更新时,其它Item的动画执行
 @Override
 public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
     return false;
 }

 //Item更新回调
 @Override
 public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
     return false;
 }

 //真正控制执行动画的地方
 @Override
 public void runPendingAnimations() {

 }
 //停止某个Item的动画
 @Override
 public void endAnimation(RecyclerView.ViewHolder item) {

 }

 //停止所有动画
 @Override
 public void endAnimations() {

 }

 @Override
 public boolean isRunning() {
     return false;
 }

没个方法的作用我都在上面写了注释,相信看懂应该不难,接下来我们再根据这根藤回到 DefaultItemAnimation。

  @Override
   public boolean animateAdd(final ViewHolder holder) {
     resetAnimation(holder);
     ViewCompat.setAlpha(holder.itemView, 0);
     mPendingAdditions.add(holder);
     return true;
   }
  @Override
   public boolean animateRemove(final ViewHolder holder) {
     resetAnimation(holder);
     mPendingRemovals.add(holder);
     return true;
 }
  @Override
     public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
         int toX, int toY) {
     final View view = holder.itemView;
     fromX += ViewCompat.getTranslationX(holder.itemView);
     fromY += ViewCompat.getTranslationY(holder.itemView);
     resetAnimation(holder);
     int deltaX = toX - fromX;
     int deltaY = toY - fromY;
     if (deltaX == 0 && deltaY == 0) {
         dispatchMoveFinished(holder);
         return false;
     }
     if (deltaX != 0) {
         ViewCompat.setTranslationX(view, -deltaX);
     }
     if (deltaY != 0) {
         ViewCompat.setTranslationY(view, -deltaY);
     }
     mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
     return true;
 }
  @Override
   public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
         int fromX, int fromY, int toX, int toY) {
     if (oldHolder == newHolder) {
         // Don't know how to run change animations when the same view holder is re-used.
         // run a move animation to handle position changes.
         return animateMove(oldHolder, fromX, fromY, toX, toY);
     }
     final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
     final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
     final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
     resetAnimation(oldHolder);
     int deltaX = (int) (toX - fromX - prevTranslationX);
     int deltaY = (int) (toY - fromY - prevTranslationY);
     // recover prev translation state after ending animation
     ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
     ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
     ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
     if (newHolder != null) {
         // carry over translation values
         resetAnimation(newHolder);
         ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
         ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
         ViewCompat.setAlpha(newHolder.itemView, 0);
     }
     mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
     return true;
 }

好累啊😫,终于把它分析完了,接下来我们一鼓作气,动手撸一个。但是我项目中好像没有现成的,然后写动画这种需要创意的事情我心累,而且关键是实际开发中几乎也不怎么用得到。但是有一部分同学可能会对动画比较感兴趣,于是机智的我去 github 上找了一个RecyclerView 的 Item 动画库,大家可以对着我的分析去看看别人的实现,贴上传送门:https://github.com/wasabeef/recyclerview-animators

五、ItemTouchHelper

ItemTouchHelper 条目触摸助手,顾名思义,这个类就是用来帮助我们处理 RV 条目触摸事件的,如常见的滑动删除,长按拖拽。效果图如下:

ItemTouchHelper.gif ItemTouchHelper2.gif

没有啥特别的,我直接贴代码吧:

 ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
     @Override
     public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
         int from = viewHolder.getAdapterPosition();
         int to = target.getAdapterPosition();
         if (from < to) {
             for (int i = from; i < to; i++)
                 Collections.swap(mNewTopsAdapter.getDataList(), i, i + 1);
         } else {
             for (int i = from; i > to; i--)
                 Collections.swap(mNewTopsAdapter.getDataList(), i, i - 1);
         }
         mNewTopsAdapter.notifyItemMoved(from, to);
         return false;
     }

     @Override
     public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
         mNewTopsAdapter.removeItem(viewHolder.getAdapterPosition());

     }
 });
itemTouchHelper.attachToRecyclerView(mRecycleView);

实现起来很简单,new一个 ItemTouchHelper, 然后调用ItemTouchHelper的 attachToRecyclerView() 依附给 RV 就行了。关键点在 ItemTouchHelper 的构造方法里面必传的参数ItemTouchHelper.Callback()。由于这里需求比较简单,我直接用了已经做过一次封装的SimpleCallback。

SimpleCallback:继承自ItemTouchHelper.Callback,对父类进行了包装,只暴露出两个简单的方法供开发者去实现。

构造方法:

我们在 new SimpleCallback()的时候把拖拽的参数配置好,然后再实现如下两个抽象方法

其中 onMove方法会在 Item 拖动的时候不断调用,此时我们需要在条目拖动的时候调用 adapter 的notifyItemMoved()方法刷新条目位置,具体代码如下:

 int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
if (from < to) {
    for (int i = from; i < to; i++)
   Collections.swap(mNewTopsAdapter.getDataList(), i, i + 1);
} else {
    for (int i = from; i > to; i--)
        Collections.swap(mNewTopsAdapter.getDataList(), i, i - 1);
}
mNewTopsAdapter.notifyItemMoved(from, to);
return true;
首先获取当前条目和目标条目position,这里需要注意getAdapterPosition()和getLayoutPosition(),前者是在adapter 调用界面刷新的时候就给 position 赋值了,而后者是在界面刷新结束之后才能获取到正确的赋值。我们都知道,RV 的界面刷新是异步的,大概会有一个16毫秒左右的延时,因此使用getLayoutPosition()获取 position 可能会出错哦。

onSwiped方法则是用来控制条目滑动删除之后的逻辑处理。其中direction参数是滑动的方向。比如说滑动删除:我们直接在方法体里面调用 adapter 的 notifyItemRemoved()方法即可。

好了,ItemTouchHelper 的基本用法就这些,基本也能满足开发过程中的大部分需求了,如果还有更高的需求,那么继续跟我去肛一波ItemTouchHelper.Callback的源码。

****ItemTouchHelper.Callback****
抽象方法有三个:

其中第二个第三个方法,我们在SimpleCallback已经解释过一次了,这里不在赘述。我们来讲一下第一个方法

方法说明上,我用我三级英语水平加上翻译软件,大概可以读出这个方法是要返回一个控制 item 移动方向的混合标志,混合标志怎么生成,可以使用方法makeMovementFlags();好,那么实现getMovementFlags的方法体大概就是 return getMovementFlags(dragFlags,swipeFlags);
而 getMovementFlags 要求我们传两个参数,这两个参数怎么传呢,我们继续去追getMovementFlags();

 public static int makeMovementFlags(int dragFlags, int swipeFlags) {
        return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags) |
                makeFlag(ACTION_STATE_SWIPE, swipeFlags) | makeFlag(ACTION_STATE_DRAG,
                dragFlags);
    }

方法说明上,我们可以知道这个方法是用来创建移动 flag 的,说白了就是用来控制 Item 的移动/滑动方向,方法中两个参数分别是拖动 flag 和滑动 flag。看到这里,我们来回想一下SimpleCallback的构造方法,是不是也要传这两个参数,而SimpleCallback不需要实现 getMovementFlags()方法,是不是因为已经帮我们实现了,通过查看源码验证了我们的猜想。

然后就是一些公共方法,可重写定制的:

 //是否可以把拖动的ViewHolder拖动到目标ViewHolder之上
 @Override
 public boolean canDropOver(RecyclerView recyclerView,RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) {
return true;
 }

 //获取拖动
 @Override
 public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected, List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
return dropTargets.get(0);
 }

 //调用时与元素的用户交互已经结束,也就是它也完成了它的动画时候
 @Override
 public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
 }

 @Override
 public int convertToAbsoluteDirection(int flags, int layoutDirection) {
return super.convertToAbsoluteDirection(flags, layoutDirection);
 }

 //设置手指离开后ViewHolder的动画时间
 @Override
 public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {
return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy);
 }

 @Override
 public int getBoundingBoxMargin() {
return super.getBoundingBoxMargin();
 }

 //返回值作为用户视为拖动的距离
 @Override
 public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
return super.getMoveThreshold(viewHolder);
 }

 //返回值滑动消失的距离,滑动小于这个值不消失,大于消失
 @Override
 public float getSwipeEscapeVelocity(float defaultValue) {
return super.getSwipeEscapeVelocity(defaultValue);
 }

 //返回值滑动消失的距离, 这里是相对于RecycleView的宽度,0.5f表示为RecycleView的宽度的一半,取值为0~1f之间
 @Override
 public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
return super.getSwipeThreshold(viewHolder);
 }

 //返回值作为滑动的流程程度,越小越难滑动,越大越好滑动
 @Override
 public float getSwipeVelocityThreshold(float defaultValue) {
return 1f;
 }

 //当用户拖动一个视图出界的ItemTouchHelper调用
 @Override
 public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll) {
return super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll);
 }

 //返回值决定是否有滑动操作
 @Override
 public boolean isItemViewSwipeEnabled() {
return super.isItemViewSwipeEnabled();
 }

 //返回值决定是否有拖动操作
 @Override
 public boolean isLongPressDragEnabled() {
return super.isLongPressDragEnabled();
 }

 //自定义拖动与滑动交互
 @Override
 public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
 }

 //自定义拖动与滑动交互
 @Override
 public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
 }

 //当onMove return ture的时候调用
 @Override
 public void onMoved(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, int fromPos, RecyclerView.ViewHolder target, int toPos, int x, int y) {
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y);
 }

 //当拖动或者滑动的ViewHolder改变时调用
 @Override
 public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
 }

六、LayoutManger

LayoutManger:布局管理
LayoutManger是 RecyclerView 用来管理子 view 布局的一个组件(另一个组件是 Recycler,负责回收视图),它主要负责三个事情:

1.布局子视图
2.在滚动的过程中根据子视图在布局中所处的位置,决定何时添加子视图和回收视图
3.滚动子视图

其中只有滚动子视图才需要对子视图回收或添加,而添加子视图则必然伴随着所添加对象的布局处理,在滚动过程中,添加一次子视图只会影响到被添加对象,原有子视图的相对位置不会变化。

LayoutManger 是 RecyclerView 的一个抽象内部类,一般我们使用它都是使用它的子类:

这三个类的用法我就不过多的赘述了,相信大家都用过,一般情况下,这三个 LayoutManger 也能够满足大家99%的需求了。自定义LayoutManger 是一件比较有难度的工程,而且使用场景很少(反正我是没碰到过这样的需求)。但是网上有很多炫酷的自定义 LayoutManger 效果,最经典的当属防探探的卡片式布局,在网上也看过很多自定义 LayoutManger 的文章,但现在还是半吊子。

感兴趣的同学可以看看这个库,里面有 bolg 链接。当然,需要的时候再去看也行。传送门:https://github.com/mcxtzhang/ZLayoutManager

好了,RecyclerView 到这里就讲完了,可能有些地方深度不够,但是基本能满足大部分的需求了,如果有问题可以留言提问,后者直接私信我。

上一篇 下一篇

猜你喜欢

热点阅读