RecyclerView详细解释

来源:互联网 发布:mysql 重启 编辑:程序博客网 时间:2024/06/07 13:56

原文链接:http://www.haotianyi.win/2016/11/recyclerview%E8%AF%A6%E7%BB%86%E8%A7%A3%E9%87%8A.html

RecyclerView详细解释


RecyclerView简单介绍

RecyclerView是一个高度的解耦,异常的灵活的View,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator实现令人瞠目的效果。

RecyclerView与老前辈ListView的不同点,主要在于以下几个特性:

Adapter中的ViewHolder模式 ­ 对于ListView来说,通过创建 ViewHolder 来提升性能并不是必须的。因为ListView并没有严格的ViewHolder设计模式。但是在使用RecyclerView的时候,Adapter 必须实现至少一个ViewHolder,必须遵循ViewHolder设计模式。

定制Item条目 ­ ListView只能实现垂直线性排列的列表视图,与之不同的是,RecyclerView可以通过设置RecyclerView.LayoutManager来定制不同风格的视图,比如水平滚动列表或者不规则的瀑布流列表。

tem动画 ­ 在ListView中没有提供任何方法或者接口,方便开发者实现Item的增删动画。相反地,可以通过设置RecyclerView的 RecyclerView.ItemAnimator 来为条目增加动画效果。

设置数据源 ­ 在LisView中针对不同数据封装了各种类型的 Adapter ,比如用来处理数组的 ArrayAdapter 和用来展示Database结果的 CursorAdapter 。相反地,在RecyclerView中必须自定义实现 RecyclerView.Adapter 并为其提供数据集合。

设置条目分割线 ­ 在ListView中可以通过设置 android:divider 属性来为两个Item间设置分割线。如果想为RecyclerView添加此效果,则必须使用 RecyclerView.ItemDecoration ,这种实现方式不仅更灵活,而且样式也更加丰富。

设置点击事件 ­ 在ListView中存在 AdapterView.OnItemClickListener 接口,用来绑定条目的点击事件。但是,很遗憾的是在RecyclerView中,并没有提供这样的接口,不过,提供了另外一个接口 RcyclerView.OnItemTouchListener ,用来响应条目的触摸事件。

RecyclerView简单使用

一个是在Gradle中直接添加:

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

版本号不用找,仿照上面的appcompat直接用,把appcompat改成recyclerview即可

compile 'com.android.support:appcompat-v7:24.2.1'

第二种是在Design面板直接拖拽,会提示你添加

sp161110_195241

核心类的介绍

RecyclerView的任务仅限于回收和定位屏幕上的View。 View能够显示数据还离不
开另外两个类的支持:Adapter子类和ViewHolder子类。

RecyclerView.Adapter  ­ 处理数据集合并负责绑定视图

ViewHolder  ­ 持有所有的用于绑定数据或者需要操作的View

LayoutManager  ­ 负责摆放视图等相关操作

ItemDecoration  ­ 负责绘制Item附近的分割线

ItemAnimator  ­ 为Item的一般操作添加动画效果,如,增删条目等

sp161111_103954

ViewHolder

ViewHolder只做一件事:容纳View视图

RecyclerView自身不会创建视图,它创建的是ViewHolder,而ViewHolder引用着一个个
itemView,如下图:
sp161110_195941

可以通过打印 ViewHolder.toString 来获取更多有效信息

@Overridepublic String toString() {    final StringBuilder sb = new StringBuilder("ViewHolder{" +            Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId +            ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);    if (isScrap()) {        sb.append(" scrap ")                .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]");    }    if (isInvalid()) sb.append(" invalid");    if (!isBound()) sb.append(" unbound");    if (needsUpdate()) sb.append(" update");    if (isRemoved()) sb.append(" removed");    if (shouldIgnore()) sb.append(" ignored");    if (isTmpDetached()) sb.append(" tmpDetached");    if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")");    if (isAdapterPositionUnknown()) sb.append(" undefined adapter position");     if (itemView.getParent() == null) sb.append(" no parent");    sb.append("}");    return sb.toString();}

Adapter

Adapter是个控制器对象,从模型层获取数据,然后提供给RecyclerView显示,起到了沟通的桥梁作用,adapter负责:
1、创建必要的ViewHolder;
2、 绑定ViewHolder至模型层数据。

public VH onCreateViewHolder(ViewGroup parent, int viewType)  创建Item视图,并返回相应的ViewHolder

public void onBindViewHolder(VH holder, int position)  绑定数据到正确的Item视图上。

public int getItemCount()  返回该Adapter所持有的Itme数量

要创建adapter, 首先要定义RecyclerView.Adapter子类,RecyclerView需要显示视图对象时, 就会去找它的adapter,如下图:

sp161110_200249

首先,通过调用adapter的getItemCount()方法,RecyclerView询问数组列表中包含多少个
对象。
接 着 , RecyclerView 调 用 adapter 的 createViewHolder(ViewGroup, int) 方 法 创 建
ViewHolder以及ViewHolder要显示的视图。
最后, RecyclerView会传入ViewHolder及其位置, 调用onBindViewHolder(ViewHolder,
int)方法。adapter会找到目标位置的数据并绑定到ViewHolder的视图上。所谓绑定,就是使用
模型数据填充视图。
整个过程执行完毕,RecyclerView就能在屏幕上显示crime列表项了。需要注意的是,相对
于onBindViewHolder(ViewHolder, int)方法,createViewHolder(ViewGroup, int)方法
的调用并不频繁。一旦创建了够用的ViewHolder,RecyclerView就会停止调用createView
Holder(...)方法。然后,通过回收利用旧的ViewHolder节约时间和内存。

开始使用

首先创建一个JavaBean负责模拟数据

public class JavaBean {    String name;    int age;     public JavaBean(String name, int age) {        this.name = name;        this.age = age;    }}

然后在根据上面的两种方式添加依赖,之后设置控制器:

/** * @author HaoTianYi hao.ty@haotianyi.win * @version v1.0 * @des   一个作用是初始化UI、一个是负责更新数据 * @time 2016-11-10 18:54 */ public class MyHolder extends RecyclerView.ViewHolder {     private TextView mTvAge;    private TextView mTvName;     public MyHolder(View itemView) {        super(itemView);//      初始化View        mTvAge = (TextView) itemView.findViewById(R.id.age);        mTvName = (TextView) itemView.findViewById(R.id.name);    }     /**     * 负责更新数据也就是刷新UI     * @param bean  数据     * @call 由MyAdapter的onBindViewHolder方法调用     */    public void bindData(JavaBean bean) {        mTvAge.setText(" "+bean.age);        mTvName.setText(bean.name);    }}

设置内容填充者:

/** * @author HaoTianYi hao.ty@haotianyi.win * @version v1.0 * @des   从模型层拿到数据并且传递给RecyclerView(中间人的角色) * @time 2016-11-10 18:54 */ public class MyAdapter extends RecyclerView.Adapter<MyHolder> {     private List<JavaBean> mBeen;     public MyAdapter(List<JavaBean> been) {        mBeen = been;    }     @Override    public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {//      可以从parent得到context,没有必要在传递一个context参数//      这里的父布局参数是null        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, null);        return new MyHolder(itemView);    }     /**     * 一旦创建填充满屏幕的item之后,在滑动item就会复用     * 也就是不再调用onCreateViewHolder而是调用onBindViewHolder     * @param holder     * @param position     */    @Override    public void onBindViewHolder(MyHolder holder, int position) {//      用控制器控制数据        holder.bindData(mBeen.get(position));    }     @Override    public int getItemCount() {        return mBeen.size();    }}

Activity的使用代码:

public class MainActivity extends AppCompatActivity {     private List<JavaBean> mBeen = new ArrayList<JavaBean>();    private final String NAME = "普朗特";     @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        init();    }     private void init() {//      模拟数据        for (int i = 0; i < 50; i++) {            JavaBean javaBean = new JavaBean(NAME + i, i);            mBeen.add(javaBean);        }         RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv);        recyclerView.setAdapter(new MyAdapter(mBeen));//      一定要设置布局,否则不会显示内容        recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));    }}

相比ListView而言,RecyclerView的每一个类都是真是职责超级单一啊!!!

最后放上item的代码:

<LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="60dp"    android:orientation="horizontal">    <TextView        android:id="@+id/name"        android:layout_width="0dp"        android:layout_height="match_parent"        android:layout_weight="1"        android:gravity="center"        android:text="nihao"        android:textSize="20sp"/>    <TextView        android:id="@+id/age"        android:layout_width="0dp"        android:layout_height="match_parent"        android:layout_weight="1"        android:gravity="center"        android:text="llll"        android:textSize="20sp"/></LinearLayout>

sp161110_202126

布局管理LayoutManager

布局管理的基类是RecyclerView.LayoutManager(抽象类)

sp161110_202537

LinearLayoutManager 线性管理器,支持横向、纵向。
GridLayoutManager 网格布局管理器
StaggeredGridLayoutManager 瀑布式布局管理器

改变上面的代码,瞬间实现水平滚动:

LinearLayoutManager manager = new LinearLayoutManager(MainActivity.this);manager.setOrientation(LinearLayoutManager.HORIZONTAL);recyclerView.setLayoutManager(manager);

实现网格管理器,每一行4列

recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.this,4));

sp161110_203053

瀑布流管理器,当第二个参数设置成Vertical时和上面一样:

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

当设置成HORIZONTAL变成水平是4行,这时是水平滑动的,item的宽度需要注意去设置,毕竟横向的宽度没有约束了,应为控件可以横向滚动了。,这里没有严重的变形是因为字体设置了大小

sp161110_203615

常用API

findFirstVisibleItemPosition()  返回当前第一个可见Item的position
findFirstCompletelyVisibleItemPosition()  返回当前第一个完全可见Item的position
findLastVisibleItemPosition()  返回当前最后一个可见Item的position
findLastCompletelyVisibleItemPosition()  返回当前最后一个完全可见Item的position

设置一个瀑布流式的布局

要把每个item的高度不固定,即可实现,首先改Adapter中的onCreateViewHolder方法

  @Override    public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {//      可以从parent得到context,没有必要在传递一个context参数//      这里的父布局参数是null        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, null);        //设置随机高度        int height = (int) (Math.random() * 100);        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(954/4,300+height);        params.setMargins(10,10,10,10);        itemView.setLayoutParams(params);         return new MyHolder(itemView);    }

Activity中的引用代码不变:

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

这时可以实现,但是每一个item在一会就会改变,而且有动画效果

RecyclerView11211111

可以看到在滑动时候会有布局的变化,重写Adapter中的getItemViewType可以使每一个item固定

@Overridepublic int getItemViewType(int position) {    return position;}

现在是没有布局变化了

分隔条RecyclerView.ItemDecoration

通过设置 recyclerView.addItemDecoration(new DividerDecoration(this)); 来改变Item之间的偏移量或者对Item进行装饰。

RecyclerView.ItemDecoration 是一个抽象类,可以通过重写以下三个方法,来实现Item之间的偏移量或者装饰效果:
public void onDraw(Canvas c, RecyclerView parent)  装饰的绘制在Item条目绘制之前调用,所以这有可能被Item的内容所
遮挡
public void onDrawOver(Canvas c, RecyclerView parent)  装饰的绘制在Item条目绘制之后调用,因此装饰将浮于Item之上
public  void  getItemOffsets(Rect  outRect,  int  itemPosition,  RecyclerView  parent)   与 padding 或 margin 类 似 ,LayoutManager在测量阶段会调用该方法,计算出每一个Item的正确尺寸并设置偏移量。

具体使用

public class DividerItemDecoration extends RecyclerView.ItemDecoration {    /**     * 设置一个属性集合,可以在自己的attrs中自行设置     */    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;    /**     * 分割符的Drawable     */    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) {        System.out.println("recyclerview - itemdecoration   onDraw()");         if (mOrientation == VERTICAL_LIST) {            drawVertical(c, parent);        } else {            drawHorizontal(c, parent);        }     }     /**     * 绘制垂直分割符(如果是垂直滑动的话)     * @param c     * @param 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);        }    }     /**     *  绘制水平分割符(如果是水平滑动的话)     * @param c     * @param parent     */    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);        }    }}

在上面的代码中更改Activity中的代码:

recyclerView.addItemDecoration(new DividerItemDecoration(MainActivity.this,DividerItemDecoration.VERTICAL_LIST));

sp161111_105657

自定义分割符

只要在attrs中添加一个android:listDivider属性即可,其余全部是自动生成的:

<!-- Base application theme. --><style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">    <!-- Customize your theme here. -->    <item name="colorPrimary">@color/colorPrimary</item>    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>    <item name="colorAccent">@color/colorAccent</item>    <item name="android:listDivider">@drawable/divider_item</item></style>

对应的自定义divider_item.xml

<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android"       android:shape="rectangle">    <size android:width="3dp" android:height="3dp"/>    <solid android:color="@color/colorPrimaryDark"/></shape>

sp161111_110014

最简单的分割符

如果只是想设置一个空余的间距,在Adapter中设置margin跟少的代码量,改变adapter中的onCreateViewHolder方法:

    @Override    public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {//      可以从parent得到context,没有必要在传递一个context参数//      这里的父布局参数是null        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, null);         LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(parent.getWidth(),150);        params.setMargins(10,10,10,10);        itemView.setLayoutParams(params);         return new MyHolder(itemView);    }

网格布局中的分隔符实例

参考:csdn

public class DividerGridItemDecoration extends RecyclerView.ItemDecoration{     private static final int[] ATTRS = new int[] { android.R.attr.listDivider };    private Drawable mDivider;     public DividerGridItemDecoration(Context context)    {        final TypedArray a = context.obtainStyledAttributes(ATTRS);        mDivider = a.getDrawable(0);        a.recycle();    }     @Override    public void onDraw(Canvas c, RecyclerView parent)    {         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 c, 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                    + mDivider.getIntrinsicWidth();            final int top = child.getBottom() + params.bottomMargin;            final int bottom = top + mDivider.getIntrinsicHeight();            mDivider.setBounds(left, top, right, bottom);            mDivider.draw(c);        }    }     public void drawVertical(Canvas c, 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 + mDivider.getIntrinsicWidth();             mDivider.setBounds(left, top, right, bottom);            mDivider.draw(c);        }    }     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 isLastRaw(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();            // StaggeredGridLayoutManager 且纵向滚动            if (orientation == StaggeredGridLayoutManager.VERTICAL)            {                childCount = childCount - childCount % spanCount;                // 如果是最后一行,则不需要绘制底部                if (pos >= childCount)                    return true;            } else            // StaggeredGridLayoutManager 且横向滚动            {                // 如果是最后一行,则不需要绘制底部                if ((pos + 1) % spanCount == 0)                {                    return true;                }            }        }        return false;    }     @Override    public void getItemOffsets(Rect outRect, int itemPosition,                               RecyclerView parent)    {        int spanCount = getSpanCount(parent);        int childCount = parent.getAdapter().getItemCount();        if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最后一行,则不需要绘制底部        {            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);        } else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最后一列,则不需要绘制右边        {            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());        } else        {            outRect.set(0, 0, mDivider.getIntrinsicWidth(),                    mDivider.getIntrinsicHeight());        }    }

动画RecyclerView.ItemAnimator

在Android中默认实现了一个 DefaultItemAnimator

ItemAnimator能够帮助Item实现独立的动画,ItemAnimator作触发于以下三种事件:

1. 某条数据被插入到数据集合中
2. 从数据集合中移除某条数据
3. 更改数据集合中的某条数据

事件监听listener

public void setOnItemClickListener(@Nullable OnItemClickListener listener)  Item点击事件监听
public void setOnItemLongClickListener(OnItemLongClickListener listener)  Item长按事件监听

首先在Adapter中设置一个回调接口,添加OnItemClickListener接口,更改onBindViewHolder方法

 private OnItemClickListener mOnItemClickListener;      interface OnItemClickListener {//      点击事件        void onItemClick(View view, int position);//      长按事件        void onItemLongClick(View view, int position);    }     public void setOnItemClickLitener(OnItemClickListener mLitener) {        mOnItemClickListener = mLitener;    }     /**     * 一旦创建填充满屏幕的item之后,在滑动item就会复用     * 也就是不再调用onCreateViewHolder而是调用onBindViewHolder     *     * @param holder     * @param position     */    @Override    public void onBindViewHolder(final MyHolder holder, final int position) {//      用控制器控制数据        System.out.println(holder.toString());        holder.bindData(mBeen.get(position));         if (mOnItemClickListener != null) {            holder.itemView.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    mOnItemClickListener.onItemClick(holder.itemView, position);                }            });             holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {                @Override                public boolean onLongClick(View v) {                    mOnItemClickListener.onItemLongClick(holder.itemView, position);                    return true;                }            });        }    }

在Activity中设置监听:

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv);mAdapter = new MyAdapter(mBeen);mAdapter.setOnItemClickLitener(new MyAdapter.OnItemClickListener() {    @Override    public void onItemClick(View view, int position) {        System.out.println("-----"+"onItemClick"+"-----");    }     @Override    public void onItemLongClick(View view, int position) {        System.out.println("-----"+"onItemLongClick"+"-----");    }});recyclerView.setAdapter(mAdapter);

触发事件,可以看到结果:
sp161111_113526

参考RecyclerView


0 0