美文网首页
RecyclerView使用及原理介绍

RecyclerView使用及原理介绍

作者: corabsins | 来源:发表于2017-12-31 16:13 被阅读0次

    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的内容。

    • 添加布局文件或者在动态代码设置
      布局文件:
        <android.support.v7.widget.RecyclerView
            android:id="@+id/race_root_lv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@+id/race_details_bottom_ll"
            android:divider="@color/white"
            android:dividerHeight="20dp"
            android:overScrollMode="never" />
    
    • 编写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<View> 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 extends View> 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<MultipleItemBean> {
    
        public MultiItemAdapter(Context context, List<MultipleItemBean> 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使用及原理介绍

          本文链接:https://www.haomeiwen.com/subject/wokvwxtx.html