編輯:關於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 List mDatas;
@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的天氣軟件(二)
謝謝大家對該系列博文的支持與關注,我們現在趁熱打鐵正式開始我們的Android天氣軟件的開發吧!沒有閱讀過之前關於該軟件的功能需求的同學可以先看一下 一起來開發Andro
Android插件化探索(三)免安裝運行Activity(上)
前情提要在上一篇中有一個細節沒有提到,那就是getResourcesForApplication和AssetManager的區別。getResourcesForAppli
android 圖片選擇器 圖片預覽
需求:近段時間公司有要求寫一個類似於微信發送圖片時,用來選擇照片的一個圖片浏覽器,本來想在網上找一個直接拿來用,找尋無果,只能自己寫了。相信有很多網頁也有這
sweet alert dialog 在android studio應用問題說明詳解
看到這個sweet-alert-dialog很親切,因為前端開發本人用的提示就是這個js插件,java牛人很厲害,直接弄成一個java包插件,Good!下面記錄如何引用到