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:
[html] view plain copy
- <android.support.v7.widget.RecyclerView
- android:id="@+id/recyclerview"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:scrollbars="vertical" />
如果想水準排列顯示,把layoutManager.setOrientation(LinearLayoutManager.VERTICAL)替換成layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL)即可。
[java] view plain copy
- // 如果布局大小一緻有利于優化
- recyclerView.setHasFixedSize(true);
- // 建立一個線性布局管理器
- LinearLayoutManager layoutManager = new LinearLayoutManager(this);
- layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
- // 設定布局管理器
- recyclerView.setLayoutManager(layoutManager);
- // 建立資料集
- List<User> listData = new ArrayList<User>();
- for (int i = 0; i < 20; ++i) {
- User uBean = new User();
- uBean.setUsername("我是Item" + i);
- listData.add(uBean);
- }
- // 建立Adapter,并指定資料集
- MyAdapter adapter = new MyAdapter(context, listData);
- // 設定Adapter
- recyclerView.setAdapter(adapter);
MyAdapter:
[java] view plain copy
- public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MViewHolder> {
- private Context context;
- private List<User> listData;
- public MyAdapter(Context context, List<User> mList) {
- super();
- this.context = context;
- this.listData = mList;
- }
- @Override
- public int getItemCount() {
- // TODO Auto-generated method stub
- return listData.size();
- }
- @Override
- public MViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) {
- View view = View.inflate(viewGroup.getContext(),
- R.layout.item_user_friend_nod, null);
- // 建立一個ViewHolder
- MViewHolder holder = new MViewHolder(view);
- return holder;
- }
- @Override
- public void onBindViewHolder(MViewHolder mViewHolder, int arg1) {
- mViewHolder.mTextView.setText(listData.get(arg1).getUsername());
- mViewHolder.image.setBackgroundResource(R.drawable.head);
- }
- public class MViewHolder extends RecyclerView.ViewHolder {
- public TextView mTextView;
- public ImageView image;
- public MViewHolder(View view) {
- super(view);
- this.mTextView = (TextView) view.findViewById(R.id.tv_friend_name);
- this.image = (ImageView) itemView.findViewById(R.id.img_friend_avatar);
- }
- }
- }
MViewHolder是一個内部類,在其構造函數中,擷取控件。 MyAdapter繼承了RecyclerView.Adapter<ViewHolder>,并重寫了getItemCount(),onCreateViewHolder和onBindViewHolder三個方法。
為RecyclerView的Item及item中的子View添加點選事件
RecyclerView并沒有像ListView那樣提供OnItemClickListener和OnLongClickListener的回調,為了給RecyclerView添加Onclick監聽,需要自己去實作其Onclick監聽方法再對外公開。
首先,定義一個接口,并在裡面聲明3個監聽回調函數,分别是Item普通點選監聽,Item長按監聽和Item内部View點選監聽。
[java] view plain copy
- /**
- * item點選回調接口
- *
- * @author wen_er
- *
- */
- public interface ItemClickListener {
- /**
- * Item 普通點選
- */
- public void onItemClick(View view, int postion);
- /**
- * Item 長按
- */
- public void onItemLongClick(View view, int postion);
- /**
- * Item 内部View點選
- */
- public void onItemSubViewClick(View view, int postion);
- }
然後再稍稍改造一下MyAdapter:
[java] view plain copy
- public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MViewHolder> {
- private Context context;
- private List<User> listData;
- private ItemClickListener mItemClickListener;
- public MyAdapter(Context context, List<User> mList) {
- super();
- this.context = context;
- this.listData = mList;
- }
- public void setItemClickListener(ItemClickListener mItemClickListener) {
- this.mItemClickListener = mItemClickListener;
- }
- @Override
- public int getItemCount() {
- // TODO Auto-generated method stub
- return listData.size();
- }
- @Override
- public MViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) {
- View view = View.inflate(viewGroup.getContext(),
- R.layout.item_user_friend_nod, null);
- // 建立一個ViewHolder
- MViewHolder holder = new MViewHolder(view);
- return holder;
- }
- @Override
- public void onBindViewHolder(final MViewHolder mViewHolder,
- final int postion) {
- mViewHolder.mTextView.setText(listData.get(postion).getUsername());
- mViewHolder.image.setBackgroundResource(R.drawable.head);
- // 為image添加監聽回調
- mViewHolder.image.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (null != mItemClickListener) {
- mItemClickListener.onItemSubViewClick(mViewHolder.image,
- postion);
- }
- }
- });
- }
- public class MViewHolder extends RecyclerView.ViewHolder {
- public TextView mTextView;
- public ImageView image;
- public MViewHolder(final View view) {
- super(view);
- this.mTextView = (TextView) view.findViewById(R.id.tv_friend_name);
- this.image = (ImageView) itemView.findViewById(R.id.img_friend_avatar);
- //為item添加普通點選回調
- view.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (null != mItemClickListener) {
- mItemClickListener.onItemClick(view, getPosition());
- }
- }
- });
- //為item添加長按回調
- view.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (null != mItemClickListener) {
- mItemClickListener.onItemLongClick(view, getPosition());
- }
- return true;
- }
- });
- }
- }
- }
對比以上的代碼,隻是在onBindViewHolder中為Item的子View添加監聽回調,在MViewHolder的構造方法中為Item添加點選和長按監聽回調。
最後,在MainActivity中具體執行個體化我們的監聽事件就OK啦!
[java] view plain copy
- //為Item具體執行個體點選3種事件
- adapter.setItemClickListener(new ItemClickListener() {
- @Override
- public void onItemSubViewClick(View view, int postion) {
- T.showShort(context, "親,你點選了Image"+postion);
- }
- @Override
- public void onItemLongClick(View view, int postion) {
- T.showShort(context, "親,你長按了Item"+postion);
- }
- @Override
- public void onItemClick(View view, int postion) {
- T.showShort(context, "親,你點選了Item"+postion);
- }
- });
為Item之間添加分隔線
如果想要給RecyclerView的Item之間添加分隔線,可以使用addItemDecoration,但如果想圖友善,就直接在Item對應的XML中定義就可以了,比如說,像這樣(下面的例子隻适合Item垂直布局)
[html] view plain copy
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/selector_item_action" >
- <TextView
- android:id="@+id/tv_friend_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_marginLeft="20dp"
- android:layout_toRightOf="@+id/img_friend_avatar"
- android:text="test"
- android:textSize="18sp" />
- <ImageView
- android:id="@+id/img_friend_avatar"
- android:layout_width="50dp"
- android:layout_height="50dp"
- android:layout_alignParentLeft="true"
- android:layout_marginBottom="8dip"
- android:layout_marginLeft="8dip"
- android:layout_marginTop="8dip"
- android:background="@drawable/ic_launcher" />
- </RelativeLayout>
- <!-- 可以添加以下代碼為Item之間設定分隔線 -->
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_alignParentBottom="true"
- android:background="@drawable/divider_horizontal_line" />
- </RelativeLayout>
addItemDecoration的參數ItemDecoration需要我們重寫它的onDraw,onDrawOver和getItemOffsets方法,因為item可能是水準排列,也可能是垂直排列,是以我們傳入一個參數oritation值,作為item排列方向的标記,參考了Git上的代碼,原來的代碼當水準布局時,分隔線的高度會填滿整個螢幕(Item并未填滿整個螢幕),是以稍稍做了改動。
[html] view plain copy
- <pre name="code" class="java">public class ItemDecorationDivider extends ItemDecoration {
- private Drawable mDivider;
- private int mOritation;
- public ItemDecorationDivider(Context context, int resId, int oritation) {
- mDivider = context.getResources().getDrawable(resId);
- this.mOritation = oritation;
- Log.i("ItemDecorationDivider", "mOritation=" + mOritation);
- }
- @Override
- public void onDrawOver(Canvas c, RecyclerView parent) {
- if (mOritation == LinearLayoutManager.VERTICAL) {
- 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);
- 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);
- }
- } else if (mOritation == LinearLayoutManager.HORIZONTAL) {
- 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();
- final int bottom = child.getBottom();
- mDivider.setBounds(left, top, right, bottom);
- mDivider.draw(c);
- }
- }
- }
- @Override
- public void getItemOffsets(Rect outRect, int position,
- RecyclerView parent) {
- if (mOritation == LinearLayoutManager.VERTICAL) {
- outRect.set(0, 0, 0, mDivider.getIntrinsicWidth());
- // outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
- } else if (mOritation == LinearLayoutManager.HORIZONTAL) {
- // outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
- outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
- }
- }
- }
最主要的方法在onDrawOver中,
mDrawable是Item之間分隔的Drawable資源,
mDrawable.setBounds(left, top, right, bottom)設定分隔線的繪制範圍,再繪制出來 ,
getItemOffsets為Item設定偏移量,告知RecyclerView需要繪制Item之間分隔線,然後把實作的ItemDivider作為參數傳給recyclerView.addItemDecoration。
[java] view plain copy
- recyclerView.addItemDecoration(new ItemDecorationDivider(context,
- R.drawable.item_divider, LinearLayoutManager.VERTICAL));
item_divider:
[html] view plain copy
- <?xml version="1.0" encoding="utf-8"?>
- <shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid android:color="#CCCCCC" />
- <size android:height="1dp" />
- </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的動畫效果。
[java] view plain copy
- </pre><pre name="code" class="java"> /**
- * TODO<添加資料,指定其位置>
- */
- public void addData(User info, int position) {
- listData.add(position, info);
- notifyItemInserted(position);
- // notifyDataSetChanged(); //不會觸發Item的動畫效果,告知資料改變,重新整理UI
- }
- /**
- * TODO<添加資料到最後面添加>
- */
- public void addData(User info) {
- // listData.add(position, info);
- // notifyItemInserted(position);
- listData.add(info);
- notifyDataSetChanged();
- }
- /**
- * TODO<删除資料,指定其位置>
- */
- public void daleteData(int position) {
- listData.remove(position);
- notifyItemRemoved(position);
- }
- /**
- * TODO<某一位置開始,有itemCount個Item的資料删除>
- */
- public void itemRangeRemoved(int positionStart, int itemCount) {
- for (int i = positionStart; i < itemCount; i++) {
- listData.remove(positionStart);
- }
- notifyItemRangeRemoved(positionStart, itemCount);
- // notifyDataSetChanged(); //不會觸發Item的動畫效果,告知資料改變,重新整理UI
- }
- /**
- * TODO<某一位置開始,有itemCount個Item的資料插入>
- */
- public void itemRangeInserted(User info, int positionStart, int itemCount) {
- for (int i = positionStart; i < itemCount; i++) {
- listData.add(i, info);
- }
- notifyItemRangeInserted(positionStart, itemCount);
- // notifyDataSetChanged();
- }
[java] view plain copy
- </pre><pre>
直接使用:
[java] view plain copy
- case R.id.btn3:
- User uBean = new User();
- uBean.setUsername("我是增加的Item");
- adapter.addData(uBean, 0);// 添加到第一個
- break;
- case R.id.btn4:
- adapter.daleteData(0); // 删除第一個
- break;
- case R.id.btn5:
- User uBean1 = new User();
- uBean1.setUsername("我是連續添加的Item");
- adapter.itemRangeInserted(uBean1, 0, 5);
- break;
- case R.id.btn6:
- adapter.itemRangeRemoved(0, 5);
- break;
為RecyclerView的Item添加動畫
在在ListView中,給item添加動畫的常用方式是,使用LayoutAnimationController為ViewGroup添加動畫,在RecyclerView中,則使用RecyclerView提供的setItemAnimator()方法
[java] view plain copy
- // 使用RecyclerView提供的預設的動畫效果
- recyclerView.setItemAnimator(new DefaultItemAnimator());
這就是我們在效果圖看到的動畫效果,如果想要其它的動畫效果,參見GitHub:https://github.com/gabrielemariotti/RecyclerViewItemAnimators
目前提供了:ScaleInOutItemAnimator,SlideInOutBottomItemAnimator,SlideInOutLeftItemAnimator,SlideInOutRightItemAnimator,SlideInOutTopItemAnimator,SlideScaleInOutRightItemAnimator幾種動畫效果,使用方法相似。
RecyclerView滾動狀态監聽
RecyclerView提供了setOnScrollListener方法,以便監聽螢幕滾動狀态。
[java] view plain copy
- recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(RecyclerView recyclerView,
- int scrollState) {
- updateState(scrollState);
- }
- @Override
- public void onScrolled(RecyclerView recyclerView, int i, int i2) {
- String s = "可見Item數量:" + layoutManager.getChildCount()+"\n"
- + "可見Item第一個Position:"
- + layoutManager.findFirstVisibleItemPosition()+"\n"
- + "可見Item最後一個Position:"
- + layoutManager.findLastVisibleItemPosition();
- tv.setText(s);
- }
- });
[java] view plain copy
- private void updateState(int scrollState) {
- String stateName = "Undefined";
- switch (scrollState) {
- case SCROLL_STATE_IDLE:
- stateName = "Idle";
- break;
- case SCROLL_STATE_DRAGGING:
- stateName = "Dragging";
- break;
- case SCROLL_STATE_SETTLING:
- stateName = "Flinging";
- break;
- }
- tv_state.setText("滑動狀态:" + stateName);
- }
當滾動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決定布局是否反向。
[java] view plain copy
- gridLayoutManager = new GridLayoutManager(this, 3,
- GridLayoutManager.VERTICAL, false);
- // 設定布局管理器
- recyclerView.setLayoutManager(gridLayoutManager);
- StaggeredGridLayoutManager
流式布局,兩個構造函數:
StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
StaggeredGridLayoutManager(int spanCount, int orientation) //spanCount為列數,orientation為布局方向
[java] view plain copy
- StaggeredGridLayoutManager = new StaggeredGridLayoutManager(2,
- StaggeredGridLayoutManager.VERTICAL);
- // 設定布局管理器
- recyclerView.setLayoutManager(StaggeredGridLayoutManager);
LinearLayoutManager,GridLayoutManager和StaggeredGridLayoutManager使用方法都類似,直接作為參數傳給setLayoutManager就可以了。
關于RecyclerView更深入的用法在後面的博文中介紹。
GITHUb下載下傳位址:https://github.com/myjoybar/Android-RecyclerView
CSDN下載下傳位址:http://download.csdn.net/detail/yalinfendou/8837997
