Android控件—RecyclerView

RecyclerView

  RecyclerView 与 ListView、GridView 类似,都是可以显示同一种类型 View 的集合的控件。

一.基本使用

  RecyclerView的基本使用分四步:
1.build.gradle 文件中加入

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

2.创建RecyclerVIew对象

RecyclerView rv = (RecyclerView) findViewById(R.id.recyclerView);

3.设置布局管理器

 LinearLayoutManager manager = new LinearLayoutManager(this,LinearLayout.HORIZONTAL,false);
 rv.setLayoutManager(manager);

4.设置适配器

rv.setAdapter(adapter);

与ListView、GridView一样,我们需要为这些控件增加适配器用来设置每个item的显示内容,通常就是自定义一个适配器继承BaseAdapter,实现四个抽象方法,创建一个内部类ViewHolder,在getView() 方法中判断 convertView是否为空,创建还是获取viewholder对象。而RecyclerView也是类似的实现,首先自定义一个适配器类继承RecyclerView.Adapter类,实现三个抽象方法,创建一个内部类ViewHolder,必须继承RecyclerView.ViewHolder,另外,自定义适配器类继承时的泛型必须为此ViewHolder。

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{

    private Context context;
    private List<String> list;

    public MyAdapter(Context context,List<String> list){
        this.context = context;
        this.list = list;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context)
                .inflate(R.layout.recycler_view_item,parent,false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        holder.tv.setText(list.get(position));
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder{
        ImageView iv;
        TextView tv;
        Button btn;
        public ViewHolder(View itemView) {
            super(itemView);
            iv = (ImageView) itemView.findViewById(R.id.iv);
            tv = (TextView) itemView.findViewById(R.id.tv);
            btn = (Button) itemView.findViewById(R.id.btn);
        }
    }
}

二.布局管理

  RecyclerView 将所有的显示规则交给一个叫 LayoutManager 的类去完成了。LayoutManager 是一个抽象类,系统已经为我们提供了三个默认的实现类,分别是 LinearLayoutManager、GridLayoutManager 、 StaggeredGridLayoutManager,从名字我们就能看出来了,分别是,线性显示、网格显示、瀑布流显示。当然你也可以通过继承这些类来扩展实现自己的LayougManager。
* LinearLayoutManager

LinearLayoutManager manager = new LinearLayoutManager(this,LinearLayout.HORIZONTAL,false);

创建一个线性布局管理器对象,第一个参数Context,第二个参数为设置布局方向,水平or竖直,第三个参数表示false表示正常显示,true表示倒序展示,并且初始展示为最后。

  • GridLayoutManager
GridLayoutManager manager = new GridLayoutManager(this,3);

创建一个网格布局管理对象,第一个参数为Context,第二个参数为设置布局方向。

  • StaggeredGridLayoutManager
StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);

创建一个瀑布流布局管理对象,第一个参数为列数,第二个参数为设置布局方向。
主要实现瀑布流的方式为,在onBindViewHolder方法中给itemView赋于不同的高度:

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
        layoutParams.height = 200 + (position % 2) * 50;
        holder.itemView.setLayoutParams(layoutParams);
    }

三.添加与删除(动画)

  同 ListView 每次修改了数据源后,都要调用 notifyDataSetChanged() 刷新每项 item 类似,只不过RecyclerView 还支持局部刷新 notifyItemInserted(index); notifyItemRemoved(position)、 notifyItemChanged(position)。在添加或删除了数据后,RecyclerView 还提供了一个默认的动画效果,来改变显示。同时,你也可以定制自己的动画效果,模仿 DefaultItemAnimator 或直接继承这个类,实现自己的动画效果,并调用recyclerview.setItemAnimator(new DefaultItemAnimator()); 设置上自己的动画(github上面有很多可参考)。 适配器中添加刷新数据方法如下:

   /**
     * 增加一项
     * @param position
     * @param str
     */
    public void add(int position,String str){
        list.add(position,str);
        notifyItemInserted(position);
    }

    /**
     * 删除一项
     * @param position
     */
    public void remove(int position){
        list.remove(position);
        notifyItemRemoved(position);
    }

四.Click and LongClick

  用习惯了 ListView 的 OnItemClickListener,RecyclerView的 OnItemClickListener 呢? 查看API后,只提供了OnItemTouchListener,点击事件需要我们自己定义添加,常见的方式有两种:
1. 通过mRecyclerView.addOnItemTouchListener去监听然后去判断手势

public abstract class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener{

    protected abstract void onItemClick(View view, int position);
    protected abstract void onLongItemClick(View view,int position);

    private GestureDetector mGestureDetector;

    public RecyclerItemClickListener(Context context, final RecyclerView recyclerView) {
        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                View childView = recyclerView.findChildViewUnder(e.getX(),e.getY());
                if(childView != null){
                    onItemClick(childView,recyclerView.getChildLayoutPosition(childView));
                    return true;
                }
                return false;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
                if (childView != null) {
                    onLongItemClick(childView, recyclerView.getChildAdapterPosition(childView));
                }
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
        //把事件交给GestureDetector处理
        if(mGestureDetector.onTouchEvent(e))
            return true;
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) {
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    }
}
  1. 通过adapter中自己去提供回调方法
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{

    private Context context;
    private List<String> list;

    public MyAdapter(Context context,List<String> list){
        this.context = context;
        this.list = list;
    }

    /**
     * 增加一项
     * @param position
     * @param str
     */
    public void add(int position,String str){
        list.add(position,str);
        notifyItemInserted(position);
    }

    /**
     * 删除一项
     * @param position
     */
    public void remove(int position){
        list.remove(position);
        notifyItemRemoved(position);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context)
                .inflate(R.layout.recycler_view_item,parent,false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        if (mOnItemClickLitener != null){
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mOnItemClickLitener.onItemClick(v,position);
                }
            });
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    mOnItemClickLitener.onItemLongClick(v,position);
                    return false;
                }
            });
        }
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder{

        ImageView iv;
        public ViewHolder(View itemView) {
            super(itemView);
            iv = (ImageView) itemView.findViewById(R.id.iv);
        }
    }

    public interface OnItemClickLitener {
        void onItemClick(View view, int position);
        void onItemLongClick(View view , int position);
    }
    private OnItemClickLitener mOnItemClickLitener;

    public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener) {
        this.mOnItemClickLitener = mOnItemClickLitener;
    }
}

五.定制分割线

在ListView中设置 divider 非常简单,只需要在XML文件中设置就可以了,同时还可以设置 divider 高度。

android:divider="@android:color/black"
android:dividerHeight="2dp"

而在RecyclerView里面,想实现这两种需求,稍微复杂一点,需要自己继承RecyclerView.ItemDecoration来实现想要实现的方法。这里封装了一个可以满足基本需求:

public class Divider extends RecyclerView.ItemDecoration{

    private Drawable mDivider;
    private int leftMargin, rightMargin, topMargin, bottomMargin;
    private int width, height;
    private int mOrientation;

    public Divider(Drawable divider, int orientation) {
        setDivider(divider);
        setOrientation(orientation);
    }

    private void setDivider(Drawable divider) {
        this.mDivider = divider;
        if (mDivider == null) {
            mDivider = new ColorDrawable(0xffff0000);
        }
        width = mDivider.getIntrinsicWidth();
        height = mDivider.getIntrinsicHeight();
    }

    private void setOrientation(int orientation) {
        if (orientation != LinearLayoutManager.HORIZONTAL && orientation != LinearLayoutManager.VERTICAL) {
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }

    public void setMargin(int left, int top, int right, int bottom) {
        this.leftMargin = left;
        this.topMargin = top;
        this.rightMargin = right;
        this.bottomMargin = bottom;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        if (mOrientation == LinearLayoutManager.HORIZONTAL) {
            drawHorizontal(c, parent);
        } else {
            drawVertical(c, parent);
        }
    }

    public void drawHorizontal(Canvas c, RecyclerView parent) {
        final int top = parent.getPaddingTop() + topMargin;
        final int bottom = parent.getHeight() - parent.getPaddingBottom() - bottomMargin;

        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 + leftMargin;
            final int right = left + width;
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    public void drawVertical(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft() + leftMargin;
        final int right = parent.getWidth() - parent.getPaddingRight() - rightMargin;

        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.getBottom() + params.bottomMargin + topMargin;
            final int bottom = top + height;
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if (mOrientation == LinearLayoutManager.HORIZONTAL) {
            outRect.set(0, 0, leftMargin + width + rightMargin, 0);
        } else {
            outRect.set(0, 0, 0, topMargin + height + bottomMargin);
        }
    }
}

六.缓存与复用原理

  RecyclerView 的内部维护了一个二级缓存,滑出界面的 ViewHolder 会暂时放到 cache 结构中,而从 cache 结构中移除的 ViewHolder,则会放到一个叫做 RecycledViewPool 的循环缓存池中。
  顺带一说,RecycledView 的性能并不比 ListView 要好多少,它最大的优势在于其扩展性。但是有一点,在 RecycledView 内部的这个第二级缓存池 RecycledViewPool 是可以被多个 RecyclerView 共用的,这一点比起直接缓存 View 的 ListView 就要高明了很多,但也正是因为需要被多个 RecyclerView 公用,所以我们的 ViewHolder 必须继承自同一个基类(即RecyclerView.ViewHolder)。
  默认的情况下,cache 缓存 2 个 holder,RecycledViewPool 缓存 5 个 holder。对于二级缓存池中的 holder 对象,会根据 viewType 进行分类,不同类型的 viewType 之间互不影响。

Recycler
一个超大型的缓存器,拥有三级缓存(如果算上创建的那一次,应该是四级了)

   public final class Recycler {
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        private ArrayList<ViewHolder> mChangedScrap = null;

        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

        private final List<ViewHolder>
                mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);

        private int mViewCacheMax = DEFAULT_CACHE_SIZE;

        private RecycledViewPool mRecyclerPool;

        private ViewCacheExtension mViewCacheExtension;

        private static final int DEFAULT_CACHE_SIZE = 2;
  • 第一级缓存
      就是上面的一系列 mCachedViews。如果仍依赖于 RecyclerView (比如已经滑动出可视范围,但还没有被移除掉),但已经被标记移除的 ItemView 集合会被添加到 mAttachedScrap 中。然后如果 mAttachedScrap 中不再依赖时会被加入到 mCachedViews 中。 mChangedScrap 则是存储 notifXXX 方法时需要改变的 ViewHolder 。
  • 第二级缓存
      ViewCacheExtension 是一个抽象静态类,用于充当附加的缓存池,当 RecyclerView 从第一级缓存找不到需要的 View 时,将会从 ViewCacheExtension 中找。不过这个缓存是由开发者维护的,如果没有设置它,则不会启用。通常我们也不会去设置他,系统已经预先提供了两级缓存了,除非有特殊需求,比如要在调用系统的缓存池之前,返回一个特定的视图,才会用到他。
  • 第三极缓存
      最强大的缓存器。之前讲了,与 ListView 直接缓存 ItemView 不同,从上面代码里我们也能看到,RecyclerView 缓存的是 ViewHolder。而 ViewHolder 里面包含了一个 View 这也就是为什么在写 Adapter 的时候 必须继承一个固定的 ViewHolder 的原因。首先来看一下 RecycledViewPool:
   public static class RecycledViewPool {
        private SparseArray<ArrayList<ViewHolder>> mScrap =
                new SparseArray<ArrayList<ViewHolder>>();
        private SparseIntArray mMaxScrap = new SparseIntArray();
        private int mAttachCount = 0;

        private static final int DEFAULT_MAX_SCRAP = 5;

        public void clear() {
            mScrap.clear();
        }

        public void setMaxRecycledViews(int viewType, int max) {
            mMaxScrap.put(viewType, max);
            final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
            if (scrapHeap != null) {
                while (scrapHeap.size() > max) {
                    scrapHeap.remove(scrapHeap.size() - 1);
                }
            }
        }

        public ViewHolder getRecycledView(int viewType) {
            final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
            if (scrapHeap != null && !scrapHeap.isEmpty()) {
                final int index = scrapHeap.size() - 1;
                final ViewHolder scrap = scrapHeap.get(index);
                scrapHeap.remove(index);
                return scrap;
            }
            return null;
        }

        int size() {
            int count = 0;
            for (int i = 0; i < mScrap.size(); i ++) {
                ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i);
                if (viewHolders != null) {
                    count += viewHolders.size();
                }
            }
            return count;
        }

        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList scrapHeap = getScrapHeapForType(viewType);
            if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
                return;
            }
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            scrap.resetInternal();
            scrapHeap.add(scrap);
        }

从名字来看,他是一个缓存池,实现上,是通过一个默认为 5 大小的 ArrayList 实现的。这一点,同 ListView 的 RecyclerBin 这个类一样。然后每一个 ArrayList 又都是放在一个 Map 里面的,SparseArray 这个类我们在讲性能优化的时候已经多次提到了,就是两个数组,用来替代 Map 的。把所有的 ArrayList 放在一个 Map 里面,这也是 RecyclerView 最大的亮点,这样根据 itemType 来取不同的缓存 Holder,每一个 Holder 都有对应的缓存,而只需要为这些不同 RecyclerView 设置同一个 Pool 就可以了。

参考:http://kymjs.com/code/2016/07/10/01

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值