zoukankan      html  css  js  c++  java
  • 【Android

      RecyclerView是Android 5.0新特性——Material Design中的一个控件,它将ListView、GridView整合到一起,可以使用极少的代码在ListView、GridView和瀑布流等布局方式之间转换。RecyclerView整体使用的是插件式的方式,解耦度相比提高了不少,非常灵活。

      RecyclerView之所以叫RecyclerView,是因为它的特性:它不关心Item是否显示在正确的位置上;不关心Item间如何分隔;不关心增加与删除的动画效果,只关心如何回收和复用View。

    RecyclerView可以实现的Item布局方式:

    • 类似ListView的样式(横、纵都可以实现)
    • 类似GridView的样式(横、纵都可以实现)
    • 瀑布流样式(交错布局)

    RecyclerView中可能用到的类:

    • LayoutManager:用来管理RecyclerView中的Item的布局方式
    • ItemDecoration:用来绘制RecyclerView中Item之间的间隔
    • ItemAnimation:用来绘制RecyclerView中的各种动画
    • RecyclerView.ViewHolder:用来存放每个Item中的控件
    • RecyclerView.Adapter:RecyclerView的适配器类的父类

    1、RecyclerView适配数据:

      RecyclerView适配数据的方法和ListView、GridView使用的BaseAdapter适配数据的方法不太相同,RecyclerView内部提供了一个ViewHolder用来盛放item中出现的控件,相当于BaseAdapter中我们自己定义的ViewHolder相同,从这可以看出,从RecyclerView开始,Android开始“逼”我们对Item进行回收和复用;RecyclerView内部还提供了一个Adapter,其中有三个抽象方法:

    • getItemCount():获取Item的个数
    • onCreateViewHolder():返回当前Item的ViewHolder
    • onBindViewHolder():向ViewHolder中适配数据

      说了这么多,下面贴一下RecyclerView的适配器类RecyclerAdaper中的代码:

    public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {
        private Context context;
        private List<String> data;
        private LayoutInflater inflater;
    
        public RecyclerAdapter(Context context, List<String> data, boolean isStagger, OnRecyclerViewItemOperationListener listener) {
            this.context = context;
            this.data = data;
            this.inflater = LayoutInflater.from(context);
        }
    
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = inflater.inflate(R.layout.recycleritem_item, parent, false);
            return new MyViewHolder(view);
        }
    
        @Override
        public void onBindViewHolder(final MyViewHolder holder, final int position) {
            holder.tv.setText(data.get(position));
        }
    
        @Override
        public int getItemCount() {
            return data.size();
        }
    
        static class MyViewHolder extends RecyclerView.ViewHolder {
            TextView tv;
    
            public MyViewHolder(View itemView) {
                super(itemView);
                tv = (TextView) itemView.findViewById(R.id.item_tv);
            }
        }
    }

    2、展示模式:

      如果是ListView或者GridView,那么直接设置适配器就可以显示数据了,而RecyclerView不行,因为RecyclerView是可以在ListView和GridView,甚至瀑布流之间进行任意切换的,因此我们还需要设置它的布局模式,这里就用到了LayoutManager类。LayoutManager是一个抽象类,我们最常用的子类有两个:LinearLayoutManager(适合线性布局,用于实现ListView的效果)和StaggeredGridLayoutManager(适合格子布局,用于实现GridView或瀑布流的效果)。

      我们可以通过RecyclerView对象的setLayoutManager()方法设置它的展示模式:

    rv.setLayoutManager(new LinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL, false));

    3、分隔线:

      这里说的分隔线是RecyclerView的Item之间的分隔线,其实我们大可以不用“正儿八经”的给RecyclerView设置分隔线,因为我们可以使用Item的margin来设置间距,简介实现分隔线的效果。

      RecyclerView也给我们提供了一个分隔线的抽象类——ItemDecoration,可以帮助我们实现分隔线,但是Android没有给我们提供这个类的子类,因此,我们需要自己去写。GitHub上有很多大神发布了一些分隔线的类,这里贴出其中一个DividerItemDecoration类来:

    public class DividerItemDecoration extends RecyclerView.ItemDecoration {
        // 系统默认的分隔条的Drawable资源的ID
        private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
        public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
        public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
    
        // 绘制Item间间隔的Drawable
        private Drawable mDivider;
        // 方向(水平、数值)
        private int mOrientation;
    
        public DividerItemDecoration(Context context, int orientation) {
            final TypedArray a = context.obtainStyledAttributes(ATTRS);
            // 获取系统提供的分隔条的Drawable对象
            mDivider = a.getDrawable(0);
            // 回收TypedArray所占用的控件
            a.recycle();
            setOrientation(orientation);
        }
    
        public void setOrientation(int orientation) {
            if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
                throw new IllegalArgumentException("invalid orientation");
            }
            mOrientation = orientation;
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent) {
            if (mOrientation == VERTICAL_LIST) {
                drawVertical(c, parent);
            } else {
                drawHorizontal(c, parent);
            }
        }
    
        /**
         * 如果设置为纵向列表的样式,则调用这个方法
         */
        public void drawVertical(Canvas c, RecyclerView parent) {
            // Item距离左边缘的距离
            final int left = parent.getPaddingLeft();
            // Item距离右边缘的距离
            final int right = parent.getWidth() - parent.getPaddingRight();
            // 获取Item的总数
            final int childCount = parent.getChildCount();
            // 开始绘制所有Item之间的分隔线
            for (int i = 0; i < childCount; i++) {
                final View child = parent.getChildAt(i);
                RecyclerView v = new RecyclerView(parent.getContext());
                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
                // Item距离上边缘的距离
                final int top = child.getBottom() + params.bottomMargin;
                // Item距离下边缘的距离
                final int bottom = top + mDivider.getIntrinsicHeight();
                // 分隔线可以看成是一个长方形,所以需要设置它的上下左右的位置
                mDivider.setBounds(left, top, right, bottom);
                // 开始绘制分隔线
                mDivider.draw(c);
            }
        }
    
        public void drawHorizontal(Canvas c, RecyclerView parent) {
            final int top = parent.getPaddingTop();
            final int bottom = parent.getHeight() - parent.getPaddingBottom();
            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 left = child.getRight() + params.rightMargin;
                final int right = left + mDivider.getIntrinsicHeight();
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
        }
    
        @Override
        public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
            if (mOrientation == VERTICAL_LIST) {
                outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
            } else {
                outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
            }
        }
    }

      这样,我们只需要调用下面这行代码,就可以为RecyclerView设置分隔线了:

            // 设置RecyclerView的分隔线
            rv.addItemDecoration(new DividerItemDecoration(MainActivity.this, DividerItemDecoration.VERTICAL_LIST));

      现在,我们倒回来看一下这个DividerItemDecoration类,其中提到了android.R.attr.listDivider这个属性,这个类中调用的是当前主题中设置的listDivider属性的值,我们可以通过修改主题中的这个属性来达到自定义分隔线的目的。我们只需要自己设计一个分隔线布局,然后在res/styles.xml文件中的AppTheme中添加下面这行代码,就可以实现自定义分隔线了:

    <item name="android:listDivider">@drawable/divider_gradient</item>

    4、动画:

      现在很多APP中都用到了RecyclerView,其中不乏有一些非常炫酷的动画,例如:向下滑动的时候使用动画添加Item、添加Item时候的动画、删除Item时候的动画等。GitHub上也有很多这类动画,大家可以找自己喜欢的动画来设置。

      这里用的是系统给我们提供的一种动画——DefaultItemAnimator,我们直接调用下面这行代码,就为RecyclerView设置好了动画。

            // 设置RecyclerView的动画效果
            rv.setItemAnimator(new DefaultItemAnimator());

      这个动画只有添加/删除Item的时候的动画,没有下滑加载Item时候的动画。

    5、点击和长按事件:

      RecyclerView中没有给我们提供OnClickListener、OnLongClickListener这类接口,因此,我们需要自己写,方法就是使用接口回调。我们可以在Adapter中设置一个接口,里面有点击和长按两个抽象方法,然后在onBindViewHolder()方法中设置Item的View的点击和长按事件,分别回调这两个抽象方法,然后在外界为Adapter对象设置这个接口即可。具体的Adapter的代码如下:

    public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {
        private Context context;
        private List<String> data;
        private LayoutInflater inflater;
    
        private OnRecyclerViewItemOperationListener listener;
    
        public RecyclerAdapter(Context context, List<String> data, boolean isStagger, OnRecyclerViewItemOperationListener listener) {
            this.context = context;
            this.data = data;
            this.inflater = LayoutInflater.from(context);
            this.listener = listener;
        }
    
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = inflater.inflate(R.layout.recycleritem_item, parent, false);
            return new MyViewHolder(view);
        }
    
        @Override
        public void onBindViewHolder(final MyViewHolder holder, final int position) {
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // 如果不使用这个方法,则获取添加/删除的Item的position会出错
                    int layoutPosition = holder.getLayoutPosition();
                    listener.onRecyclerViewItemClickListener(layoutPosition);
                }
            });
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    listener.onRecyclerViewItemLongClickListener(holder.getLayoutPosition());
                    return true;
                }
            });
            holder.tv.setText(data.get(position));
        }
    
        @Override
        public int getItemCount() {
            return data.size();
        }
    
        static class MyViewHolder extends RecyclerView.ViewHolder {
            TextView tv;
    
            public MyViewHolder(View itemView) {
                super(itemView);
                tv = (TextView) itemView.findViewById(R.id.item_tv);
            }
        }
    
        public interface OnRecyclerViewItemOperationListener {
            void onRecyclerViewItemClickListener(int position);
    
            void onRecyclerViewItemLongClickListener(int position);
        }
    }

    6、添加/删除Item:

      添加、删除操作主要就是修改适配器绑定的数据源中的数据,加一项或删一项。在RecyclerView中需要注意的是,如果我们为RecyclerView设置了动画,就不能调用Adapter对象的notifyDataSetChanged()方法去更新数据源,因为如果调用notifyDataSetChanged()方法,就没有了动画效果。我们需要调用notifyItemInserted()方法来更新添加Item后的数据源,调用notifyItemRemoved()方法来更新删除Item后的数据源。

      另外,在添加了新的Item之后,如果我们调用onBindViewHolder()方法参数中的position,就会出现新添加的Item的position不准确的问题,因此,我们需要使用holder.getLayoutPosition()方法来获取当前Item所在的位置。

      最后贴一下我做的一个RecyclerView的小DEMO中的截屏,然后贴源码地址:

          

          

      下面是码云上的源码地址,供大家参考。

    DEMO地址

  • 相关阅读:
    CSS样式
    Python宏观
    javaScript----------(函数)
    vue-----5计算属性
    python之函数作用域、嵌套以及闭包
    python之函数的定义、传参以及动态参数
    python之文件操作
    基础数据类型的补充以及深浅copy
    小数据池、代码块以及编码转换
    python基础二
  • 原文地址:https://www.cnblogs.com/itgungnir/p/6210798.html
Copyright © 2011-2022 走看看