RecyclerView 教程,全面了解各种使用方法

RecyclerView 在2014年就已经出来了,15年的时候有了解一下,但是项目中一直没用上,最近看到,发现RecyclerView 出现了很多拓展,它的出现就是为了代替ListView、GridView。所以介绍一下RecyclerView该如何使用,及梳理一下这些拓展应该怎么用,是个什么效果。

RecyclerView

RecyclerView 比 ListView 更高级且更具灵活性。 它是一个用于显示庞大数据集的容器,可通过保持有限数量的视图进行非常有效的滚动操作。 如果您有数据集合,其中的元素将因用户操作或网络事件而在运行时发生改变,请使用 RecyclerView 。

从它类名上看,RecyclerView代表的意义是,我只管Recycler View,也就是说RecyclerView只管回收与复用View,其他的你可以自己去设置。可以看出其高度的解耦,给予你充分的定制自由(所以你才可以轻松的通过这个控件实现ListView,GirdView,瀑布流等效果)。

在ListView中 改变列表某一个item数据,然后刷新列表,会回到最顶部,而RecyclerView可以保持原来滑动的位置不变。

要实现一个RecyclerView,会接触到它的几个小伙伴,其中1、2是必须的。剩下的3、4、5三项,可以让RecyclerView更好看、效果更好。

  1. 想要控制其item们的排列方式,请使用布局管理器LayoutManager

  2. 如果要创建一个适配器,请使用RecyclerView.Adapter

  3. 想要控制Item间的间隔,请使用RecyclerView.ItemDecoration

  4. 想要控制Item增删的动画,请使用RecyclerView.ItemAnimator

  5. CardView 扩展 FrameLayout 类并让您能够显示卡片内的信息,这些信息在整个平台中拥有一致的呈现方式。CardView 小部件可拥有阴影和圆角。

如果要使用 RecyclerView 小部件,必须指定一个Adapter和一个LayoutManager。

在下面会详解这些类的使用

RecyclerView 教程,全面了解各种使用方法_第1张图片

非常简单的一个示例

这里先给出一个简单的例子,感受一下怎么样使用RecyclerView

1、首先要用这个控件,你需要在gradle文件中添加包的引用

compile 'com.android.support:cardview-v7:21.0.3'
compile 'com.android.support:recyclerview-v7:21.0.3'

2、 然后是在XML文件用使用RecyclerView

.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/recycler_view"
    android:layout_centerVertical="true"
    android:layout_centerHorizontal="true"/>

3、在Activity中设置它

public class MainActivity extends ActionBarActivity {
    @InjectView(R.id.recycler_view)
    RecyclerView mRecyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);

        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));//这里用线性显示 类似于listview
//        mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));//这里用线性宫格显示 类似于grid view
//        mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));//这里用线性宫格显示 类似于瀑布流
        mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

4、Adapter 的Item的xml代码,使用CardView


<android.support.v7.widget.CardView
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    android:id="@+id/cv_item"
    card_view:cardCornerRadius="4dp">

    <TextView
        android:id="@+id/text_view"
        android:padding="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
android.support.v7.widget.CardView>

5、然后是适配器代码

public class NormalRecyclerViewAdapter extends RecyclerView.Adapter<NormalRecyclerViewAdapter.NormalTextViewHolder> {
    private final LayoutInflater mLayoutInflater;
    private final Context mContext;
    private String[] mTitles;

    public NormalRecyclerViewAdapter(Context context) {
        mTitles = context.getResources().getStringArray(R.array.titles);
        mContext = context;
        mLayoutInflater = LayoutInflater.from(context);
    }

    @Override
    public NormalTextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new NormalTextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
    }

    @Override
    public void onBindViewHolder(NormalTextViewHolder holder, int position) {
        holder.mTextView.setText(mTitles[position]);
    }

    @Override
    public int getItemCount() {
        return mTitles == null ? 0 : mTitles.length;
    }

    public static class NormalTextViewHolder extends RecyclerView.ViewHolder {
        @InjectView(R.id.text_view)
        TextView mTextView;

        NormalTextViewHolder(View view) {
            super(view);
            ButterKnife.inject(this, view);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("NormalTextViewHolder", "onClick--> position = " + getPosition());
                }
            });
        }
    }
}

至此一个简单的RecyclerView就这样很乖巧的趴在屏幕上,有图有真相:

LinearLayoutManager样式:
RecyclerView 教程,全面了解各种使用方法_第2张图片

GridLayoutManager样式:

RecyclerView 教程,全面了解各种使用方法_第3张图片


下面来围绕这个示例,对RecyclerView的其他小伙伴进行介绍:

二、LayoutManager

布局管理器,通过设置不同的布局管理器,来控制这些Item的排列方式。

RecyclerView提供的布局管理器:

  • LinearLayoutManager 以垂直或水平滚动列表方式显示项目。
  • GridLayoutManager 在网格中显示项目。
  • StaggeredGridLayoutManager 在分散对齐网格中显示项目。

使用这个函数来设置mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

三、Adapter

给recycleView提供数据的类,使用方法如下

 mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this));

可以看到数据适配器与BaseAdapter比较发生了相当大的变化,主要有3个方法:

getItemCount() 获取总的条目数
onCreateViewHolder() 创建ViewHolder
onBindViewHolder() 将数据绑定至ViewHolder

可见,RecyclerView对ViewHolder也进行了一定的封装,但是如果你仔细观察,你会发出一个疑问,ListView里面有个getView,它返回View为Item的布局,那么RecyclerView这个Item的样子在哪控制?

其实是这样的,我们创建的ViewHolder必须继承RecyclerView.ViewHolder,这个RecyclerView.ViewHolder构造时必须传入一个View,这个View相当于我们ListView getView中的convertView (即:inflate的item布局需要传入)。

还有一点,ListView中convertView是复用的,在RecyclerView中,是把ViewHolder类作为缓存的单位了,然后convertView作为ViewHolder的成员变量保持在ViewHolder中,也就是说,假设屏幕显示10个条目,则会创建10个ViewHolder缓存起来,每次复用的是ViewHolder,所以他把getView这个方法变为了onCreateViewHolder。


可以对Adapter进行一些个性化操作,实现不同的功能,下面给出两个示例:

瀑布式布局

ok,接下来准备看大招,如果让你去实现个瀑布流,最起码不是那么随意就可以实现的吧?但是,如果使用RecyclerView,分分钟的事。
那么如何实现?还是使用StaggeredGridLayoutManage,只需要在Adapter的onBindViewHolder()为我们的item设置个随机的高度,下面仅给出Adapter的代码(下面会给出全部工程代码):

public class WaterpallStaggeredAdapter extends
        RecyclerView.Adapter {

    private List mDatas;
    private LayoutInflater mInflater;

    private List mHeights;

    public interface OnItemClickListener {
        void onItemClick(View view, int position);

        void onItemLongClick(View view, int position);
    }

    private OnItemClickListener mOnItemClickListener;

    public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
        this.mOnItemClickListener = mOnItemClickListener;
    }

    public WaterpallStaggeredAdapter(Context context, List datas) {
        mInflater = LayoutInflater.from(context);
        mDatas = datas;

        mHeights = new ArrayList();
        for (int i = 0; i < mDatas.size(); i++) {
            mHeights.add((int) (100 + Math.random() * 300));
        }
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        MyViewHolder holder = new MyViewHolder(mInflater.inflate(
                R.layout.item_staggered_home, parent, false));
        return holder;
    }

    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {
        LayoutParams lp = holder.tv.getLayoutParams();
        lp.height = mHeights.get(position);

        holder.tv.setLayoutParams(lp);
        holder.tv.setText(mDatas.get(position));

        // 如果设置了回调,则设置点击事件
        if (mOnItemClickListener != null) {
            holder.itemView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickListener.onItemClick(holder.itemView, pos);
                }
            });

            holder.itemView.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickListener.onItemLongClick(holder.itemView, pos);
                    removeData(pos);
                    return false;
                }
            });
        }
    }

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

    public void addData(int position) {
        mDatas.add(position, "Insert One");
        mHeights.add((int) (100 + Math.random() * 300));
        notifyItemInserted(position);
    }

    public void removeData(int position) {
        mDatas.remove(position);
        notifyItemRemoved(position);
    }

    class MyViewHolder extends ViewHolder {

        TextView tv;

        public MyViewHolder(View view) {
            super(view);
            tv = (TextView) view.findViewById(R.id.id_num);

        }
    }
}

看下效果图:

RecyclerView 教程,全面了解各种使用方法_第4张图片

CursorAdapter(使用LoaderManager和CursorLoader实现)

如果你用RecyclerView,你会发现CursorAdapter这个类没有了,既然没有了,那我们就自己仿照着ListView的CursorAdapter类来实现,具体的代码没什么大的出入,无非就是注册两个观察者去监听数据库数据的变化。

如果对ListView的CursorAdapter使用不了解,可参考下面两篇文章
Android之CursorAdapter用法
Android中CursorAdapter的使用详解

在recycleView中实现CursorAdapter,有两个地方需要注意一下,一个就是hasStableIds() 这个方法RecyclerView.Adapter中不能复写父类的方法,需要在初始化的时候调用setHasStableIds(true); 来完成相同功能,第二个就是notifyDataSetInvalidated() 这个方法没有,统一修改成notifyDataSetChanged() 方法即可。

完整代码见BaseAbstractRecycleCursorAdapter,和ListView的CursorAdapter用法一致,见代码:ItemsFragment.java

关于RecyclerView的Adapter 各种解决方案

https://github.com/CymChad/BaseRecyclerViewAdapterHelper

四、ItemDecoration 添加分割线

使用方法:

mRecyclerView.addItemDecoration(new DividerItemDecoration(this,
DividerItemDecoration.VERTICAL_LIST));

ItemDecoration类很好的实现了RecyclerView添加分割线(当使用LayoutManager为LinearLayoutManager时)。 该实现类可以看到通过读取系统主题中的 Android.R.attr.listDivider作为Item间的分割线,并且支持横向和纵向。

该分割线是系统默认的,你可以在theme.xml中找到该属性的使用情况。

在styles.xml找使用的android:listDivider的xml——shape_divider,你也可以对其进行自定义

<resources>

    
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        -- Customize your theme here. -->
        <item name="android:listDivider">@drawable/shape_divider
    style>

resources>

下面给出DividerItemDecoration的代码

public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    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;
    private Drawable mDivider;
    private int mOrientation;

    public DividerItemDecoration(Context context, int orientation) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        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, RecyclerView.State state) {
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

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

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin;
            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 教程,全面了解各种使用方法_第5张图片RecyclerView 教程,全面了解各种使用方法_第6张图片

五、ItemAnimator

删除和添加的动画效果

ItemAnimator也是一个抽象类,好在系统为我们提供了一种默认的实现类

借助默认的实现,当Item添加和移除的时候,添加动画效果很简单:

// 设置item动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());

系统为我们提供了一个默认的实现,我们为我们的瀑布流添加以上一行代码,效果为:

RecyclerView 教程,全面了解各种使用方法_第7张图片

如果是GridLayoutManager呢?动画效果为:
RecyclerView 教程,全面了解各种使用方法_第8张图片

推荐2个RecyclerView的动画库 :
https://github.com/wasabeef/recyclerview-animators
https://github.com/gabrielemariotti/RecyclerViewItemAnimators

六、CardView

使用方法:在item的xml布局文件中,直接使用CardView即可。

CardView 扩展 FrameLayout 类并让您能够显示卡片内的信息,这些信息在整个平台中拥有一致的呈现方式。CardView 小部件可拥有阴影和圆角。

如果要使用阴影创建卡片,请使用 card_view:cardElevation 属性。CardView 在 Android 5.0(API 级别 21)及更高版本中使用真实高度与动态阴影,而在早期的 Android 版本中则返回编程阴影实现。

使用这些属性自定义 CardView 小部件的外观:

  • 如果要在您的布局中设置圆角半径,请使用 card_view:cardCornerRadius 属性。
  • 如果要在您的代码中设置圆角半径,请使用 CardView.setRadius 方法。
  • 如果要设置卡片的背景颜色,请使用 card_view:cardBackgroundColor 属性。

CardView示例图:
RecyclerView 教程,全面了解各种使用方法_第9张图片

使用CardView很简单,只需要在xml,创建CardView布局,见上面示例的第4点,然后在adapter中使用这个xml即可。

源代码:

https://github.com/JantXue/AndroidRecyclerViewDemo

参考:
上面图片都是来自,参考博客中,给出的代码实现和图片有些许差异。

还在用ListView?RecyclerView都已经出来一年多了!
Android 自定义RecyclerView 实现真正的Gallery效果
Android RecyclerView 使用完全解析 体验艺术般的控件
创建列表与卡片
RecyclerView使用详解(一)
RecyclerView使用详解(二)
RecyclerView使用详解(三)

关注我的公众号,轻松了解和学习更多技术
这里写图片描述

你可能感兴趣的:(Android)