RecyclerView使用详解

RecyclerView使用详解

Recyclerview API

第1节 常用属性理解

  1. stopScroll 停止滚动

1.1 setHasFixedSize 优化

官方推荐设置true 理解:告诉RecyclerView我的Item大小不会变,重新绘制的时候不用重新计算

解释博文:RecyclerView setHasFixedSize(true)的意义

1.2 RecyclerView.Adapter 不同数据不同布局

Demo.java

 @Override
    public int getItemViewType(int position) {
        if (mTypeList.get(position) == 0) {         // 横向
            return TYPE_HORIZONTAL;
        } else if (mTypeList.get(position) == 1) {  // 纵向
            return TYPE_VERTICAL;
        } else {
            return super.getItemViewType(position);
        }
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == TYPE_HORIZONTAL) {
            //inflate viewHorizontal
            return new HorizontalViewHolder(viewHorizontal);
        } else if (viewType == TYPE_VERTICAL) {
            //inflate viewVertical
            return new VerticalViewHolder(viewVertical);
        }
        return null;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        // 布局控制
        if (holder instanceof HorizontalViewHolder) {
            if (mHorizontalList != null) {

            }
        } else if (holder instanceof VerticalViewHolder) {
            if (mVerticalList != null) {
            }
        }

1.3 滑动相关

  • smoothScrollToPosition(int) 大量数据不理想,查看下文TopSmoothScroller案例,实践过程中TopSmoothScroller在下滑数据量很大的情况下置顶时间过长,滑动时间久会导致使用不舒适
  • scrollToPosition(int) 滑动到指定位置
  • scrollBy(int x,int y) 滑动指定像素

最终的置顶方案

 int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
            //当可显示的item大于30时,直接置顶,否则丝滑滚动置顶
      if (firstVisibleItemPosition > 30) {
        mRecyclerView.scrollToPosition(0);
      }else {
        LinearSmoothScroller lss = new TopSmoothScroller(getActivity());
        lss.setTargetPosition(0);
        layoutManager.startSmoothScroll(lss);
      }

第2节 OnScrollListener

2.1 三种滑动状态 newState

/**
 * The RecyclerView is not currently scrolling.(静止没有滚动)
 */
public static final int SCROLL_STATE_IDLE = 0;
/**
 * The RecyclerView is currently being dragged by outside input such as user touch input.
 *(正在被外部拖拽,一般为用户正在用手指滚动)
 */
public static final int SCROLL_STATE_DRAGGING = 1;
/**
 * The RecyclerView is currently animating to a final position while not under outside control.
 *(自动滚动)
 */
public static final int SCROLL_STATE_SETTLING = 2;

2.2 onScrolled

onScrolled(RecyclerView recyclerView, int dx, int dy)

dx : 水平滚动距离
dy : 垂直滚动距离

第3节 LinearLayoutManager

LinearLayoutManager API

2.1 常用API白话理解

  1. 位置数量相关
linearLayoutManager.getChildCount()

注解:得到显示屏幕内的list数量

mLinearLayoutManager.getItemCount()

注解:得到list的总数量

int position = linearLayoutManager.findFirstVisibleItemPosition();

注解: 得到显示屏内的第一个list的位置数position* 类似:findLastVisibleItemPosition

findFirstCompletelyVisibleItemPosition

注解:第一个完全显示的Item位置 类似findLastCompletelyVisibleItemPosition

View firstVisiableChildView = linearLayoutManager.findViewByPosition(position);

注解:根据position找到这个Item的View

public class TopSmoothScroller extends LinearSmoothScroller {
  public TopSmoothScroller(Context context) {
    super(context);
  }

  @Override protected int getHorizontalSnapPreference() {
    return SNAP_TO_START;
  }

  @Override
  protected int getVerticalSnapPreference() {
    return SNAP_TO_START;//具体见源码注释
  }
}

LinearSmoothScroller lss = new TopSmoothScroller(getActivity());
lss.setTargetPosition(0);
layoutManager.startSmoothScroll(lss);

注解:使滑动到RecyclerView指定位置

canScrollVertically和canScrollHorizontally

注解:是否能垂直/水平滑动

第4节 ItemDecoration

  @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
       //根据itemView的位置设置边框大小
        int position = parent.getChildAdapterPosition(view);
        outRect.right = spaces;
    }

参考:ItemDecoration解析(二) onDraw onDrawOver 画出Item之间的分割线

使用案例 RecyclerView分割线

第5 节 ItemTouchHelper

ItemTouchHelper.Callback callback = new DragItemTouchHelper(adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerView);

DragItemTouchHelper.java

public class DragItemTouchHelper extends ItemTouchHelper.Callback {

    private DragAdapter mAdapter;

    public DragItemTouchHelper(DragAdapter adapter) {
        mAdapter = adapter;
    }

    /**
     * 设置滑动类型标记
     *
     * @param recyclerView
     * @param viewHolder
     * @return 返回一个整数类型的标识,用于判断Item那种移动行为是允许的
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
          // dragFlags =0 禁止拖动
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;  // 允许上下的拖动
          //int swipeFlags = ItemTouchHelper.LEFT;  // 只允许从右向左滑动
        int swipeFlags = 0; // 不允许左右滑动
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    /**
     * 拖拽切换 Item 的回调
     *
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return true Item切换了位置,false Item没切换位置
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

    }

    /**
     * Item 是否支持长按拖动
     * @return true  支持长按操作,false 不支持长按操作
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    /**
     * Item 是否支持滑动
     * @return true  支持滑动操作,false 不支持滑动操作
     */
    @Override
    public boolean isItemViewSwipeEnabled() {
        return false;
    }

    /**
     * Item被选中时候回调
     *
     * @param viewHolder
     * @param actionState 当前Item的状态
     *                    ItemTouchHelper.ACTION_STATE_IDLE   闲置状态
     *                    ItemTouchHelper.ACTION_STATE_SWIPE  滑动中状态
     *                    ItemTouchHelper#ACTION_STATE_DRAG   拖拽中状态
     */
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        // item 被选中的操作
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            viewHolder.itemView.setBackgroundResource(R.drawable.select_bg);
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        // 操作完毕后恢复颜色
        viewHolder.itemView.setBackgroundResource(R.drawable.common_bg);
        super.clearView(recyclerView, viewHolder);
    }

     /**
     * 移动过程中重新绘制 Item,随着滑动的距离,设置 Item 的透明度
     *  @param isCurrentlyActive True if this view is currently being controlled by the user or
     *                          false it is simply animating back to its original state.
     */
    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        float x = Math.abs(dX) + 0.5f;
        float width = viewHolder.itemView.getWidth();
        float alpha = 1f - x / width;
        viewHolder.itemView.setAlpha(alpha);
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
    }

  public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
                              ViewHolder viewHolder,
                              float dX, float dY, int actionState, boolean isCurrentlyActive) {
  }

}

Recylcerview 常用功能实现

  1. 水平滑动时不是最后一个Item, 都显示居中

  2. 左右联动 LinkActivity.java

滑动事件冲突

Android事件分发机制及滑动冲突解决方案【讲解比较详细】,提供两种方式

  1. 外部拦截法
  2. 内部拦截法

浅谈RecycleView嵌套RecycleView竖向滑动冲突解决

onInterceptTouchEvent == false; // 不向下分发,自己消费这次事件
onTouchEvent == true ;// 不向上分发,自己消费这次事件
parent.requestDisallowInterceptTouchEvent(true) // 请求父类true不拦截,false 请求父控件拦截

RecyclerView 源码解析

RecyclerView 优化

实践心酸采坑史

第9条命 BaseQuickAdapter.getViewByPosition

这个方法只有在Adapter不复用View的情况下能找到View,即子View全部可见时

🐛 故事:NestedScrollView ->(换成) LinearLayout, 导致无法找到子View ==> 🐛 NullpointerException

   for (int i = 0; i < list.size(); i++) {
      EditText
          etPrice =
          (EditText) mAdapter.getViewByPosition(mRecyclerView, i, R.id.et_dialog_price);
      String priceStr = etPrice.getText().toString();

    }

一些比较有趣的用法

ListView Google 推荐的优化方法

public View getView(int position, View convertView, ViewGroup parent) {
    Log.d("MyAdapter", "Position:" + position + "---"
            + String.valueOf(System.currentTimeMillis()));
    ViewHolder holder;
    if (convertView == null) {
        final LayoutInflater inflater = (LayoutInflater) mContext
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.list_item_icon_text, null);
        holder = new ViewHolder();
        holder.icon = (ImageView) convertView.findViewById(R.id.icon);
        holder.text = (TextView) convertView.findViewById(R.id.text);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }
    holder.icon.setImageResource(R.drawable.icon);
    holder.text.setText(mData[position]);
    return convertView;
}

static class ViewHolder {
    ImageView icon;
    TextView text;
}

ListView MultiChoiceModeListener

 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
    listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
      int num = 0;

      @Override
      public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
          boolean checked) {

        if (checked) {
          list.get(position).setBo(true);
          adapter.notifyDataSetChanged();
          num++;
        } else {
          list.get(position).setBo(false);
          adapter.notifyDataSetChanged();
          num--;
        }
        mode.setTitle("  " + num + " Selected");
      }

      @Override
      public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.action_mode, menu);
        num = 0;
        adapter.notifyDataSetChanged();
        return true;
      }

      @Override
      public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        adapter.notifyDataSetChanged();
        return false;
      }

      public void refresh() {
        for (int i = 0; i < 6; i++) {
          list.get(i).setBo(false);
        }
      }

      @Override
      public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        switch (item.getItemId()) {
          case R.id.menu_delete:
            adapter.notifyDataSetChanged();
            num = 0;
            refresh();
            mode.finish();
            return true;
          case R.id.action_save:
            //通过mode.getmenu 来控制menu的显示
                mode.getMenu().
                findItem(R.id.action_save)
                .setIcon(R.drawable.ic_action_unsave);
            mode.getMenu().findItem(R.id.menu_delete).setVisible(false);
            return true;
          default:
            refresh();
            adapter.notifyDataSetChanged();
            num = 0;
            return false;
        }
      }

      @Override
      public void onDestroyActionMode(ActionMode mode) {
        refresh();
        adapter.notifyDataSetChanged();
      }
    });

附录

参考博文

开源库