RecyclerView知识点串讲

来源:互联网 发布:linux 编译打包war 编辑:程序博客网 时间:2024/05/18 00:18

RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替代性比listview更好。

概述

RecyclerView出现已经有一段时间了,相信大家肯定不陌生了,大家可以通过导入support-v7对其进行使用。
据官方的介绍,该控件用于在有限的窗口中展示大量数据集,其实这样功能的控件我们并不陌生,例如:ListView、GridView。

那么有了ListView、GridView为什么还需要RecyclerView这样的控件呢?整体上看RecyclerView架构,提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator实现令人瞠目的效果。

  • 你想要控制其显示的方式,请通过布局管理器LayoutManager
  • 你想要控制Item间的间隔(可绘制),请通过ItemDecoration
  • 你想要控制Item增删的动画,请通过ItemAnimator
  • 你想要控制点击、长按事件,请自己写(擦,这点尼玛。)

基本使用

RecyclerView 与 ListView、GridView 类似,都是可以显示同一种类型 View 的集合的控件。
首先看看最简单的用法,四步走:

  1. 接入 build.gradle 文件中加入

    compile 'com.android.support:recyclerview-v7:24.0.0'
  2. 创建对象

    RecyclerView recyclerview = (RecyclerView) findViewById(R.id.recyclerview);
  3. 设置显示规则

    recyclerview.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));

    RecyclerView 将所有的显示规则交给一个叫 LayoutManager 的类去完成了。
    LayoutManager 是一个抽象类,系统已经为我们提供了三个默认的实现类,分别是:

    1. LinearLayoutManager
    2. GridLayoutManager
    3. StaggeredGridLayoutManager
      从名字我们就能看出来了,分别是,线性显示、网格显示、瀑布流显示。当然你也可以通过继承这些类来扩展实现自己的 LayougManager。
  4. 设置适配器

    recyclerview.setAdapter(adapter);

    适配器,同 ListView 一样,用来设置每个item显示内容的。
    通常,我们写 ListView 适配器,都是首先继承 BaseAdapter,实现四个抽象方法,创建一个静态 ViewHolder , getView() 方法中判断 convertView 是否为空,创建还是获取 viewholder 对象。
    而 RecyclerView 也是类似的步骤,首先继承RecyclerView.Adapter类,实现三个抽象方法,创建一个静态的 ViewHolder。不过 RecyclerView 的 ViewHolder 创建稍微有些限制,类名就是上面继承的时候泛型中声明的类名(好像反了,应该是上面泛型中的类名应该是这个holder的类名);并且 ViewHolder 必须继承自RecyclerView.ViewHolder类。

相比较于ListView的代码,ListView可能只需要去设置一个adapter就能正常使用了。而RecyclerView基本需要上面一系列的步骤,那么为什么会添加这么多的步骤呢?
那么就必须解释下RecyclerView的这个名字了,从它类名上看,RecyclerView代表的意义是,我只管Recycler View,也就是说RecyclerView只管回收与复用View,其他的你可以自己去设置。可以看出其高度的解耦,给予你充分的定制自由(所以你才可以轻松的通过这个控件实现ListView,GirdView,瀑布流等效果)。

  • MainActivity.java

    public class MainActivity extends Activity  {private RecyclerView recyclerView;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    //获取RecyclerView控件    recyclerView = (RecyclerView) findViewById(R.id.recyclerView);    //设置显示规则    recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));    //设置适配器    final MyAdapter myAdapter = new MyAdapter();    myAdapter.setOnItemClickListener(new MyAdapter.OnItemClickLitener() {        @Override        public void onItemClick_Add(View view, List<String> datas, int position) {            datas.add(position-1, "hello");            myAdapter.notifyItemInserted(position);        }        @Override        public void onItemClick_Delete(View view, List<String> datas, int position) {            datas.remove(position);            myAdapter.notifyItemRemoved(position);        }    });    recyclerView.setAdapter(myAdapter);    //设置分割线    MyItemDecoration divider = new MyItemDecoration(new ColorDrawable(0xffff0000), OrientationHelper.VERTICAL);    divider.setMargin(50,50,50,50);    divider.setHeight(20);    recyclerView.addItemDecoration(divider);}}
  • MyAdapter.java

    public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {/** * 定义RecyclerView的列表类型 */private static final int TYPE_HEADER = 1;private static final int TYPE_ITEM = 2;private static final int TYPE_FOOTER = 3;/** * OnItemClick点击事件 */public interface OnItemClickLitener {    void onItemClick_Add(View view, List<String> datas, int position);    void onItemClick_Delete(View view, List<String> datas, int position);}private OnItemClickLitener mOnItemClickLitener;public void setOnItemClickListener(OnItemClickLitener onItemClickLitener) {    mOnItemClickLitener = onItemClickLitener;}private List<String> datas;public MyAdapter() {    datas = new ArrayList(2);    datas.add("01");    datas.add("02");    datas.add("03");    datas.add("04");    datas.add("05");}@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {    LayoutInflater inflater = LayoutInflater.from(parent.getContext());    switch (viewType) {        case TYPE_HEADER:            return new HeadViewHolder(inflater.inflate(R.layout.layout_recycler_head, parent, false));        case TYPE_ITEM:            return new ItemViewHolder(inflater.inflate(R.layout.layout_recycler_item, parent, false));        case TYPE_FOOTER:            return new FooterViewHolder(inflater.inflate(R.layout.layout_recycler_footer, null));    }    return null;}@Overridepublic void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {    if (holder instanceof HeadViewHolder) {        ((HeadViewHolder) holder).mTextView.setText("我是一个Header");    } else if (holder instanceof FooterViewHolder) {        ((FooterViewHolder) holder).mTextView.setText("我是一个Footer");    } else {        ((ItemViewHolder)holder).txtInfo.setText(datas.get(position-1));        ((ItemViewHolder) holder).btnAdd.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                int pos = holder.getLayoutPosition();                mOnItemClickLitener.onItemClick_Add(v, datas, pos);            }        });        ((ItemViewHolder) holder).btnDelete.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                int pos = holder.getLayoutPosition();                mOnItemClickLitener.onItemClick_Delete(v, datas, pos);            }        });    }}@Overridepublic int getItemCount() {    return datas.size() + 2;}@Overridepublic int getItemViewType(int position) {    if (position == 0) {        return TYPE_HEADER;    } else if (position == datas.size() + 1) {        return TYPE_FOOTER;    }    return TYPE_ITEM;}//自定义的ViewHolder,持有每个Item的的所有界面元素public static class ItemViewHolder extends RecyclerView.ViewHolder {    public TextView txtInfo;    public Button btnAdd;    public Button btnDelete;    public ItemViewHolder(View view) {        super(view);        txtInfo = (TextView) view.findViewById(R.id.txtInfo);        btnAdd = (Button) view.findViewById(R.id.btnAdd);        btnDelete = (Button) view.findViewById(R.id.btnDelete);    }}public static class HeadViewHolder extends RecyclerView.ViewHolder {    public TextView mTextView;    public HeadViewHolder(View view) {        super(view);        mTextView = (TextView) view.findViewById(R.id.txtHeader);    }}public static class FooterViewHolder extends RecyclerView.ViewHolder {    public TextView mTextView;    public FooterViewHolder(View view) {        super(view);        mTextView = (TextView) view.findViewById(R.id.txtFooter);    }}}
  • MyItemDecoration.java

    public class MyItemDecoration extends RecyclerView.ItemDecoration {private Drawable mDivider;private int leftMargin, rightMargin, topMargin, bottomMargin;private int width, height;private int mOrientation;public MyItemDecoration(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;}@Overridepublic 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);    }}@Overridepublic 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);    }}}
  • layout_recycler_head.xml

 <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="fill_parent"              android:background="#FFFF00"    android:orientation="horizontal" >    <TextView        android:id="@+id/txtHeader"        android:text="Header"        android:layout_width="match_parent"        android:layout_height="175dp"        android:textColor="#ff000000"        android:gravity="center"        android:textSize="20sp" /></LinearLayout>
  • yout_recycler_item.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="fill_parent"    android:orientation="horizontal" >    <TextView        android:id="@+id/txtInfo"        android:layout_width="0dp"        android:layout_height="80dp"        android:layout_weight="1"        android:textColor="#ff000000"        android:gravity="center"        android:layout_gravity="center"        android:text="Hello"        android:textSize="20sp" />    <Button        android:id="@+id/btnAdd"        android:text="新增"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:layout_weight="1"/>    <Button        android:id="@+id/btnDelete"        android:text="删除"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:layout_weight="1"/></LinearLayout>
  • layout_recycler_footer.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"              android:layout_width="fill_parent"              android:layout_height="fill_parent"              android:background="#FFFF00"              android:orientation="horizontal">    <TextView        android:id="@+id/txtFooter"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_weight="1"        android:gravity="center"        android:text="Footer"        android:textColor="#ff000000"        android:textSize="20sp"/></LinearLayout>

RecyclerView显示Item布局不一致

在自定义RecyclerAdapter的时候,在重写onCreateViewHolder方法是使用了

    @Override    public H onCreateViewHolder(ViewGroup parent, int viewType) {        View view=View.inflate(context,layoutId,null);        return view;    }

进行生成布局,结果发现生成的布局没有LayoutParams。以前自定义View的时候发现,LayoutParams是由于ViewGroup生成的,因为这里添加的ViewGroup为null。所以并不会生成LayoutParams。结果在RecyclerView的getViewForPosition方法中检查了有没有LayoutParams如果没有的话就调用LayoutManager的generateDefaultLayoutParams生成默认的LayoutParames。代码段如下:

final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();            final LayoutParams rvLayoutParams;            if (lp == null) {                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();                holder.itemView.setLayoutParams(rvLayoutParams);            } else if (!checkLayoutParams(lp)) {                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);                holder.itemView.setLayoutParams(rvLayoutParams);            } else {                rvLayoutParams = (LayoutParams) lp;            }

而在LinearLayoutManager中generateDefaultLayoutParams方法实现如下。

 /**     * {@inheritDoc}     */    @Override    public LayoutParams generateDefaultLayoutParams() {        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,                ViewGroup.LayoutParams.WRAP_CONTENT);    }

最终会造成RecycleView的显示效果与布局文件不一致。后来使用了LayoutInflater来填充布局。

@Override    public H onCreateViewHolder(ViewGroup parent, int viewType) {        View view = mInflater.inflate(layoutId, parent, false);        return getInstanceOfH(view);    }

查看LayoutInflater源码发现inflate最后的参数如果是false的话就不会将生成的View添加到parent。但是会根据parent产生相应的LayoutParams 。源码如下:

 * @param attachToRoot Whether the inflated hierarchy should be attached to     *        the root parameter? If false, root is only used to create the     *        correct subclass of LayoutParams for the root view in the XML.

因为在onCreateViewHolder中产生的View不能由我们手动添加到RecycleView中所以最后的参数只能是false;


吐槽

OnItemTouchListener 什么鬼?
用习惯了 ListView 的 OnItemClickListener ,RecyclerView 你的 OnItemClickListener 呢?
Tell me where do I find, something like ListView listener ?

好吧,翻遍了 API 列表,就找到了个 OnItemTouchListener ,这特么什么鬼,我干嘛要对每个 item 监听触摸屏事件。
万万没想到,最终我还是在 Google IO 里面的介绍找到了原因。原来是 Google 的工程师分不清究竟是改给 listview 的 item 添加点击事件,还是应该给每个 item 的 view 添加点击事件,索性就不给 OnItemClickListener 了,然后在 support demo 里面,你就会发现,RecyclerView 的 item 点击事件都是写在了 adapter 的 ViewHolder 里面。

RecyclerView是一个高度自由的控件,它把控件位置的摆放交给LayoutManager去管理,RecyclerView的数据交给Adapter类去管理,RecyclerView的分割线交给RecyclerView.ItemDecoration的实现类去管理(需用户自己实现,系统未给出默认实现类),

0 0