RecylerView是support-v7包中的新組件,是一個強大的滑動組件,與經典的ListView相比,一樣擁有item回收複用的功能,這一點從它的名字recylerview即回收view也能夠看出。官方對於它的介紹則是:RecyclerView 是 ListView 的升級版本,更加先進和靈活。RecyclerView經過設置LayoutManager,ItemDecoration,ItemAnimator實現你想要的效果。java
使用LayoutManager來肯定每個item的排列方式。android
使用ItemDecoration本身繪製分割線,更靈活canvas
使用ItemAnimator爲增長或刪除一行設置動畫效果。api
新建完項目,須要在app/build.gradle增長RecylerView依賴,否則找不到RecyclerView類app
compile 'com.android.support:recyclerview-v7:23.1.0'RecylerView簡單的Demo
咱們來看activity代碼,跟ListView寫法差很少,只是這邊多設置了佈局管理器。ide
public class LinearLayoutActivity extends AppCompatActivity { private RecyclerView recyclerView; private RecyclerViewAdapter adapter; private List<String> datas; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.recycler_main); initData(); recyclerView= (RecyclerView) findViewById(R.id.recyclerview); recyclerView.setLayoutManager(new LinearLayoutManager(this));//設置佈局管理器 recyclerView.addItemDecoration(new DividerItemDecoration(this)); recyclerView.setAdapter(adapter=new RecyclerViewAdapter(this,datas)); } private void initData(){ datas=new ArrayList<>(); for(int i=0;i<100;i++){ datas.add("item:"+i); } } }
activity對應的佈局文件:recycler_main.xml佈局
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="match_parent" /></RelativeLayout>
adapter相對ListView來講變化比較大的。把ViewHolder邏輯封裝起來了,代碼相對簡單一些。gradle
須要繼承RecyclerView.Adapter,重寫三個方法動畫
MyViewHolder須要繼承RecyclerView.ViewHolderui
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder>{private List<String> datas;private LayoutInflater inflater; public RecyclerViewAdapter(Context context,List<String> datas){ inflater=LayoutInflater.from(context); this.datas=datas; }//建立每一行的View 用RecyclerView.ViewHolder包裝@Overridepublic RecyclerViewAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView=inflater.inflate(R.layout.recycler_item,null); return new MyViewHolder(itemView); }//給每一行View填充數據@Overridepublic void onBindViewHolder(RecyclerViewAdapter.MyViewHolder holder, int position) { holder.textview.setText(datas.get(position)); }//數據源的數量@Overridepublic int getItemCount() { return datas.size(); }class MyViewHolder extends RecyclerView.ViewHolder{ private TextView textview; public MyViewHolder(View itemView) { super(itemView); textview= (TextView) itemView.findViewById(R.id.textview); } } }
咱們來看看效果圖:
RecyclerView增長分隔線RecyclerView是沒有android:divider跟android:dividerHeight屬性的,若是咱們須要分割線,就只能本身動手去實現了。
須要繼承ItemDecoration類,實現onDraw跟getItemOffsets方法。
調用RecyclerView的addItemDecoration方法。
咱們先寫一個DividerItemDecoration類,繼承RecyclerView.ItemDecoration,在getItemOffsets留出item之間的間隔,而後就會調用onDraw方法繪製(onDraw的繪製優先於每一行的繪製)
public class DividerItemDecoration extends RecyclerView.ItemDecoration{ /* * RecyclerView的佈局方向,默認先賦值 爲縱向佈局 * RecyclerView 佈局可橫向,也可縱向 * 橫向和縱向對應的分割線畫法不同 * */ private int mOrientation = LinearLayoutManager.VERTICAL; private int mItemSize = 1;//item之間分割線的size,默認爲1 private Paint mPaint;//繪製item分割線的畫筆,和設置其屬性 public DividerItemDecoration(Context context) { this(context,LinearLayoutManager.VERTICAL,R.color.colorAccent); } public DividerItemDecoration(Context context, int orientation) { this(context,orientation, R.color.colorAccent); } public DividerItemDecoration(Context context, int orientation, int dividerColor){ this(context,orientation,dividerColor,1); } /** * @param context * @param orientation 繪製方向 * @param dividerColor 分割線顏色 顏色資源id * @param mItemSize 分割線寬度 傳入dp值就行 */ public DividerItemDecoration(Context context, int orientation, int dividerColor, int mItemSize){ this.mOrientation = orientation; if(orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL){ throw new IllegalArgumentException("請傳入正確的參數") ; } //把dp值換算成px this.mItemSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,mItemSize,context.getResources().getDisplayMetrics()); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(context.getResources().getColor(dividerColor)); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { if(mOrientation == LinearLayoutManager.VERTICAL){ drawVertical(c,parent) ; }else { drawHorizontal(c,parent) ; } } /** * 繪製縱向 item 分割線 * @param canvas * @param parent */ private void drawVertical(Canvas canvas,RecyclerView parent){ final int left = parent.getPaddingLeft() ; final int right = parent.getMeasuredWidth() - parent.getPaddingRight(); final int childSize = parent.getChildCount() ; for(int i = 0 ; i < childSize ; i ++){ final View child = parent.getChildAt( i ) ; RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); final int top = child.getBottom() + layoutParams.bottomMargin ; final int bottom = top + mItemSize ; canvas.drawRect(left,top,right,bottom,mPaint); } } /** * 繪製橫向 item 分割線 * @param canvas * @param parent */ private void drawHorizontal(Canvas canvas,RecyclerView parent){ final int top = parent.getPaddingTop() ; final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom() ; final int childSize = parent.getChildCount() ; for(int i = 0 ; i < childSize ; i ++){ final View child = parent.getChildAt( i ) ; RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); final int left = child.getRight() + layoutParams.rightMargin ; final int right = left + mItemSize ; canvas.drawRect(left,top,right,bottom,mPaint); } } /** * 設置item分割線的size * @param outRect * @param view * @param parent * @param state */ @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if(mOrientation == LinearLayoutManager.VERTICAL){ outRect.set(0,0,0,mItemSize);//垂直排列 底部偏移 }else { outRect.set(0,0,mItemSize,0);//水平排列 右邊偏移 } } }
不要忘記調用addItemDecoration方法哦
recyclerView.addItemDecoration(new DividerItemDecoration(this));//添加分割線
從新運行,效果圖:
你們讀到這裏確定會有一個疑問,這貨比ListView麻煩多了啊,可是google官方爲何要說是ListView的升級版呢?接下來開始放大招。。。
GridLayoutManager在RecyclerView中實現不一樣的列表,只須要切換不一樣的LayoutManager便可。RecyclerView.LayoutManager跟RecyclerView.ItemDecoration同樣,都是RecyclerView靜態抽象內部類,可是LayoutManager有三個官方寫好的實現類。
LinearLayoutManager 線性佈局管理器 跟ListView功能類似
GridLayoutManager 網格佈局管理器 跟GridView功能類似
StaggeredGridLayoutManager 瀑布流佈局管理器
剛剛咱們用的是LinearLayoutManager,如今咱們切換到GridLayoutManager,看到下面這句代碼,有沒有感受分分鐘切換不一樣列表顯示。
recyclerView.setLayoutManager(new GridLayoutManager(this,2));
若是要顯示多列或者要縱向顯示就new不一樣的構造方法,如下代碼縱向顯示4列。當前若是你還須要反方向顯示,把false改爲true就能夠。
recyclerView.setLayoutManager(new GridLayoutManager(this,4,GridLayoutManager.HORIZONTAL,false));
由於用的是網格佈局,因此呢繪製分割線的代碼須要從新修改一下。網格佈局一行能夠有多列,而且最後一列跟最後一行不須要繪製,因此咱們得從新建立一個類。
DividerGridItemDecoration.java
public class DividerGridItemDecoration extends RecyclerView.ItemDecoration { /* * RecyclerView的佈局方向,默認先賦值 爲縱向佈局 * RecyclerView 佈局可橫向,也可縱向 * 橫向和縱向對應的分割線畫法不同 * */ private int mOrientation = LinearLayoutManager.VERTICAL; private int mItemSize = 1;//item之間分割線的size,默認爲1 private Paint mPaint;//繪製item分割線的畫筆,和設置其屬性 public DividerGridItemDecoration(Context context) { this(context,LinearLayoutManager.VERTICAL,R.color.colorAccent); } public DividerGridItemDecoration(Context context, int orientation) { this(context,orientation, R.color.colorAccent); } public DividerGridItemDecoration(Context context, int orientation, int dividerColor){ this(context,orientation,dividerColor,1); } /** * @param context * @param orientation 繪製方向 * @param dividerColor 分割線顏色 顏色資源id * @param mItemSize 分割線寬度 傳入dp值就行 */ public DividerGridItemDecoration(Context context, int orientation, int dividerColor, int mItemSize){ this.mOrientation = orientation; if(orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL){ throw new IllegalArgumentException("請傳入正確的參數") ; } //把dp值換算成px this.mItemSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,mItemSize,context.getResources().getDisplayMetrics()); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(context.getResources().getColor(dividerColor)); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 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 canvas, 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 + mItemSize; final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mItemSize; canvas.drawRect(left,top,right,bottom,mPaint); } } public void drawVertical(Canvas canvas, 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 + mItemSize; canvas.drawRect(left,top,right,bottom,mPaint); } } @Override public void getItemOffsets(Rect outRect, int itemPosition,RecyclerView parent) { int spanCount = getSpanCount(parent); int childCount = parent.getAdapter().getItemCount(); if (isLastRow(parent, itemPosition, spanCount, childCount)){//若是是最後一行,不須要繪製底部 outRect.set(0, 0, mItemSize, 0); } else if (isLastColum(parent, itemPosition, spanCount, childCount)){// 若是是最後一列,不須要繪製右邊 outRect.set(0, 0, 0, mItemSize); } else { outRect.set(0, 0, mItemSize,mItemSize); } } 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 isLastRow(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(); if (orientation == StaggeredGridLayoutManager.VERTICAL){//縱向 childCount = childCount - childCount % spanCount; if (pos >= childCount)//最後一行 return true; } else{ //橫向 if ((pos + 1) % spanCount == 0) {//是最後一行 return true; } } } return false; } }
寫了這兩個畫分割線的類,主流的佈局:線性列表跟網格列表都能展現了。。。趕忙運行代碼看看結果:
StaggeredGridLayoutManageractviity中修改下佈局管理器,你們應該感受很熟悉了吧~~~
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL));
瀑布流列表通常列的高度是不一致的,爲了模擬不一樣的寬高,數據源我把String類型改爲了對象.而後初始化的時候隨機了一個高度.
public class ItemData { private String content;//item內容 private int height;//item高度 public ItemData() { } public ItemData(String content, int height) { this.content = content; this.height = height; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } }
瀑布流列表沒有添加分割線,給item佈局設置了android:padding屬性。recycler_staggered_item.xml
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:padding="5dp" android:layout_width="wrap_content" android:layout_height="match_parent"> <TextView android:id="@+id/textview" android:background="@color/colorAccent" android:layout_width="100dp" android:layout_height="wrap_content" android:gravity="center" android:text="122" android:textSize="20sp"/></FrameLayout>
最後咱們在適配器的onBindViewHolder方法中給itemd中的TextView設置一個高度
@Overridepublic void onBindViewHolder(StaggeredGridAdapter.MyViewHolder holder, int position) { ItemData itemData=datas.get(position); holder.textview.setText(itemData.getContent()); //手動更改高度,不一樣位置的高度有所不一樣 holder.textview.setHeight(itemData.getHeight()); }
是否是感受so easy,趕忙運行看看效果:
添加header跟footerRecyclerView添加頭部跟底部是沒有對應的api的,可是咱們不少的需求都會用到,因而只能本身想辦法實現了。咱們能夠經過適配器的getItemViewType方法來實現這個功能。
修改後的適配器代碼:RecyclerHeadFootViewAdapter.java
public class RecyclerHeadFootViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{ private List<String> datas; private LayoutInflater inflater; public static final int TYPE_HEADER=1;//header類型 public static final int TYPE_FOOTER=2;//footer類型 private View header=null;//頭View private View footer=null;//腳View public RecyclerHeadFootViewAdapter(Context context, List<String> datas){ inflater=LayoutInflater.from(context); this.datas=datas; } //建立每一行的View 用RecyclerView.ViewHolder包裝 @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(viewType==TYPE_HEADER){ return new RecyclerView.ViewHolder(header){}; }else if(viewType==TYPE_FOOTER){ return new RecyclerView.ViewHolder(footer){}; } View itemView=inflater.inflate(R.layout.recycler_item,null); return new MyViewHolder(itemView); } //給每一行View填充數據 @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position){ if(getItemViewType(position)==TYPE_HEADER||getItemViewType(position)==TYPE_FOOTER){ return; } MyViewHolder myholder= (MyViewHolder) holder; myholder.textview.setText(datas.get(getRealPosition(position))); } //若是有頭部 position的位置是從1開始的 因此須要-1 public int getRealPosition(int position){ return header==null?position:position-1; } //數據源的數量 @Override public int getItemCount() { if(header == null && footer == null){//沒有head跟foot return datas.size(); }else if(header == null && footer != null){//head爲空&&foot不爲空 return datas.size() + 1; }else if (header != null && footer == null){//head不爲空&&foot爲空 return datas.size() + 1; }else { return datas.size() + 2;//head不爲空&&foot不爲空 } } @Override public int getItemViewType(int position){ //若是頭佈局不爲空&&位置是第一個那就是head類型 if(header!=null&&position==0){ return TYPE_HEADER; }else if(footer!=null&&position==getItemCount()-1){//若是footer不爲空&&最後一個 return TYPE_FOOTER; } return super.getItemViewType(position); } public void setHeader(View header) { this.header = header; notifyItemInserted(0);//在位置0插入一條數據,而後刷新 } public void setFooter(View footer) { this.footer = footer; notifyItemInserted(datas.size()-1);//在尾部插入一條數據,而後刷新 } class MyViewHolder extends RecyclerView.ViewHolder{ private TextView textview; public MyViewHolder(View itemView) { super(itemView); textview= (TextView) itemView.findViewById(R.id.textview); } } }
getItemCount
有header跟footer的時候須要在源數據長度基礎上進行增長。
getItemViewType
經過getItemViewType判斷不一樣的類型
onCreateViewHolder
經過不一樣的類型建立item的View
onBindViewHolder
若是是header跟footer類型是不須要綁定數據的,header跟footer的View通常在actvity中建立,不須要這邊作處理,因此這兩種類型咱們就不往下執行,若是有頭佈局,position==0的位置被header佔用了,可是咱們的數據源也就是集合的下標是從0開始的,因此這裏須要-1。
setHeader
設置頭佈局,在第一行插入一條數據,而後刷新。注意這個方法調用後會有插入的動畫,這個動畫可使用默認的,也能夠本身定義
setFooter
設置尾部佈局,在尾部插入一條數據,而後刷新。
添加header跟footer的方法終於封裝好了,在activity中只須要兩行代碼就能添加header,跟ListView調用addHeader方法同樣簡單,又能夠happy的玩耍了。這裏須要注意的是咱們初始化View的時候,inflate方法須要三個參數。
resource 資源id
root 父View
attachToRoot true:返回父View false:返回資源id生成的View
//添加headerView header=LayoutInflater.from(this).inflate(R.layout.recycler_header,recyclerView,false); adapter.setHeader(header);//添加footerView footer=LayoutInflater.from(this).inflate(R.layout.recycler_footer,recyclerView,false); adapter.setFooter(footer);
recycler_header跟recycler_footer佈局文件我就不貼出來了,就一個TextView,咱們直接看效果圖:
當咱們調用RecyclerView的setOnItemClickListener方法的時候,發現竟然沒有,用了RecyclerView你要習慣什麼東西都本身封裝。。。
首先咱們從adapter開刀,內部寫一個接口,一個實例變量,提供一個公共方法,設置監聽。
private RecyclerViewItemClick recyclerViewItemClick;public void setRecyclerViewItemClick(RecyclerViewItemClick recyclerViewItemClick) { this.recyclerViewItemClick = recyclerViewItemClick; }public interface RecyclerViewItemClick{ /** * item點擊 * @param realPosition 數據源position * @param position view position */ void onItemClick(int realPosition,int position); }
在onBindViewHolder方法中給item監聽點擊事件
if(recyclerViewItemClick!=null) { myholder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { recyclerViewItemClick.onItemClick(getRealPosition(position),position); } }); }
在activity的onCreate方法中進行監聽,順便設置item增長刪除動畫。我用的是sdk自帶的默認動畫。
adapter.setRecyclerViewItemClick(recyclerViewItemClick);recyclerView.setItemAnimator(new DefaultItemAnimator());
private RecyclerHeadFootViewAdapter.RecyclerViewItemClick recyclerViewItemClick=new RecyclerHeadFootViewAdapter.RecyclerViewItemClick() { @Override public void onItemClick(int realPosition, int position) { Log.i("ansen","刪除數據:"+realPosition+" view位置:"+position); Log.i("ansen","當前位置:"+position+" 更新item數量:"+(adapter.getItemCount()-position-1)); datas.remove(realPosition);//刪除數據源 adapter.notifyItemRemoved(position);//item移除動畫 //更新position至adapter.getItemCount()-1的數據 adapter.notifyItemRangeChanged(position,adapter.getItemCount()-position-1); } };