RecyclerView

作者: Android开发666 | 来源:发表于2017-01-05 21:21 被阅读108次

    RecylerView介绍

    RecylerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字recylerview即回收view也可以看出。官方对于它的介绍则是:RecyclerView 是 ListView 的升级版本,更加先进和灵活。RecyclerView通过设置LayoutManager,ItemDecoration,ItemAnimator实现你想要的效果。

    • 使用LayoutManager来确定每一个item的排列方式。
    • 使用ItemDecoration自己绘制分割线,更灵活
    • 使用ItemAnimator为增加或删除一行设置动画效果。

    注意

    新建完项目,需要在app/build.gradle增加RecylerView依赖,不然找不到RecyclerView类

    compile 'com.android.support:recyclerview-v7:23.1.0'
    

    RecylerView简单的Demo

    我们来看activity代码,跟ListView写法差不多,只是这边多设置了布局管理器。

    public class LinearLayoutActivity extends AppCompatActivity {
        private RecyclerView recyclerView;
        private RecyclerViewAdapter adapter;
        private List<String> datas;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.recycler_main);
    
            initData();
    
            recyclerView= (RecyclerView) findViewById(R.id.recyclerview);
            recyclerView.setLayoutManager(new LinearLayoutManager(this));//设置布局管理器
            recyclerView.addItemDecoration(new DividerItemDecoration(this));
            recyclerView.setAdapter(adapter=new RecyclerViewAdapter(this,datas));
        }
    
        private void initData(){
            datas=new ArrayList<>();
            for(int i=0;i<100;i++){
                datas.add("item:"+i);
            }
        }
    }
    

    activity对应的布局文件:recycler_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </RelativeLayout>
    

    adapter相对ListView来说变化比较大的。把ViewHolder逻辑封装起来了,代码相对简单一些。

    • 需要继承RecyclerView.Adapter,重写三个方法
    • MyViewHolder需要继承RecyclerView.ViewHolder
    public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder>{
        private List<String> datas;
        private LayoutInflater inflater;
    
        public  RecyclerViewAdapter(Context context,List<String> datas){
            inflater=LayoutInflater.from(context);
            this.datas=datas;
        }
    
        //创建每一行的View 用RecyclerView.ViewHolder包装
        @Override
        public RecyclerViewAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View itemView=inflater.inflate(R.layout.recycler_item,null);
            return new MyViewHolder(itemView);
        }
    
        //给每一行View填充数据
        @Override
        public void onBindViewHolder(RecyclerViewAdapter.MyViewHolder holder, int position) {
            holder.textview.setText(datas.get(position));
        }
    
        //数据源的数量
        @Override
        public int getItemCount() {
            return datas.size();
        }
    
        class MyViewHolder extends RecyclerView.ViewHolder{
            private TextView textview;
    
            public MyViewHolder(View itemView) {
                super(itemView);
                textview= (TextView) itemView.findViewById(R.id.textview);
            }
        }
    }
    

    我们来看看效果图:


    RecylerView基本使用RecylerView基本使用

    RecyclerView增加分隔线

    RecyclerView是没有android:divider跟android:dividerHeight属性的,如果我们需要分割线,就只能自己动手去实现了。

    • 需要继承ItemDecoration类,实现onDraw跟getItemOffsets方法。
    • 调用RecyclerView的addItemDecoration方法。

    我们先写一个DividerItemDecoration类,继承RecyclerView.ItemDecoration,在getItemOffsets留出item之间的间隔,然后就会调用onDraw方法绘制(onDraw的绘制优先于每一行的绘制)

    public class DividerItemDecoration extends RecyclerView.ItemDecoration{
        /*
        * RecyclerView的布局方向,默认先赋值 为纵向布局
        * RecyclerView 布局可横向,也可纵向
        * 横向和纵向对应的分割线画法不一样
        * */
        private int mOrientation = LinearLayoutManager.VERTICAL;
    
        private int mItemSize = 1;//item之间分割线的size,默认为1
    
        private Paint mPaint;//绘制item分割线的画笔,和设置其属性
    
        public DividerItemDecoration(Context context) {
            this(context,LinearLayoutManager.VERTICAL,R.color.colorAccent);
        }
    
        public DividerItemDecoration(Context context, int orientation) {
            this(context,orientation, R.color.colorAccent);
        }
    
        public DividerItemDecoration(Context context, int orientation, int dividerColor){
            this(context,orientation,dividerColor,1);
        }
    
        /**
         * @param context
         * @param orientation 绘制方向
         * @param dividerColor 分割线颜色 颜色资源id
         * @param mItemSize 分割线宽度 传入dp值就行
         */
        public DividerItemDecoration(Context context, int orientation, int dividerColor, int mItemSize){
            this.mOrientation = orientation;
            if(orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL){
                throw new IllegalArgumentException("请传入正确的参数") ;
            }
            //把dp值换算成px
            this.mItemSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,mItemSize,context.getResources().getDisplayMetrics());
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
            mPaint.setColor(context.getResources().getColor(dividerColor));
        }
    
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            if(mOrientation == LinearLayoutManager.VERTICAL){
                drawVertical(c,parent) ;
            }else {
                drawHorizontal(c,parent) ;
            }
        }
    
        /**
         * 绘制纵向 item 分割线
         * @param canvas
         * @param parent
         */
        private void drawVertical(Canvas canvas,RecyclerView parent){
            final int left = parent.getPaddingLeft() ;
            final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
            final int childSize = parent.getChildCount() ;
            for(int i = 0 ; i < childSize ; i ++){
                final View child = parent.getChildAt( i ) ;
                RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
                final int top = child.getBottom() + layoutParams.bottomMargin ;
                final int bottom = top + mItemSize ;
                canvas.drawRect(left,top,right,bottom,mPaint);
            }
        }
    
        /**
         * 绘制横向 item 分割线
         * @param canvas
         * @param parent
         */
        private void drawHorizontal(Canvas canvas,RecyclerView parent){
            final int top = parent.getPaddingTop() ;
            final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom() ;
            final int childSize = parent.getChildCount() ;
            for(int i = 0 ; i < childSize ; i ++){
                final View child = parent.getChildAt( i ) ;
                RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
                final int left = child.getRight() + layoutParams.rightMargin ;
                final int right = left + mItemSize ;
                canvas.drawRect(left,top,right,bottom,mPaint);
            }
        }
    
        /**
         * 设置item分割线的size
         * @param outRect
         * @param view
         * @param parent
         * @param state
         */
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            if(mOrientation == LinearLayoutManager.VERTICAL){
                outRect.set(0,0,0,mItemSize);//垂直排列 底部偏移
            }else {
                outRect.set(0,0,mItemSize,0);//水平排列 右边偏移
            }
        }
    }
    

    不要忘记调用addItemDecoration方法哦

    recyclerView.addItemDecoration(new DividerItemDecoration(this));//添加分割线
    

    重新运行,效果图:

    添加分割线添加分割线

    大家读到这里肯定会有一个疑问,这货比ListView麻烦多了啊,但是google官方为什么要说是ListView的升级版呢?接下来开始放大招。。。

    GridLayoutManager

    在RecyclerView中实现不同的列表,只需要切换不同的LayoutManager即可。RecyclerView.LayoutManager跟RecyclerView.ItemDecoration一样,都是RecyclerView静态抽象内部类,但是LayoutManager有三个官方写好的实现类。

    • LinearLayoutManager 线性布局管理器 跟ListView功能相似
    • GridLayoutManager 网格布局管理器 跟GridView功能相似
    • StaggeredGridLayoutManager 瀑布流布局管理器

    刚刚我们用的是LinearLayoutManager,现在我们切换到GridLayoutManager,看到下面这句代码,有没有感觉分分钟切换不同列表显示。

    recyclerView.setLayoutManager(new GridLayoutManager(this,2));
    

    如果要显示多列或者要纵向显示就new不同的构造方法,以下代码纵向显示4列。当前如果你还需要反方向显示,把false改成true就可以。

    recyclerView.setLayoutManager(new GridLayoutManager(this,4,GridLayoutManager.HORIZONTAL,false));
    

    因为用的是网格布局,所以呢绘制分割线的代码需要重新修改一下。网格布局一行可以有多列,并且最后一列跟最后一行不需要绘制,所以我们得重新创建一个类。
    DividerGridItemDecoration.java

    public class DividerGridItemDecoration extends RecyclerView.ItemDecoration {
        /*
        * RecyclerView的布局方向,默认先赋值 为纵向布局
        * RecyclerView 布局可横向,也可纵向
        * 横向和纵向对应的分割线画法不一样
        * */
        private int mOrientation = LinearLayoutManager.VERTICAL;
    
        private int mItemSize = 1;//item之间分割线的size,默认为1
    
        private Paint mPaint;//绘制item分割线的画笔,和设置其属性
    
        public DividerGridItemDecoration(Context context) {
            this(context,LinearLayoutManager.VERTICAL,R.color.colorAccent);
        }
    
        public DividerGridItemDecoration(Context context, int orientation) {
            this(context,orientation, R.color.colorAccent);
        }
    
        public DividerGridItemDecoration(Context context, int orientation, int dividerColor){
            this(context,orientation,dividerColor,1);
        }
    
        /**
         * @param context
         * @param orientation 绘制方向
         * @param dividerColor 分割线颜色 颜色资源id
         * @param mItemSize 分割线宽度 传入dp值就行
         */
        public DividerGridItemDecoration(Context context, int orientation, int dividerColor, int mItemSize){
            this.mOrientation = orientation;
            if(orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL){
                throw new IllegalArgumentException("请传入正确的参数") ;
            }
            //把dp值换算成px
            this.mItemSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,mItemSize,context.getResources().getDisplayMetrics());
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
            mPaint.setColor(context.getResources().getColor(dividerColor));
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            drawHorizontal(c, parent);
            drawVertical(c, parent);
        }
    
        private int getSpanCount(RecyclerView parent) {
            // 列数
            int spanCount = -1;
            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
            if (layoutManager instanceof GridLayoutManager) {
                spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
            } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
            }
            return spanCount;
        }
    
        public void drawHorizontal(Canvas canvas, RecyclerView parent) {
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = parent.getChildAt(i);
                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
                final int left = child.getLeft() - params.leftMargin;
                final int right = child.getRight() + params.rightMargin + mItemSize;
                final int top = child.getBottom() + params.bottomMargin;
                final int bottom = top + mItemSize;
                canvas.drawRect(left,top,right,bottom,mPaint);
            }
        }
    
        public void drawVertical(Canvas canvas, RecyclerView parent) {
            final int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = parent.getChildAt(i);
    
                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
                final int top = child.getTop() - params.topMargin;
                final int bottom = child.getBottom() + params.bottomMargin;
                final int left = child.getRight() + params.rightMargin;
                final int right = left + mItemSize;
                canvas.drawRect(left,top,right,bottom,mPaint);
            }
        }
    
        @Override
        public void getItemOffsets(Rect outRect, int itemPosition,RecyclerView parent) {
            int spanCount = getSpanCount(parent);
            int childCount = parent.getAdapter().getItemCount();
    
            if (isLastRow(parent, itemPosition, spanCount, childCount)){//如果是最后一行,不需要绘制底部
                outRect.set(0, 0, mItemSize, 0);
            } else if (isLastColum(parent, itemPosition, spanCount, childCount)){// 如果是最后一列,不需要绘制右边
                outRect.set(0, 0, 0, mItemSize);
            } else {
                outRect.set(0, 0, mItemSize,mItemSize);
            }
        }
    
        private boolean isLastColum(RecyclerView parent, int pos, int spanCount, int childCount) {
            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
            if (layoutManager instanceof GridLayoutManager) {
                if ((pos + 1) % spanCount == 0){// 如果是最后一列,则不需要绘制右边
                    return true;
                }
            } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();
                if (orientation == StaggeredGridLayoutManager.VERTICAL) {
                    if ((pos + 1) % spanCount == 0){// 如果是最后一列,则不需要绘制右边
                        return true;
                    }
                } else {
                    childCount = childCount - childCount % spanCount;
                    if (pos >= childCount)// 如果是最后一列,则不需要绘制右边
                        return true;
                }
            }
            return false;
        }
    
        private boolean isLastRow(RecyclerView parent, int pos, int spanCount, int childCount) {
            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
            if (layoutManager instanceof GridLayoutManager) {
                childCount = childCount - childCount % spanCount;
                if (pos >= childCount)//最后一行
                    return true;
            } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();
                if (orientation == StaggeredGridLayoutManager.VERTICAL){//纵向
                    childCount = childCount - childCount % spanCount;
                    if (pos >= childCount)//最后一行
                        return true;
                } else{ //横向
                    if ((pos + 1) % spanCount == 0) {//是最后一行
                        return true;
                    }
                }
            }
            return false;
        }
    }
    

    写了这两个画分割线的类,主流的布局:线性列表跟网格列表都能展示了。。。赶紧运行代码看看结果:

    网格列表网格列表

    StaggeredGridLayoutManager

    actviity中修改下布局管理器,大家应该感觉很熟悉了吧~~~

    recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL));
    

    瀑布流列表一般列的高度是不一致的,为了模拟不同的宽高,数据源我把String类型改成了对象.然后初始化的时候随机了一个高度.

    public class ItemData {
        private String content;//item内容
        private int height;//item高度
    
        public ItemData() {
        }
    
        public ItemData(String content, int height) {
            this.content = content;
            this.height = height;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
        public int getHeight() {
            return height;
        }
    
        public void setHeight(int height) {
            this.height = height;
        }
    }
    

    瀑布流列表没有添加分割线,给item布局设置了android:padding属性。recycler_staggered_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:padding="5dp"
        android:layout_width="wrap_content"
        android:layout_height="match_parent">
    
        <TextView
            android:id="@+id/textview"
            android:background="@color/colorAccent"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="122"
            android:textSize="20sp"/>
    </FrameLayout>
    

    最后我们在适配器的onBindViewHolder方法中给itemd中的TextView设置一个高度

    @Override
    public void onBindViewHolder(StaggeredGridAdapter.MyViewHolder holder, int position) {
        ItemData itemData=datas.get(position);
        holder.textview.setText(itemData.getContent());
        //手动更改高度,不同位置的高度有所不同
        holder.textview.setHeight(itemData.getHeight());
    }
    

    是不是感觉so easy,赶紧运行看看效果:

    瀑布流效果瀑布流效果

    添加header跟footer

    RecyclerView添加头部跟底部是没有对应的api的,但是我们很多的需求都会用到,于是只能自己想办法实现了。我们可以通过适配器的getItemViewType方法来实现这个功能。

    修改后的适配器代码:RecyclerHeadFootViewAdapter.java

    public class RecyclerHeadFootViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
        private List<String> datas;
        private LayoutInflater inflater;
    
        public static final int TYPE_HEADER=1;//header类型
        public static final int TYPE_FOOTER=2;//footer类型
        private View header=null;//头View
        private View footer=null;//脚View
    
        public  RecyclerHeadFootViewAdapter(Context context, List<String> datas){
            inflater=LayoutInflater.from(context);
            this.datas=datas;
        }
    
        //创建每一行的View 用RecyclerView.ViewHolder包装
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if(viewType==TYPE_HEADER){
                return new RecyclerView.ViewHolder(header){};
            }else if(viewType==TYPE_FOOTER){
                return new RecyclerView.ViewHolder(footer){};
            }
            View itemView=inflater.inflate(R.layout.recycler_item,null);
            return new MyViewHolder(itemView);
        }
    
        //给每一行View填充数据
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position){
            if(getItemViewType(position)==TYPE_HEADER||getItemViewType(position)==TYPE_FOOTER){
                return;
            }
            MyViewHolder myholder= (MyViewHolder) holder;
            myholder.textview.setText(datas.get(getRealPosition(position)));
        }
        
        //如果有头部 position的位置是从1开始的  所以需要-1
        public int getRealPosition(int position){
            return header==null?position:position-1;
        }
    
        //数据源的数量
        @Override
        public int getItemCount() {
            if(header == null && footer == null){//没有head跟foot
                return datas.size();
            }else if(header == null && footer != null){//head为空&&foot不为空
                return datas.size() + 1;
            }else if (header != null && footer == null){//head不为空&&foot为空
                return datas.size() + 1;
            }else {
                return datas.size() + 2;//head不为空&&foot不为空
            }
        }
    
        @Override
        public int getItemViewType(int position){
            //如果头布局不为空&&位置是第一个那就是head类型
            if(header!=null&&position==0){
                return TYPE_HEADER;
            }else if(footer!=null&&position==getItemCount()-1){//如果footer不为空&&最后一个
                return TYPE_FOOTER;
            }
            return super.getItemViewType(position);
        }
    
        public void setHeader(View header) {
            this.header = header;
    
            notifyItemInserted(0);//在位置0插入一条数据,然后刷新
        }
    
        public void setFooter(View footer) {
            this.footer = footer;
            notifyItemInserted(datas.size()-1);//在尾部插入一条数据,然后刷新
        }
    
        class MyViewHolder extends RecyclerView.ViewHolder{
            private TextView textview;
    
            public MyViewHolder(View itemView) {
                super(itemView);
                textview= (TextView) itemView.findViewById(R.id.textview);
            }
        }
    }
    
    • getItemCount

      有header跟footer的时候需要在源数据长度基础上进行增加。

    • getItemViewType

      通过getItemViewType判断不同的类型

    • onCreateViewHolder

      通过不同的类型创建item的View

    • onBindViewHolder

    如果是header跟footer类型是不需要绑定数据的,header跟footer的View一般在actvity中创建,不需要这边做处理,所以这两种类型我们就不往下执行,如果有头布局,position==0的位置被header占用了,但是我们的数据源也就是集合的下标是从0开始的,所以这里需要-1。

    • setHeader

      设置头布局,在第一行插入一条数据,然后刷新。注意这个方法调用后会有插入的动画,这个动画可以使用默认的,也可以自己定义

    • setFooter

      设置尾部布局,在尾部插入一条数据,然后刷新。

    添加header跟footer的方法终于封装好了,在activity中只需要两行代码就能添加header,跟ListView调用addHeader方法一样简单,又可以happy的玩耍了。这里需要注意的是我们初始化View的时候,inflate方法需要三个参数。

    • resource 资源id
    • root 父View
    • attachToRoot true:返回父View false:返回资源id生成的View
    //添加header
    View header=LayoutInflater.from(this).inflate(R.layout.recycler_header,recyclerView,false);
    adapter.setHeader(header);
    
    //添加footer
    View footer=LayoutInflater.from(this).inflate(R.layout.recycler_footer,recyclerView,false);
    adapter.setFooter(footer);
    

    recycler_header跟recycler_footer布局文件我就不贴出来了,就一个TextView,我们直接看效果图:


    RecyclerView添加头跟尾RecyclerView添加头跟尾

    item点击事件&&增加或删除带动画效果

    当我们调用RecyclerView的setOnItemClickListener方法的时候,发现居然没有,用了RecyclerView你要习惯什么东西都自己封装。。。

    首先我们从adapter开刀,内部写一个接口,一个实例变量,提供一个公共方法,设置监听。

    private RecyclerViewItemClick recyclerViewItemClick;
    
    public void setRecyclerViewItemClick(RecyclerViewItemClick recyclerViewItemClick) {
        this.recyclerViewItemClick = recyclerViewItemClick;
    }
    
    public interface RecyclerViewItemClick{
        /**
         * item点击
         * @param realPosition 数据源position
         * @param position view position
         */
        void onItemClick(int realPosition,int position);
    }
    

    在onBindViewHolder方法中给item监听点击事件

    if(recyclerViewItemClick!=null) {
        myholder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                recyclerViewItemClick.onItemClick(getRealPosition(position),position);
            }
        });
    }
    

    在activity的onCreate方法中进行监听,顺便设置item增加删除动画。我用的是sdk自带的默认动画。

    adapter.setRecyclerViewItemClick(recyclerViewItemClick);
    recyclerView.setItemAnimator(new DefaultItemAnimator());
    
    private RecyclerHeadFootViewAdapter.RecyclerViewItemClick recyclerViewItemClick=new RecyclerHeadFootViewAdapter.RecyclerViewItemClick() {
        @Override
        public void onItemClick(int realPosition, int position) {
            Log.i("ansen","删除数据:"+realPosition+" view位置:"+position);
            Log.i("ansen","当前位置:"+position+" 更新item数量:"+(adapter.getItemCount()-position-1));
    
            datas.remove(realPosition);//删除数据源
            adapter.notifyItemRemoved(position);//item移除动画
            //更新position至adapter.getItemCount()-1的数据
            adapter.notifyItemRangeChanged(position,adapter.getItemCount()-position-1);
        }
    };
    

    源码下载

    RecyclerView

    相关文章

      网友评论

        本文标题:RecyclerView

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