編輯:關於Android編程
針對RecyclerView,谷歌有一段介紹的話:
RecyclerView is a more advanced and flexible version of ListView. This widget is a Container for large sets of views that can be recycled and scrolled very efficiently. Use the RecyclerView widget when you have lists with elements that change dynamically.
大概就是說RecyclerView是一個更加高效靈活的ListView。當你有一系列的元素需要動態加載的時候,可以使用RecyclerView這個控件。
RecyclerView提供了高度自由化定制的功能,比如:
通過LayoutManager(布局管理器),控制item的顯示方式;
通過ItemDecoration,控制item間的背景;
通過ItemAnimator,控制動態增刪item的動畫;
雖然RecyclerView提供了非常自由化的定制操作,但是它自身並不支持item的點擊事件,也不像ListView一樣能夠簡單的添加頭和尾布局。想要實現這樣的功能,同樣需要自身去實現。
從上面我們可以看出使用RecyclerView的基本步驟:
recyclerView = (RecyclerView) findViewById(R.id.recyclerView); recyclerView.setLayoutManager(); //設置布局管理器 recyclerView.setAdapter(); //設置Adapter,同ListView recyclerView.addItemDecoration(); //設置Item的間隔背景 recyclerView.setItemAnimator(); //設置Item增刪時的動畫
下面我將通過代碼來逐步介紹RecyclerView的具體使用。
1.布局文件content_main.xml
2.Activity中的代碼
package mo.yumf.com.mddemo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private RecyclerView recyclerView; private ListmDatas; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.content_main); initData(); recyclerView = (RecyclerView) findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); //設置布局管理器 recyclerView.setAdapter(new MyAdapter(this,mDatas)); //設置Adapter } private void initData() { mDatas = new ArrayList<>(); for(int i = 0;i < 20;i ++){ mDatas.add("Test"+i); } } }
3.自定義Adapter
package mo.yumf.com.mddemo; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.List; public class MyAdapter extends RecyclerView.Adapter{ private Context context; private List mDatas; public MyAdapter(Context context,List mDatas) { this.context = context; this.mDatas = mDatas; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { MyViewHolder holder = new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item_recyclerview,parent,false)); return holder; } @Override public void onBindViewHolder(MyViewHolder holder, int position) { holder.textView.setText(mDatas.get(position)); } @Override public int getItemCount() { return mDatas.size(); } class MyViewHolder extends RecyclerView.ViewHolder { TextView textView; public MyViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.item_text); } } }
4.item的布局文件item_recyclerview.xml
上述代碼執行後效果圖:
可以看到上面的Item之間沒有分割線,給人感覺十分不友好,現在我們給它加上一個分割線背景。
文章開頭的部分,我們提到過設置分割線是通過方法addItemDecoration(ItemDecoration decor)。但是通過查看代碼能夠知道ItemDecoration 類是一個抽象類:
public static abstract class ItemDecoration { /** * 該方法會在item view 的繪制之前調用 */ public void onDraw(Canvas c, RecyclerView parent, State state) { onDraw(c, parent); } @Deprecated public void onDraw(Canvas c, RecyclerView parent) { } /** * 該方法會在item view 的繪制之後調用 */ public void onDrawOver(Canvas c, RecyclerView parent, State state) { onDrawOver(c, parent); } @Deprecated public void onDrawOver(Canvas c, RecyclerView parent) { } @Deprecated public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { outRect.set(0, 0, 0, 0); } /** *為每個item設置偏移量 */ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),parent); } }
我們在繼承該類來設置分割線時,需要用到的方法只有兩個:
1.繪制分割線 public void onDraw(Canvas c, RecyclerView parent, State state);
2.設置偏移量 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state);
接下來我們看看具體是如何實現繪制分割線:
package mo.yumf.com.mddemo; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.State; import android.view.View; 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, 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); 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, View view, RecyclerView parent, State state) { if (mOrientation == VERTICAL_LIST) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } } }
接著需要在原來的代碼中添加:
recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL_LIST));
修改之後,繼續運行:
可以看到在執行addItemDecoration()之後,可以看到一條分割線。那麼如果想要繼續修改這天分割線的高度,背景色,需要怎麼辦呢?其實從上面的DividerItemDecoration類中,可以看到這條分割線的繪制是從android.R.attr.listDivider中讀取的,所以我們在設置好這個類之後,可以再修改這個屬性值來達到修改分割線的目的。如下:
系統主題設置:
drawable/divider_bg.xml
運行代碼,效果圖:
好了,以上只是實現了類似ListView的布局效果,那麼還有沒有其他形式的布局效果呢,這就需要通過LayoutManager來實現了。
系統中LayoutManager是一個抽象類,他目前給我們提供了三個子類可以直接使用,如:
LinearLayoutManager:線性布局管理器,提供類似ListView的功能,如上;
GridLayoutManager:網格布局管理器;
StaggeredGridLayoutManager:瀑布流式布局管理器。
上面我們已經試過了LinearLayoutManager效果,現在我們可以繼續使用GridLayoutManager效果,修改代碼:
// recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setLayoutManager(new GridLayoutManager(this,3)); //每一行的列數
還需要重新繪制分割線,之前的DividerItemDecoration類已經不能使用了,我們需要重新繪制分割線:
package mo.yumf.com.mddemo; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.State; import android.support.v7.widget.StaggeredGridLayoutManager; import android.view.View; 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, State state) { drawHorizontal(c, parent); drawVertical(c, parent); } public void drawVertical(Canvas c, RecyclerView parent) { int childCount = parent.getChildCount(); for(int i = 0 ; i < childCount ; i ++){ View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int top = child.getTop() - params.topMargin; int bottom = child.getBottom() + params.bottomMargin; int left = child.getRight() + params.leftMargin; int right = left + mDivider.getIntrinsicWidth(); mDivider.setBounds(left,top,right,bottom); mDivider.draw(c); } } public void drawHorizontal(Canvas c, RecyclerView parent) { int childCount = parent.getChildCount(); for(int i = 0 ; i < childCount ; i ++){ View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int top = child.getBottom() + params.bottomMargin; int bottom = top + mDivider.getIntrinsicHeight(); int left = child.getLeft() - params.leftMargin; int right = child.getRight() + params.rightMargin + mDivider.getIntrinsicWidth(); mDivider.setBounds(left,top,right,bottom); mDivider.draw(c); } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition(); 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()); } } 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){ // // 如果是最後一列,則不需要繪制右邊 } 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; } 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; } }
這個DividerGridItemDecoration類,非本人所寫,借鑒自hongyang大神^-^!!。
然後,在修改一下divider_bg.xml中的寬度:
執行後,效果圖:
下面我們將繼續使用StaggeredGridLayoutManager布局管理器,來實現瀑布流的效果。
1.設置布局管理器為StaggeredGridLayoutManager
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL));
注:
在第二個參數為VERTICAL:前面的數字表示多少列;
在第二個參數為HORIZONTAL:前面的數字表示多少行;
2.設置item的分割線背景色,可以使用上面DividerGridItemDecoration類;
recyclerView.addItemDecoration(new DividerGridItemDecoration(this));
3.在Adapter中的onBindViewHolder方法裡,為Item設置隨機的高度。
...... private ListmHeights; ...... mHeights = new ArrayList (); for (int i = 0; i < mDatas.size(); i++){ mHeights.add( (int) (100 + Math.random() * 300)); } ..... @Override public void onBindViewHolder(MyViewHolder holder, int position) { ViewGroup.LayoutParams lp = holder.textView.getLayoutParams(); lp.height = mHeights.get(position); holder.textView.setLayoutParams(lp); holder.textView.setText(mDatas.get(position)); } .....
完成上述修改後,運行代碼:
前面我們說過recyclerView自身並不提供點擊的接口回調,這需要我們自己實現。為了能夠到達與傳統的ListView相同的點擊效果,我們可以在自定義的Adapter中,定義一個接口,通過該接口的方法可以將View以接口回調的方式傳遞出來。代碼如下:
package mo.yumf.com.mddemo; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class MyAdapter extends RecyclerView.Adapter{ private Context context; private List mDatas; private List mHeights; /** 定義接口*/ interface OnItemClickListener{ void onItemClick(View v,int position); } private OnItemClickListener onItemClickListener; /** 對外提供方法,接收示例對象*/ public void setOnItemClickListener(OnItemClickListener onItemClickListener){ this.onItemClickListener = onItemClickListener; } public MyAdapter(Context context,List mDatas) { this.context = context; this.mDatas = mDatas; 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(LayoutInflater.from(context).inflate(R.layout.item_recyclerview,parent,false)); return holder; } @Override public void onBindViewHolder(MyViewHolder holder, final int position) { ViewGroup.LayoutParams lp = holder.textView.getLayoutParams(); lp.height = mHeights.get(position); holder.textView.setLayoutParams(lp); holder.textView.setText(mDatas.get(position)); if(onItemClickListener != null){ holder.textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onItemClickListener.onItemClick(v,position); //使用接口回調的方法將參數傳遞出來 } }); } } @Override public int getItemCount() { return mDatas.size(); } class MyViewHolder extends RecyclerView.ViewHolder { TextView textView; public MyViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.item_text); } } } ------------------------------- Activity中調用方法: MyAdapter adapter = new MyAdapter(this,mDatas); adapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() { @Override public void onItemClick(View v,int position) { Toast.makeText(getApplicationContext(),position +"=="+((TextView)v).getText(),Toast.LENGTH_SHORT).show(); } });
完成上述修改後,運行代碼:
以上便是RecyclerView的基本使用,接下來將繼續介紹RecyclerView的其他知識點。
謝謝大家對該系列博文的支持與關注,我們現在趁熱打鐵正式開始我們的Android天氣軟件的開發吧!沒有閱讀過之前關於該軟件的功能需求的同學可以先看一下 一起來開發Andro
前情提要在上一篇中有一個細節沒有提到,那就是getResourcesForApplication和AssetManager的區別。getResourcesForAppli
需求:近段時間公司有要求寫一個類似於微信發送圖片時,用來選擇照片的一個圖片浏覽器,本來想在網上找一個直接拿來用,找尋無果,只能自己寫了。相信有很多網頁也有這
看到這個sweet-alert-dialog很親切,因為前端開發本人用的提示就是這個js插件,java牛人很厲害,直接弄成一個java包插件,Good!下面記錄如何引用到