RecyclerView使用及原理介绍

RecyclerView ,自从推出到现在已经经历很长一段时间了,公司的项目代码大部分还使用的ListView 来实现页面的刷新,总结下用法,后面将ListView 全部替换成RecyclerView

一、RecyclerView基本使用

1、RecyclerView都有哪些常用功能

  • 上拉加载、下拉刷新
  • 给列表添加头和尾
  • 动画
  • 点击事件(item点击及item的子控件点击)
  • 分割线
  • 拖拽排序和侧划删除
  • 滑动及顶部标题透明度变化
  • 复杂布局
  • 卡片式Gallery效果
  • 增加和删除条目
  • 上下滑动、左右滑动
  • GridView 上下滑动、左右滑动
  • 瀑布流显示效果
  • ...

2、简单实现

  • 添加依赖库,Eclipse 导入jar包

implementation 'com.android.support:appcompat-v7:26.1.0'

关于compile、api 和implementation的区别,如果还没有升级3.0的小伙伴们赶紧升级试下~
compile 在3.0版本已经过时,替代他的是api 和implementation ,
api 和compile 功能一样,没有区别
implementation 引入的库,只可在本module下使用,本module不会通过自身的接口向外部暴露其依赖module的内容。

  • 添加布局文件或者在动态代码设置
    布局文件:
    
  • 编写Adapter,具体代码就不贴了
  • 设置布局管理器,添加分割线
rvRace.setLayoutManager(new LinearLayoutManager(mActivity));
       rvRace.addItemDecoration(new SimpleDividerDecoration(mActivity));
  • 设置Adapter,绑定数据
mAdapter = new RaceDetailsAdapter(mActivity);
        rvRace.setAdapter(mAdapter);
mAdapter.setDatas(mLists);

3、复杂用法

  • RecyclerBaseAdapter封装,此类主要封装的功能有:点击事件(条目点击事件及其子控件点击事件)、动画、头部和尾部的添加逻辑、复杂布局的处理、数据初使化及刷新、拖拽排序等功能
    具体代码就不贴了,有点长,可参考此连接

  • RecyclerBaseViewHolder封装,采用之前listview baseAdapter的封装逻辑,将子控件存放于SparseArray数组中,每次通过id获取,如果有直接取,没有通过findViewById获取,并存放于SparseArray中;
    该类中同时将常用控件设值封装此类中,子类中直接调用即可

public class RecyclerBaseViewHolder extends RecyclerView.ViewHolder {
    private final SparseArray mViews;
    private Context mContext;
    public View convertView;

    public BaseViewHolder(View itemView, Context context) {
        super(itemView);
        mViews = new SparseArray<>();
        mContext = context;
        convertView = itemView;
    }

    public View getConvertView() {
        return convertView;
    }

    public  T getView(int id) {
        View view = mViews.get(id);
        if (view == null) {
            view = itemView.findViewById(id);
            mViews.put(id, view);
        }
        return (T) view;
    }

    /**
     * Will set the text of a TextView.
     *
     * @param viewId The view id.
     * @param value  The text to put in the text view.
     * @return The BaseViewHolder for chaining.
     */
    public BaseViewHolder setText(@IdRes int viewId, CharSequence value) {
        TextView view = getView(viewId);
        view.setText(value);
        return this;
    }

    /**
     * Will set the text of a TextView.
     *
     * @param viewId The view id.
     * @param strId  The text to put in the text view.
     * @return The BaseViewHolder for chaining.
     */
    public BaseViewHolder setText(@IdRes int viewId, @StringRes int strId) {
        TextView view = getView(viewId);
        view.setText(strId);
        return this;
    }

    /**
     * Will set text color of a TextView.
     *
     * @param viewId    The view id.
     * @param textColor The text color (not a resource id).
     * @return The BaseViewHolder for chaining.
     */
    public BaseViewHolder setTextColor(@IdRes int viewId, @ColorInt int textColor) {
        TextView view = getView(viewId);
        view.setTextColor(textColor);
        return this;
    }


    /**
     * Will set the image of an ImageView from a resource id.
     *
     * @param viewId     The view id.
     * @param imageResId The image resource id.
     * @return The BaseViewHolder for chaining.
     */
    public BaseViewHolder setImageResource(@IdRes int viewId, @DrawableRes int imageResId) {
        ImageView view = getView(viewId);
        view.setImageResource(imageResId);
        return this;
    }

    /**
     * Will set the image of an ImageView from a drawable.
     *
     * @param viewId   The view id.
     * @param drawable The image drawable.
     * @return The BaseViewHolder for chaining.
     */
    public BaseViewHolder setImageDrawable(@IdRes int viewId, Drawable drawable) {
        ImageView view = getView(viewId);
        view.setImageDrawable(drawable);
        return this;
    }

    /**
     * Add an action to set the image of an image view. Can be called multiple times.
     */
    public BaseViewHolder setImageBitmap(@IdRes int viewId, Bitmap bitmap) {
        ImageView view = getView(viewId);
        view.setImageBitmap(bitmap);
        return this;
    }
}

  • 添加头布局、尾布局和下拉刷新、上拉加载更多遇到一块如何处理?

RecyclerView是一种插拔式的设计思路,就是我们想要什么插入什么就可以了, 从而很好的实现了解耦
这里采取的是装饰者设计模式的思路 ,将常用的添加头部和尾部的功能放到BaseAdapter 中,可通过addHeaderView、addFooterView进行添加;将下拉刷新和上拉加载更多的功能放到装饰类里实现,装饰类也是继承BaseAdapter,就是在原有的BaseAdapter的基础上,设计一个类,增强BaseAdapter的功能,而不用总去修改BaseAdapter,使其支持下拉刷新和上拉加载更多的功能,所以我们在setAdapter时会通过装饰类装饰后,再调用RecyclerView 的setAdapter进行数据加载。代码如下:

   @Override
    public void setAdapter(Adapter adapter) {
        if (adapter instanceof BaseAdapter) {
            mWrapAdapter = new WrapAdapter(adapter);
            super.setAdapter(mWrapAdapter);
        } else {
            super.setAdapter(adapter);
        }
        adapter.registerAdapterDataObserver(mDataObserver);
        mDataObserver.onChanged();
    }
  • 加入动画

RecyclerView提供了默认的动画类DefaultItemAnimator,通过setItemAnimator就可以设置一些简单动画,如果想自己实现动画,可以拷贝源码类DefaultItemAnimator 修改源码实现,也可以在方法onBindViewHolder中对每个条目设置动画,主要这三种实现方式。

  • 分割线

RecyclerView是通过addItemDecoration来实现的,目前最新版本的RecyclerView 提供了DividerItemDecoration 默认实现,如果无法满足我们的需求,我们可以自己实现RecyclerView.ItemDecoration 丰富,使用方法请参考 源码中的DividerItemDecoration类,也希望google 多提供几种实现类。

  • 卡片式Gallery 效果
    有时候我们需要实现Gallery效果,如何做呢?
    问题:
    a、每次滑动都让其中一张图片显示在正中间
    b、最左边的和最右边的卡片距离边距要和其它卡片保持一致
    c、中间图片放大,两边图片缩小(由大变小、由小变大)
    思路:
    a、google 在24系统版本提供了一个工具类 SnapHelper,它有两个实现类LinearSnapHelper 和PagerSnapHelper,LinearSnapHelper可以一次滑动多个卡片,而PagerSnapHelper只能滑动一个卡片,就有点类似于ViewPager一次只能滑动一个,这里我们用LinearSnapHelper类
    b、其中一种方式是onBindViewHolder 判断是不是第0个或者 size-1个item 位置,如果是,修改leftMargin或rightMargin,然后用setLayoutParams 给item 设置位置;另外一种方式是通过添加分割线的方式来实现,需要实现抽象类 RecyclerView.ItemDecoration,并复写 getItemOffsets 方法来实现;第一种方式,需要修改Adapter ,耦合性比较高,建议采取第二种方式。
    c、关于动画,获取到中间和两边的item ,分别对item添加缩放动画即可

源码如下:

public class RecyclerScaleUtils {
    public  boolean mStateIdle = false;
    private RecyclerView mRecycler;
    private  int mItemCount;
    private static int mDefaultMargin = 40;
    private  int mMargin = 0;
    private  int mItemwidth;
    private  int mCurrentPosition = 0;

    private  int mDistances = 0;

    public  void attachToRecyclerView(RecyclerView recyclerView, int margin){
        if(recyclerView == null){
            return;
        }
        mRecycler = recyclerView;
        if(margin <= 0){
            mMargin = mDefaultMargin;
        }else {
            mMargin = margin;
        }
        initView();
        final RecyclerLinearSnapHelper helper = new RecyclerLinearSnapHelper();
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if(newState == RecyclerView.SCROLL_STATE_IDLE){
                    if(mDistances == 0 || mDistances == (mItemCount*mItemwidth)){
                        mStateIdle = true;
                    }else{
                        mStateIdle = false;
                    }
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if(recyclerView.getLayoutManager().getLayoutDirection() == LinearLayoutManager.HORIZONTAL){
                    mDistances += dx;
                    getCurrentPosition();
//                    setItemScale();
                }

            }
        });

        helper.attachToRecyclerView(recyclerView);
    }

    protected  void initView( ){
        mRecycler.post(new Runnable() {

            @Override
            public void run() {
                mItemCount = mRecycler.getAdapter().getItemCount();
                mItemwidth = mRecycler.getWidth() - 2 * mMargin;
                mRecycler.smoothScrollToPosition(mCurrentPosition);
//                setItemScale();
            }
        });
    }

    public  void setItemScale(){
        View leftView = null;
        View rightView = null;
        if(mCurrentPosition > 0){
            leftView = mRecycler.getLayoutManager().findViewByPosition(mCurrentPosition - 1);
        }
        View currentView = mRecycler.getLayoutManager().findViewByPosition(mCurrentPosition);
        if(mCurrentPosition < (mItemCount - 1)){
            rightView = mRecycler.getLayoutManager().findViewByPosition(mCurrentPosition +1);
        }

        //滑动百分比,左右的都是放大,中间缩小
        float percent = Math.abs((mDistances - mCurrentPosition * mItemwidth*1.0f)/mItemwidth);
        Log.d("tests","======percent=========>"+percent);
        if(leftView != null){
            //这里是缩小原来大小的0.8-1.0 左右0.8,中间1.0   0.8+(percent*0.2)
            leftView.setScaleY(0.8f+(percent*0.2f));
        }
        if(currentView != null){
            currentView.setScaleY(1-0.2f*percent);
        }
        if(rightView != null){
            rightView.setScaleY(0.8f+(percent*0.2f));
        }

    }

    protected  void getCurrentPosition(){
        if(mItemwidth <= 0) return;
        boolean change = false;

        if (Math.abs(mDistances - mCurrentPosition * mItemwidth) >= mItemwidth) {
            change = true;
        }
        if (change) { //以为这里是从0开始的
            mCurrentPosition = mDistances / mItemwidth;
        }

    }

    public static void onCreateViewHolder(ViewGroup parent, View itemView,int margin) {
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) itemView.getLayoutParams();
        if(margin <= 0){
            margin = mDefaultMargin;
        }
        lp.width = parent.getWidth() - 2*margin;
        itemView.setLayoutParams(lp);
    }

    //这里是处理最左边和最右边的宽度
    public static void onBindViewHolder(View itemView, final int position, int itemCount,int margin) {
        int leftMarin = 0;
        int rightMarin =  0;
        int topMarin = 0;
        int bottomMarin =  0;
        if(position == 0){
            leftMarin = margin;
            rightMarin = 0;
        }else if(position == (itemCount-1)){
            leftMarin = 0;
            rightMarin = margin;
        }
        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
        if (lp.leftMargin != leftMarin || lp.topMargin != topMarin || lp.rightMargin != rightMarin || lp.bottomMargin != bottomMarin) {
            lp.setMargins(leftMarin, topMarin, rightMarin, bottomMarin);
            itemView.setLayoutParams(lp);
        }

    }

//    setViewMargin(itemView, leftMarin, 0, rightMarin, 0);
    private static void setViewMargin(View view, int left, int top, int right, int bottom) {
        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
        if (lp.leftMargin != left || lp.topMargin != top || lp.rightMargin != right || lp.bottomMargin != bottom) {
            lp.setMargins(left, top, right, bottom);
            view.setLayoutParams(lp);
        }
    }

    public  void setItemPosition(int position){
        mCurrentPosition = position ;
    }


    private class RecyclerLinearSnapHelper extends LinearSnapHelper {

        /**
         //                 *  int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView);
         //                 if (snapDistance[0] != 0 || snapDistance[1] != 0) {
         //                 mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);
         //                 }
         //                 这个方法返回的数组值,是让recyclerview移动并居中显示的,如果是第一个或者最后一个位置,
         //                 因无法居中而调用recyclerview的OnScrollListener监听
         //                 */
        @Override
        public int[] calculateScrollDistance(int velocityX, int velocityY) {
            if(mStateIdle){
                return new int[2];
            }else{
                int[] ints = super.calculateScrollDistance(velocityX, velocityY);
                for (int i:ints){
                    Log.e("tests","==i=="+i);
                }
                return super.calculateScrollDistance(velocityX, velocityY);
            }
        }

    }

}

通过如下代码就可让gallery生效,如果想让gellery有缩放动画,将上述源码中 setItemScale()方法取消注释即可

RecyclerScaleUtils utils = new RecyclerScaleUtils();
utils.attachToRecyclerView(rvRaceView, DensityUtil.dip2px(mActivity, 44.0f));

在onCreateViewHolder时加入如入代码,计算每个卡片的大小

view = mInflater.inflate(R.layout.item_recycler_workbook, parent, false);
//这里设置view的宽度
RecyclerScaleUtils.onCreateViewHolder(parent, view, DensityUtil.dip2px(parent.getContext(), 44.0));

onBindViewHolder中针对第一个和最后一个位置设置左边距和右边距

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
//设置第一个与最后item对称显示
RecyclerScaleUtils.onBindViewHolder(holder.itemView, position, getItemCount(), DensityUtil.dip2px(holder.itemView.getContext(), 30f));
}

  • 点击事件
    分item 点击事件和item 子控件的点击事件,两种点击事件需要分别实现,代码如下:
/**
     * Listener
     */
    private OnItemClickListener mOnItemClickListener;
    private OnItemLongClickListener mOnItemLongClickListener;
    private OnRecyclerViewItemChildClickListener mChildClickListener;
    private OnRecyclerViewItemChildLongClickListener mChildLongClickListener;

    /**
     * Listener api
     */
    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }

    public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
        mOnItemLongClickListener = onItemLongClickListener;
    }

    /**
     * Register a callback to be invoked when childView in this AdapterView has
     * been clicked and held {@link OnRecyclerViewItemChildClickListener}
     *
     * @param childClickListener The callback that will run
     */
    public void setOnItemChildClickListener(
            OnRecyclerViewItemChildClickListener childClickListener) {
        this.mChildClickListener = childClickListener;
    }

    public class OnItemChildClickListener implements View.OnClickListener {
        public RecyclerView.ViewHolder mViewHolder;

        @Override
        public void onClick(View v) {
            if (mChildClickListener != null)
                mChildClickListener.onItemChildClick(SuperBaseAdapter.this, v,
                        mViewHolder.getLayoutPosition() - getHeaderViewCount());
        }
    }

    /**
     * Register a callback to be invoked when childView in this AdapterView has
     * been longClicked and held
     * {@link OnRecyclerViewItemChildLongClickListener}
     *
     * @param childLongClickListener The callback that will run
     */
    public void setOnItemChildLongClickListener(
            OnRecyclerViewItemChildLongClickListener childLongClickListener) {
        this.mChildLongClickListener = childLongClickListener;
    }

    public class OnItemChildLongClickListener implements View.OnLongClickListener {
        public RecyclerView.ViewHolder mViewHolder;

        @Override
        public boolean onLongClick(View v) {
            if (mChildLongClickListener != null) {
                return mChildLongClickListener.onItemChildLongClick(SuperBaseAdapter.this, v,
                        mViewHolder.getLayoutPosition() - getHeaderViewCount());
            }
            return false;
        }
    }

item点击事件会在BaseAdapter 的onCreateViewHolder中加入,只要页面设置了点击事件就会触发,而item的子控件监听需要在BaseAdapter的实现类中对子控件设置监听才会生效

  • 多布局
    这个和单布局维一的区别就是得通过getItemViewType判断类型,然后根据类型去创建不同的布局,然后去绑定数据,封装后子类的实现代码如下:
public class MultiItemAdapter extends BaseAdapter {

    public MultiItemAdapter(Context context, List data) {
        super(context, data);
    }

    @Override
    protected void convert(BaseViewHolder holder, MultipleItemBean item, int position) {
        if(item.getType() == 0){
            holder.setText(R.id.name_tv,item.getName());
        } else if(item.getType() == 1){
            holder.setText(R.id.name_tv,item.getName())
            .setText(R.id.info_tv,item.getInfo());
        }
    }

    @Override
    protected int getItemViewLayoutId(int position, MultipleItemBean item) {
        if(item.getType() == 0){
            return R.layout.adapter_multi_item1_layout;
        } else if(item.getType() == 1){
            return R.layout.adapter_multi_item2_layout;
        } else{
            return R.layout.adapter_multi_item3_layout;
        }
    }
}

  • 复杂布局
    这个得根据产品的具体功能去分析,没有RecyclerView 前,要实现多布局、多层嵌套,挺麻烦的,我们经常需要重写一些方法来解决冲突问题,如ListView 嵌套ListView 时,相信大家都不会陌生;有了RecyclerView后,这方面的问题就不用考虑了。

  • 滑动及顶部标题透明度变化
    通过RecyclerView 提供的addOnSrollListener 监听滑动,根据滑动的距离做透明度的变化和背景颜色的调整,代码如下:

raceRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                isScrollIdle = (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (isScrollIdle && adViewTopSpace < 0)
                    return;
                adViewTopSpace = DensityUtil.px2dip(GradualChangeActivity.this,
                        mAdapter.getHeaderLayout().getTop());
                adViewHeight = DensityUtil.px2dip(GradualChangeActivity.this,
                        mAdapter.getHeaderLayout().getHeight());
                if (-adViewTopSpace <= 50) {
                    tv_title.setVisibility(View.GONE);
                } else {
                    tv_title.setVisibility(View.VISIBLE);
                }
                handleTitleBarColorEvaluate();
            }
        });
    }

    // 处理标题栏颜色渐变
    private void handleTitleBarColorEvaluate() {
        float fraction;
        rlBar.setAlpha(1f);
        if (adViewTopSpace > 5) {
            fraction = 1f - adViewTopSpace * 1f / 60;
            if (fraction < 0f)
                fraction = 0f;
            rlBar.setAlpha(fraction);
            return;
        }
        float space = Math.abs(adViewTopSpace) * 1f;
        fraction = space / (adViewHeight - titleViewHeight);
        if (fraction < 0f)
            fraction = 0f;
        if (fraction > 1f)
            fraction = 1f;
        rlBar.setAlpha(1f);
        if (fraction >= 1f) {
            viewTitleBg.setAlpha(0f);
            viewActionMoreBg.setAlpha(0f);
            rlBar.setBackgroundColor(this.getResources().getColor(R.color.orange));
        } else {
            viewTitleBg.setAlpha(1f - fraction);
            viewActionMoreBg.setAlpha(1f - fraction);
            rlBar.setBackgroundColor(evaluate(this, fraction,
                    this.getResources().getColor(R.color.transparent), this.getResources().getColor(R.color.orange)));
        }
    }

/**
     * 成新的颜色值
     * @param fraction 颜色取值的级别 (0.0f ~ 1.0f)
     * @param startValue 开始显示的颜色
     * @param endValue 结束显示的颜色
     * @return 返回生成新的颜色值
     */
    public static int evaluate(float fraction, int startValue, int endValue) {
        int startA = (startValue >> 24) & 0xff;
        int startR = (startValue >> 16) & 0xff;
        int startG = (startValue >> 8) & 0xff;
        int startB = startValue & 0xff;

        int endA = (endValue >> 24) & 0xff;
        int endR = (endValue >> 16) & 0xff;
        int endG = (endValue >> 8) & 0xff;
        int endB = endValue & 0xff;

        return ((startA + (int) (fraction * (endA - startA))) << 24) |
                ((startR + (int) (fraction * (endR - startR))) << 16) |
                ((startG + (int) (fraction * (endG - startG))) << 8) |
                ((startB + (int) (fraction * (endB - startB))));
    }


  • 拖拽排序和侧划删除
    目前项目中没有用到,后面再整理

二、源码分析:

后续补充

你可能感兴趣的:(RecyclerView使用及原理介绍)