Android 技术之RecyclerView的基本使用(再不学会你就out了)

Android RecyclerView 在去年的Google I/O大会上就推出来了,以前经常使用的ListView 继承的是AbsListView,而RecyclerView则直接继承 ViewGroup,并实现了ScrollingView 和 NestedScrollingChild接口,RecyclerView相比ListView,是一次彻底的改变,RecyclerView 比ListView更加强大灵活。

DEMO实现功能:

 

  • RecyclerView的点击事件: Item及item中的子View添加点击事件RecyclerView Item之间添加分隔线:垂直与水平方向
    RecyclerView 单个与多个Item的添加与删除
    RecyclerView Item添加与删除动画效果RecyclerView滚动状态监听
    LayoutManager的使用

     

    DEMO效果图:

     

    RecyclerView的相关的LayoutManager ItemDecoration 和 ItemAnimator

  • LayoutManager:这个是为RecyclerView设置布局管理器的,决定RecyclerView的显示风格,它有两个直接子类:LinearLayoutManager 和 StaggeredGridLayoutManager,还有一个间接子类GridLayoutManager,GridLayoutManager继承LinearLayoutManager 。线性布局管理器 LinearLayoutManager 的布局像ListView显示多列,可以直接设置其布局方向(垂直或水平),网格布局管理器 GridLayoutManager像Gridview那样显示多行多列,而StaggeredGridLayoutManager则可以实现流式布局。
  • ItemDecoration:在Item之间设置分隔线和偏移,提供了三个方法:getItemOffsets,onDraw,onDrawOver。ItemDecoration的绘制是有一定的顺序的,onDraw的绘制在Item视图绘制之前,onDrawOver 的绘制在Item视图绘制之后,并将其绘制的显示在视图之上,getItemOffsets为Item设置偏移量。如果你只是想实现简单的Item之间的分割线的话,可以直接在item的XML文件中直接定义就可以了。
  • ItemAnimator:用来设置item的添加或者删除的动画风格,默认的动画是DefaultItemAnimator,也可以自己设置,继承RecyclerView.ItemAnimator,然后重写animateAdd,animateMove等方法就可以了。 来看看一个简单的例子: XML中添加RecyclerView:
    1. <android.support.v7.widget.RecyclerView
    2. android:id="@+id/recyclerview"
    3. android:layout_width="match_parent"
    4. android:layout_height="0dp"
    5. android:layout_weight="1"
    6. android:scrollbars="vertical" />
    如果想水平排列显示,把layoutManager.setOrientation(LinearLayoutManager.VERTICAL)替换成layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL)即可。
    01. // 如果布局大小一致有利于优化
    02. recyclerView.setHasFixedSize(true);
    03. // 创建一个线性布局管理器
    04. LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    05. layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
    06. // 设置布局管理器
    07. recyclerView.setLayoutManager(layoutManager);
    08. // 创建数据集
    09. List<User> listData = new ArrayList<User>();
    10. for (int i = 0; i < 20; ++i) {
    11. User uBean = new User();
    12. uBean.setUsername("我是Item" + i);
    13. listData.add(uBean);
    14. }
    15.  
    16. // 创建Adapter,并指定数据集
    17. MyAdapter adapter = new MyAdapter(context, listData);
    18. // 设置Adapter
    19. recyclerView.setAdapter(adapter);
    MyAdapter:
    01. public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MViewHolder> {
    02.  
    03. private Context context;
    04. private List<User> listData;
    05.  
    06. public MyAdapter(Context context, List<User> mList) {
    07. super();
    08. this.context = context;
    09. this.listData = mList;
    10. }
    11.  
    12. @Override
    13. public int getItemCount() {
    14. // TODO Auto-generated method stub
    15. return listData.size();
    16. }
    17.  
    18. @Override
    19. public MViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) {
    20.  
    21. View view = View.inflate(viewGroup.getContext(),
    22. R.layout.item_user_friend_nod, null);
    23. // 创建一个ViewHolder
    24. MViewHolder holder = new MViewHolder(view);
    25. return holder;
    26. }
    27.  
    28. @Override
    29. public void onBindViewHolder(MViewHolder mViewHolder, int arg1) {
    30.  
    31. mViewHolder.mTextView.setText(listData.get(arg1).getUsername());
    32. mViewHolder.image.setBackgroundResource(R.drawable.head);
    33.  
    34. }
    35.  
    36. public class MViewHolder extends RecyclerView.ViewHolder {
    37. public TextView mTextView;
    38. public ImageView image;
    39.  
    40. public MViewHolder(View view) {
    41. super(view);
    42. this.mTextView = (TextView) view.findViewById(R.id.tv_friend_name);
    43. this.image = (ImageView) itemView.findViewById(R.id.img_friend_avatar);
    44.  
    45. }
    46. }
    47.  
    48. }
    MViewHolder是一个内部类,在其构造函数中,获取控件。 MyAdapter继承了RecyclerView.Adapter<ViewHolder>,并重写了getItemCount(),onCreateViewHolder和onBindViewHolder三个方法。

    为RecyclerView的Item及item中的子View添加点击事件


    RecyclerView并没有像ListView那样提供OnItemClickListener和OnLongClickListener的回调,为了给RecyclerView添加Onclick监听,需要自己去实现其Onclick监听方法再对外公开。
    首先,定义一个接口,并在里面声明3个监听回调函数,分别是Item普通点击监听,Item长按监听和Item内部View点击监听。
    01. /**
    02. * item点击回调接口
    03. *
    04. * @author wen_er
    05. *
    06. */
    07. public interface ItemClickListener {
    08.  
    09. /**
    10. * Item 普通点击
    11. */
    12.  
    13. public void onItemClick(View view, int postion);
    14.  
    15. /**
    16. * Item 长按
    17. */
    18.  
    19. public void onItemLongClick(View view, int postion);
    20.  
    21. /**
    22. * Item 内部View点击
    23. */
    24.  
    25. public void onItemSubViewClick(View view, int postion);
    26. }
    然后再稍稍改造一下MyAdapter:
    01. public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MViewHolder> {
    02.  
    03. private Context context;
    04. private List<User> listData;
    05. private ItemClickListener mItemClickListener;
    06.  
    07. public MyAdapter(Context context, List<User> mList) {
    08. super();
    09. this.context = context;
    10. this.listData = mList;
    11. }
    12.  
    13. public void setItemClickListener(ItemClickListener mItemClickListener) {
    14.  
    15. this.mItemClickListener = mItemClickListener;
    16. }
    17.  
    18. @Override
    19. public int getItemCount() {
    20. // TODO Auto-generated method stub
    21. return listData.size();
    22. }
    23.  
    24. @Override
    25. public MViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) {
    26.  
    27. View view = View.inflate(viewGroup.getContext(),
    28. R.layout.item_user_friend_nod, null);
    29. // 创建一个ViewHolder
    30. MViewHolder holder = new MViewHolder(view);
    31. return holder;
    32. }
    33.  
    34. @Override
    35. public void onBindViewHolder(final MViewHolder mViewHolder,
    36. final int postion) {
    37.  
    38. mViewHolder.mTextView.setText(listData.get(postion).getUsername());
    39. mViewHolder.image.setBackgroundResource(R.drawable.head);
    40. // 为image添加监听回调
    41. mViewHolder.image.setOnClickListener(new OnClickListener() {
    42.  
    43. @Override
    44. public void onClick(View v) {
    45. if (null != mItemClickListener) {
    46. mItemClickListener.onItemSubViewClick(mViewHolder.image,
    47. postion);
    48. }
    49.  
    50. }
    51.  
    52. });
    53.  
    54. }
    55.  
    56. public class MViewHolder extends RecyclerView.ViewHolder {
    57. public TextView mTextView;
    58. public ImageView image;
    59.  
    60. public MViewHolder(final View view) {
    61. super(view);
    62. this.mTextView = (TextView) view.findViewById(R.id.tv_friend_name);
    63. this.image = (ImageView) itemView.findViewById(R.id.img_friend_avatar);
    64. //为item添加普通点击回调    
    65. view.setOnClickListener(new OnClickListener() {
    66.  
    67. @Override
    68. public void onClick(View v) {
    69.  
    70. if (null != mItemClickListener) {
    71. mItemClickListener.onItemClick(view, getPosition());
    72. }
    73.  
    74. }
    75. });
    76.  
    77. //为item添加长按回调  
    78. view.setOnLongClickListener(new OnLongClickListener() {
    79.  
    80. @Override
    81. public boolean onLongClick(View v) {
    82. if (null != mItemClickListener) {
    83. mItemClickListener.onItemLongClick(view, getPosition());
    84. }
    85. return true;
    86. }
    87. });
    88.  
    89. }
    90. }
    91.  
    92. }
    对比以上的代码,只是在onBindViewHolder中为Item的子View添加监听回调,在MViewHolder的构造方法中为Item添加点击和长按监听回调。 最后,在MainActivity中具体实例化我们的监听事件就OK啦!
    01. //为Item具体实例点击3种事件
    02. adapter.setItemClickListener(new ItemClickListener() {
    03.  
    04. @Override
    05. public void onItemSubViewClick(View view, int postion) {
    06. T.showShort(context, "亲,你点击了Image"+postion);
    07.  
    08. }
    09.  
    10. @Override
    11. public void onItemLongClick(View view, int postion) {
    12. T.showShort(context, "亲,你长按了Item"+postion);
    13.  
    14. }
    15.  
    16. @Override
    17. public void onItemClick(View view, int postion) {
    18. T.showShort(context, "亲,你点击了Item"+postion);
    19.  
    20. }
    21. });

    为Item之间添加分隔线

    如果想要给RecyclerView的Item之间添加分隔线,可以使用addItemDecoration,但如果想图方便,就直接在Item对应的XML中定义就可以了,比如说,像这样(下面的例子只适合Item垂直布局)
    01. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    02. android:layout_width="match_parent"
    03. android:layout_height="match_parent" >
    04.  
    05. <RelativeLayout
    06. android:layout_width="match_parent"
    07. android:layout_height="wrap_content"
    08. android:background="@drawable/selector_item_action" >
    09.  
    10. <TextView
    11. android:id="@+id/tv_friend_name"
    12. android:layout_width="wrap_content"
    13. android:layout_height="wrap_content"
    14. android:layout_centerVertical="true"
    15. android:layout_marginLeft="20dp"
    16. android:layout_toRightOf="@+id/img_friend_avatar"
    17. android:text="test"
    18. android:textSize="18sp" />
    19.  
    20. <ImageView
    21. android:id="@+id/img_friend_avatar"
    22. android:layout_width="50dp"
    23. android:layout_height="50dp"
    24. android:layout_alignParentLeft="true"
    25. android:layout_marginBottom="8dip"
    26. android:layout_marginLeft="8dip"
    27. android:layout_marginTop="8dip"
    28. android:background="@drawable/ic_launcher" />
    29. </RelativeLayout>
    30.  
    31. <!-- 可以添加以下代码为Item之间设置分隔线  -->
    32. <View
    33. android:layout_width="match_parent"
    34. android:layout_height="1dp"
    35. android:layout_alignParentBottom="true"
    36. android:background="@drawable/divider_horizontal_line" /> 
    37.  
    38.  
    39. </RelativeLayout>
    addItemDecoration的参数ItemDecoration需要我们重写它的onDraw,onDrawOver和getItemOffsets方法,因为item可能是水平排列,也可能是垂直排列,所以我们传入一个参数oritation值,作为item排列方向的标记,参考了Git上的代码,原来的代码当水平布局时,分隔线的高度会填满整个屏幕(Item并未填满整个屏幕),所以稍稍做了改动。
    01. <pre name="code" class="java">public class ItemDecorationDivider extends ItemDecoration {
    02.  
    03. private Drawable mDivider;
    04. private int mOritation;
    05.  
    06. public ItemDecorationDivider(Context context, int resId, int oritation) {
    07.  
    08. mDivider = context.getResources().getDrawable(resId);
    09. this.mOritation = oritation;
    10. Log.i("ItemDecorationDivider""mOritation=" + mOritation);
    11.  
    12. }
    13.  
    14. @Override
    15. public void onDrawOver(Canvas c, RecyclerView parent) {
    16.  
    17. if (mOritation == LinearLayoutManager.VERTICAL) {
    18. final int left = parent.getPaddingLeft();
    19. final int right = parent.getWidth() - parent.getPaddingRight();
    20.  
    21. final int childCount = parent.getChildCount();
    22. for (int i = 0; i < childCount; i++) {
    23. final View child = parent.getChildAt(i);
    24. final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
    25. .getLayoutParams();
    26.  
    27. final int top = child.getBottom() + params.bottomMargin;
    28. final int bottom = top + mDivider.getIntrinsicHeight();
    29. mDivider.setBounds(left, top, right, bottom);
    30. mDivider.draw(c);
    31. }
    32. else if (mOritation == LinearLayoutManager.HORIZONTAL) {
    33.  
    34. final int top = parent.getPaddingTop();
    35. // final int bottom = parent.getHeight() -
    36. // parent.getPaddingBottom();
    37.  
    38. final int childCount = parent.getChildCount();
    39. for (int i = 0; i < childCount; i++) {
    40. final View child = parent.getChildAt(i);
    41. final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
    42. .getLayoutParams();
    43. final int left = child.getRight() + params.rightMargin;
    44. final int right = left + mDivider.getIntrinsicHeight();
    45.  
    46. final int bottom = child.getBottom();
    47. mDivider.setBounds(left, top, right, bottom);
    48. mDivider.draw(c);
    49. }
    50. }
    51.  
    52. }
    53.  
    54. @Override
    55. public void getItemOffsets(Rect outRect, int position,
    56. RecyclerView parent) {
    57. if (mOritation == LinearLayoutManager.VERTICAL) {
    58. outRect.set(000, mDivider.getIntrinsicWidth());
    59. // outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
    60. else if (mOritation == LinearLayoutManager.HORIZONTAL) {
    61. // outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
    62. outRect.set(000, mDivider.getIntrinsicHeight());
    63. }
    64.  
    65. }
    66. }
    最主要的方法在onDrawOver中, mDrawable是Item之间分隔的Drawable资源, mDrawable.setBounds(left, top, right, bottom)设置分隔线的绘制范围,再绘制出来 , getItemOffsets为Item设置偏移量,告知RecyclerView需要绘制Item之间分隔线,然后把实现的ItemDivider作为参数传给recyclerView.addItemDecoration。
    1. recyclerView.addItemDecoration(new ItemDecorationDivider(context,
    2. R.drawable.item_divider, LinearLayoutManager.VERTICAL));
    item_divider:
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <shape xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:shape="rectangle" >
    4. <solid android:color="#CCCCCC" />
    5. <size android:height="1dp" />
    6. </shape>

    RecyclerView Item的添加与删除


    Adapter提供的的几个常用方法:
    • notifyItemChanged(int position) //通知位置position的Item的数据改变
      • notifyItemInserted(int)//通知位置position的Item的数据插入
      • notifyItemRemoved(int)//通知位置position的Item的数据移除
      • notifyItemRangeChanged(int positionStart, int itemCount) //通知从位置positionStart开始,有itemCount个Item的数据发生改变
      • notifyItemRangeInserted(int positionStart, int itemCount) //通知从位置positionStart开始,有itemCount个Item的数据插入
      • notifyItemRangeRemoved(int positionStart, int itemCount)//通知从位置positionStart开始,有itemCount个Item的数据移除 主要是使用Adapter提供的notifyItemInserted(position)和notifyItemRemoved(position)方法,告知数据改变,如果删除或者添加Item都是从Position为0的位置开始,加上notifyDataSetChanged()刷新一下UI。 注意:使用notifyDataSetChanged()不会触发Item的动画效果。
        01. </pre><pre name="code" class="java">    /**
        02. * TODO<添加数据,指定其位置>
        03. */
        04.  
        05. public void addData(User info, int position) {
        06. listData.add(position, info);
        07. notifyItemInserted(position);
        08. //  notifyDataSetChanged(); //不会触发Item的动画效果,告知数据改变,刷新UI
        09.  
        10. }
        11.  
        12. /**
        13. * TODO<添加数据到最后面添加>
        14. */
        15.  
        16. public void addData(User info) {
        17. // listData.add(position, info);
        18. // notifyItemInserted(position);
        19. listData.add(info);
        20. notifyDataSetChanged();
        21. }
        22.  
        23. /**
        24. * TODO<删除数据,指定其位置>
        25. */
        26. public void daleteData(int position) {
        27. listData.remove(position);
        28. notifyItemRemoved(position);
        29.  
        30. }
        31.  
        32. /**
        33. * TODO<某一位置开始,有itemCount个Item的数据删除>
        34. */
        35. public void itemRangeRemoved(int positionStart, int itemCount) {
        36. for (int i = positionStart; i < itemCount; i++) {
        37. listData.remove(positionStart);
        38. }
        39. notifyItemRangeRemoved(positionStart, itemCount);
        40. //  notifyDataSetChanged(); //不会触发Item的动画效果,告知数据改变,刷新UI
        41. }
        42.  
        43. /**
        44. * TODO<某一位置开始,有itemCount个Item的数据插入>
        45. */
        46. public void itemRangeInserted(User info, int positionStart, int itemCount) {
        47. for (int i = positionStart; i < itemCount; i++) {
        48. listData.add(i, info);
        49. }
        50. notifyItemRangeInserted(positionStart, itemCount);
        51. // notifyDataSetChanged();
        52. }
        1. </pre><pre>
        直接使用:
        01. case R.id.btn3:
        02. User uBean = new User();
        03. uBean.setUsername("我是增加的Item");
        04. adapter.addData(uBean, 0);// 添加到第一个
        05. break;
        06. case R.id.btn4:
        07. adapter.daleteData(0); // 删除第一个
        08. break;
        09. case R.id.btn5:
        10. User uBean1 = new User();
        11. uBean1.setUsername("我是连续添加的Item");
        12. adapter.itemRangeInserted(uBean1, 05);
        13. break;
        14. case R.id.btn6:
        15. adapter.itemRangeRemoved(05);
        16. break;

        为RecyclerView的Item添加动画


        在在ListView中,给item添加动画的常用方式是,使用LayoutAnimationController为ViewGroup添加动画,在RecyclerView中,则使用RecyclerView提供的setItemAnimator()方法
        1. // 使用RecyclerView提供的默认的动画效果
        2. recyclerView.setItemAnimator(new DefaultItemAnimator());

        这就是我们在效果图看到的动画效果,如果想要其它的动画效果,参见GitHub:https://github.com/gabrielemariotti/RecyclerViewItemAnimators 目前提供了:ScaleInOutItemAnimator,SlideInOutBottomItemAnimator,SlideInOutLeftItemAnimator,SlideInOutRightItemAnimator,SlideInOutTopItemAnimator,SlideScaleInOutRightItemAnimator几种动画效果,使用方法相似。

         

        RecyclerView滚动状态监听


        RecyclerView提供了setOnScrollListener方法,以便监听屏幕滚动状态。
        01. recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
        02. @Override
        03. public void onScrollStateChanged(RecyclerView recyclerView,
        04. int scrollState) {
        05. updateState(scrollState);
        06. }
        07.  
        08. @Override
        09. public void onScrolled(RecyclerView recyclerView, int i, int i2) {
        10.  
        11. String s = "可见Item数量:" + layoutManager.getChildCount()+"
        12. "
        13. "可见Item第一个Position:"
        14. + layoutManager.findFirstVisibleItemPosition()+"
        15. "
        16. "可见Item最后一个Position:"
        17. + layoutManager.findLastVisibleItemPosition();
        18. tv.setText(s);
        19. }
        20. });

        01. private void updateState(int scrollState) {
        02. String stateName = "Undefined";
        03. switch (scrollState) {
        04. case SCROLL_STATE_IDLE:
        05. stateName = "Idle";
        06. break;
        07.  
        08. case SCROLL_STATE_DRAGGING:
        09. stateName = "Dragging";
        10. break;
        11.  
        12. case SCROLL_STATE_SETTLING:
        13. stateName = "Flinging";
        14. break;
        15. }
        16.  
        17. tv_state.setText("滑动状态:" + stateName);
        18. }
        当滚动RecyclerView的时候,效果如DEMO效果图所示。
        最后看看LayoutManager,前面说过,LayoutManager是为RecyclerView设置布局管理器的,决定RecyclerView的显示风格。

        LinearLayoutManager

        前面的代码中,使用 LinearLayoutManager layoutManager = new LinearLayoutManager(this)和 layoutManager.setOrientation(LinearLayoutManager.VERTICAL)创建一个线性布局管理器和设置其布局方向 还可以使用直接以下使用构造方法传入布局方向: LinearLayoutManager(Context context, int orientation, boolean reverseLayout) 第二个参数为布局方向:LinearLayoutManager.HORIZONTAL或者LinearLayoutManager.VERTICAL 第三个参数:true或者false,决定布局是否反向

        GridLayoutManager

        这是类似GridView的网格布局,三个构造函数:
        GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) //可以直接在XMl中设置RecyclerView 属性"layoutManager". GridLayoutManager(Context context, int spanCount) //spanCount为列数,默认方向vertical
        GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout)//spanCount为列数,orientation为布局方向,reverseLayout决定布局是否反向。
        1. gridLayoutManager = new GridLayoutManager(this3,
        2. GridLayoutManager.VERTICAL, false);
        3. // 设置布局管理器
        4. recyclerView.setLayoutManager(gridLayoutManager);

        StaggeredGridLayoutManager

        流式布局,两个构造函数:
        StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
        StaggeredGridLayoutManager(int spanCount, int orientation) //spanCount为列数,orientation为布局方向
        1. StaggeredGridLayoutManager = new StaggeredGridLayoutManager(2,
        2. StaggeredGridLayoutManager.VERTICAL);
        3. // 设置布局管理器
        4. recyclerView.setLayoutManager(StaggeredGridLayoutManager);

        LinearLayoutManager,GridLayoutManager和StaggeredGridLayoutManager使用方法都类似,直接作为参数传给setLayoutManager就可以了。
        关于RecyclerView更深入的用法在后面的博文中介绍。
    • .新的思路:notifyItemChanged
      RecyclerView不像ListView,只有一个更新notifyDataSetChanged,它不仅保留了ListView的更新特点,还针对“增加,删除,更新”操作专门进行更新,可以只更新一个item,也可以更新一部分item,所以,用起来效率更高。因此,RecyclerView的局部刷新,就可以通过修改数据源的方式,调用notifyItemChanged(position)即可。


       优化
      虽然只更新单个item,不会造成闪烁,但是,如果单个item都很复杂,比如,item中需要从网络上加载图片等等。为了避免多次刷新照成的闪烁,我们可以在加载的时候,为ImageView设置一个Tag,比如imageView.setTag(image_url),下一次再加载之前,首先获取Tag,比如imageUrl = imageView.getTag(),如果此时的地址和之前的地址一样,我们就不需要加载了,如果不一样,再加载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值