特別聲明:
一、前言
- 話說(shuō)RecyclerView已經(jīng)面市很久,也在很多應(yīng)用中得到廣泛的使用,在整個(gè)開(kāi)發(fā)者圈子里面也擁有很不錯(cuò)的口碑,那說(shuō)明RecyclerView擁有比ListView,GridView之類(lèi)控件有很多的優(yōu)點(diǎn),例如:數(shù)據(jù)綁定,Item View創(chuàng)建,View的回收以及重用等機(jī)制。那么今天開(kāi)始我們來(lái)重點(diǎn)學(xué)習(xí)一下RecyclerView控件,本系列文章會(huì)包括到以下三個(gè)部分:
1. RecyclerView控件的基本使用,包括基礎(chǔ),進(jìn)階,高級(jí)部分,動(dòng)畫(huà)之類(lèi)
2. RecyclerView控件的實(shí)戰(zhàn)實(shí)例
3. RecyclerView控件集合AA(Android Annotations)注入框架實(shí)例
- 今天使我們本系列文章的第二講主要是我們通過(guò)RecyclerView來(lái)打造一個(gè)新版類(lèi)似Gallery控件的效果。本次講解所有用的Demo例子已經(jīng)全部更新到下面的項(xiàng)目中了,歡迎大家star和fork。
FastDev4Android框架項(xiàng)目地址:https://github.com/jiangqqlmj/FastDev4Android
二、基本實(shí)現(xiàn)
上一講我們已經(jīng)對(duì)于RecyclerView的基本使用和進(jìn)階部分做了講解(點(diǎn)擊進(jìn)入),下面我們一步步的來(lái)打造一個(gè)新版Gallery效果控件。
先來(lái)看一下和RecyclerView相關(guān)類(lèi):
| 類(lèi)名 | 說(shuō)明 |
|---|---|
| RecyclerView.Adapter | 可以托管數(shù)據(jù)集合,為每一項(xiàng)Item創(chuàng)建視圖并且綁定數(shù)據(jù) |
| RecyclerView.ViewHolder | 承載Item視圖的子布局 |
| RecyclerView.LayoutManager | 負(fù)責(zé)Item視圖的布局的顯示管理 |
| RecyclerView.ItemDecoration | 給每一項(xiàng)Item視圖添加子View,可以進(jìn)行畫(huà)分隔線之類(lèi)的東西 |
| RecyclerView.ItemAnimator | 負(fù)責(zé)處理數(shù)據(jù)添加或者刪除時(shí)候的動(dòng)畫(huà)效果 |
- 那如果要實(shí)現(xiàn)Gallery的效果,里面的Item是橫向滑動(dòng)的,也就是說(shuō)我們的RecyclerView可以支持橫向滑動(dòng),這邊我們直接采用了LinearLayoutManager布局管理器,同時(shí)設(shè)置方向?yàn)?HORIZONTAL(水平)
下面來(lái)具體看代碼:
1、作為RecyclerView控件,我們需要設(shè)置每一項(xiàng)Item的布局:
<?xmlversionxmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:androidLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:gravity="center"
android:padding="8.0dip">
<ImageView
android:id="@+id/item_img"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="fitXY"
android:adjustViewBounds="true"
android:src="@drawable/ic_item_gallery"/>
<TextView
android:id="@+id/item_tv"
android:text="標(biāo)題1"
android:layout_marginTop="5dp"
android:textSize="15sp"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
- 這個(gè)布局中我們比較簡(jiǎn)單,定義了一個(gè)圖片和一個(gè)標(biāo)題,垂直方向布局。
2、間接著,和ListView寫(xiě)法差不多,需要自定義適配器,來(lái)創(chuàng)建每一項(xiàng)布局視圖以及把數(shù)據(jù)和視圖綁定起來(lái),所以這邊繼承RecyclerView.Adapter類(lèi)創(chuàng)建一個(gè)自定義適配器GalleryRecyclerAdapter.java
- 那么需要實(shí)現(xiàn)基類(lèi)中的三個(gè)方法:
- onCreateViewHolder(ViewGroup parent,int viewType) :創(chuàng)建Item View然后通過(guò)ViewHolder來(lái)承載
onBindViewHolder(ViewHolder holder,int position):進(jìn)行視圖和數(shù)據(jù)綁定
getItemCount():獲取列表中視圖Item的數(shù)量
具體GallerRecyclerAdapter實(shí)現(xiàn)代碼如下:
public class GalleryRecyclerAdapter extends RecyclerView.Adapter<GalleryRecyclerAdapter.ViewHolder> {
private List<GalleryModel> models;
private LayoutInflater mInflater;
public GalleryRecyclerAdapter(Context context){
models=new ArrayList<GalleryModel>();
for (int i=0;i<20;i++){
int index=i+1;
models.add(new GalleryModel(R.drawable.ic_item_gallery,"Item"+index));
}
mInflater=LayoutInflater.from(context);
}
/**
* 創(chuàng)建Item View 然后使用ViewHolder來(lái)進(jìn)行承載
* @param parent
* @param viewType
* @return
*/
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view=mInflater.inflate(R.layout.item_gallery_recycler,parent,false);
ViewHolder viewHolder=new ViewHolder(view);
return viewHolder;
}
/**
* 進(jìn)行綁定數(shù)據(jù)
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.item_img.setImageResource(models.get(position).getImgurl());
holder.item_tv.setText(models.get(position).getTitle());
}
@Override
public int getItemCount() {
return models.size();
}
//自定義的ViewHolder,持有每個(gè)Item的的所有界面元素
public static class ViewHolder extends RecyclerView.ViewHolder {
private ImageView item_img;
private TextView item_tv;
public ViewHolder(View view){
super(view);
item_img=(ImageView)view.findViewById(R.id.item_img);
item_tv=(TextView)view.findViewById(R.id.item_tv);
}
}
}
3、注意看上面的代碼,我們繼承了RecyclerView.ViewHolder實(shí)現(xiàn)一個(gè)自定義類(lèi)ViewHolder,這個(gè)用來(lái)承載我們的子Item視圖,現(xiàn)在Google已經(jīng)要求開(kāi)發(fā)者必須要使用ViewHolder了。在ViewHolder中我們進(jìn)行控件的初始化工作,然后保存View視圖。
//自定義的ViewHolder,持有每個(gè)Item的的所有界面元素
public static class ViewHolder extends RecyclerView.ViewHolder {
private ImageView item_img;
private TextView item_tv;
public ViewHolder(View view){
super(view);
item_img=(ImageView)view.findViewById(R.id.item_img);
item_tv=(TextView)view.findViewById(R.id.item_tv);
}
}
4、最后在Activity中控件設(shè)置,例如布局管理器,Adapter綁定即可,完整代碼如下:
public class RecyclerGalleryActivity extends BaseActivity {
private RecyclerView gallery_recycler;
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.recycler_gallery_layout);
//初始化RecyclerView控件
gallery_recycler=(RecyclerView)this.findViewById(R.id.gallery_recycler);
//固定高度
gallery_recycler.setHasFixedSize(true);
//創(chuàng)建布局管理器
LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);
//設(shè)置橫向
linearLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);
//設(shè)置布局管理器
gallery_recycler.setLayoutManager(linearLayoutManager);
//創(chuàng)建適配器
GalleryRecyclerAdapter adapter=new GalleryRecyclerAdapter(this);
//綁定適配器
gallery_recycler.setAdapter(adapter);
}
class CustomOnClickListener implements View.OnClickListener{
@Override
public void onClick(View v) {
RecyclerGalleryActivity.this.finish();
}
}
}
5、在看運(yùn)行效果之前,我們先來(lái)看下上面的代碼,上面的代碼基本注釋已經(jīng)全部加了,相應(yīng)大家可以看的懂,不過(guò)我們需要來(lái)講一下上面的LayoutManager(布局管理器)。
- 在上一講中我們也講到了,LayoutManger(布局管理器)該類(lèi)負(fù)責(zé)將每一個(gè)Item視圖在RecyclerView中的布局。目前RecyclerView已經(jīng)給我們提供三個(gè)內(nèi)置管理器:
LinearLayoutManger
GridLayoutManger
StaggeredGridLayoutManager
- 這邊的例子中我們是采用LinearLayoutManger而且設(shè)置了橫向水平布局了。當(dāng)然LinearLayoutManger還給我們提供了以下幾個(gè)方法來(lái)讓開(kāi)發(fā)者方便的獲取到屏幕上面的頂部item和頂部item相關(guān)的信息:
1、findFirstVisibleItemPosition()
2、findFirstCompletlyVisibleItemPosition()
3、findLastVisibleItemPosition()
4、findLastCompletlyVisibleItemPosition()
- 這邊的具體設(shè)置代碼如下:
//創(chuàng)建布局管理器
LinearLayoutManagerlinearLayoutManager=new LinearLayoutManager(this);
//設(shè)置橫向
inearLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);
//設(shè)置布局管理器
allery_recycler.setLayoutManager(linearLayoutManager);
6、初步運(yùn)行效果如下:

三、升級(jí)加入點(diǎn)擊事件
通過(guò)上面的方式我們顯示了一個(gè)類(lèi)似于Gallery的效果,但是還遠(yuǎn)遠(yuǎn)不如實(shí)際Gallery的效果,現(xiàn)在只是可以有多項(xiàng)Item以及可以左右滑動(dòng),但是沒(méi)有點(diǎn)擊事件,下面我們來(lái)加入點(diǎn)擊事件操作。
對(duì)于ListView來(lái)講,我們可以為L(zhǎng)istView加入setOnItemClickListener監(jiān)聽(tīng)事件,但是對(duì)于RecyclerView控件來(lái)講,RecyclerView已經(jīng)不再負(fù)載Item視圖的布局和顯示,這些工作已經(jīng)交給了LayoutManger來(lái)做了。所以RecyclerView也沒(méi)有給我們提供類(lèi)似onItemClick事件,這樣如果非得要實(shí)現(xiàn)類(lèi)似的功能,我們開(kāi)發(fā)者也可以自定義模擬實(shí)現(xiàn)。來(lái),我們繼續(xù)往下看….
1、我們最終要實(shí)現(xiàn)點(diǎn)擊列表上面每一項(xiàng)Item來(lái)回調(diào)點(diǎn)擊方法,那么我們可以在Adapter中的每一項(xiàng)View上面做文章,首先我們來(lái)看一下Adapter中的onCreateViewHolder()方法:
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Viewview=mInflater.inflate(R.layout.item_gallery_recycler,parent,false);
ViewHolder viewHolder=new ViewHolder(view);
return viewHolder;
}
2、該方法創(chuàng)建出了Item 視圖,然后通過(guò)ViewHolder來(lái)進(jìn)行承載了,既然這樣那我們可以在View加載出來(lái)之后給它設(shè)置一些屬性例如:顏色,大小,當(dāng)然也可以是點(diǎn)擊事件等等。那這邊我們給View添加onClick事件,然后在onClick方法把View點(diǎn)擊觸發(fā)的事件回調(diào)出去,同時(shí)可以回調(diào)一些參數(shù)內(nèi)容出去。OK,那么我們這邊就需要一個(gè)自定義的接口了,我們創(chuàng)建一個(gè)GallerRecyclerAdapter的內(nèi)部類(lèi)接口:
(注意:內(nèi)部類(lèi)接口)
/**
* 類(lèi)似ListView的 onItemClickListener接口
*/
public interface OnRecyclerViewItemClickListener{
/**
* Item View發(fā)生點(diǎn)擊回調(diào)的方法
* @param view 點(diǎn)擊的View
* @paramposition 具體Item View的索引
*/
void onItemClick(View view,intposition);
}
3、然后定義接口,同時(shí)提供set和get方法,來(lái)讓外部傳入該接口,初始化:
private OnRecyclerViewItemClickListener onRecyclerViewItemClickListener;
public OnRecyclerViewItemClickListener getOnRecyclerViewItemClickListener() {
return onRecyclerViewItemClickListener;
}
public void setOnRecyclerViewItemClickListener(OnRecyclerViewItemClickListener onRecyclerViewItemClickListener) {
this.onRecyclerViewItemClickListener =onRecyclerViewItemClickListener;
}
4、現(xiàn)在開(kāi)始在onCreateViewHolder()方法中給View添加一個(gè)onClick事件,然后相應(yīng)處理,判斷onRecyclerViewItemClickListener是否存在,把事件回調(diào)出去:
view.setOnClickListener(newView.OnClickListener() {
@Override
public void onClick(View v) {
if(onRecyclerViewItemClickListener!=null){
onRecyclerViewItemClickListener.onItemClick(view,(int)view.getTag());
}
}
});
5、上面的代碼中大家可能注意到onItemClick()方法中的第二個(gè)參數(shù),獲取了tag,因?yàn)檫@邊的position索引值是在onBindViewHolder()方法中設(shè)置的:
public void onBindViewHolder(ViewHolder holder, int position) {
holder.item_img.setImageResource(models.get(position).getImgurl());
holder.item_tv.setText(models.get(position).getTitle());
holder.itemView.setTag(position);
}
6、OK這邊我們搞定了一個(gè)Item點(diǎn)擊監(jiān)聽(tīng)方法,接下去就是使用了:
adapter.setOnRecyclerViewItemClickListener(new GalleryRecyclerAdapter.OnRecyclerViewItemClickListener() {
@Override
public void onItemClick(View view,int position) {
Toast.makeText(RecyclerGalleryActivity.this,"您點(diǎn)擊的Item的索引為:"+position,Toast.LENGTH_SHORT).show();
}
});
7、現(xiàn)在該功能代碼整完了,運(yùn)行效果如下:

四、升級(jí)之加入分割線
上面我們已經(jīng)給每一項(xiàng)Item加入了點(diǎn)擊回調(diào)事件,但是總感覺(jué)還缺少點(diǎn)什么東西,例如分隔線。很遺憾的是,RecyclerView沒(méi)有提供ListView控件這樣設(shè)置分割線的方法,不過(guò)它給我們提供了ItemDecoration類(lèi)。這個(gè)ItemDecoration可以使得每一個(gè)Item在視覺(jué)上面進(jìn)行分隔開(kāi)來(lái)。RecyclerView沒(méi)有要求ItemDecoration必須要設(shè)置,同樣作為開(kāi)發(fā)者可以選擇不設(shè)置或者設(shè)置多個(gè)Decoration。然后RecyclerView會(huì)進(jìn)行相應(yīng)的繪制。
我們這邊定義了一個(gè)TestDecoration類(lèi),該類(lèi)繼承自RecyclerView.Decoration。只需要實(shí)現(xiàn)一下的兩個(gè)方法即可:
onDraw(Canvas c,RecyclerView parent,RecyclerView.State state)
getItemOffset(Rect outRect,int itemPosition,RecyclerView parent)
- 具體實(shí)現(xiàn)代碼如下:
public class TestDecoration extends RecyclerView.ItemDecoration {
//采用系統(tǒng)內(nèi)置的風(fēng)格的分割線
private static final int[] attrs=newint[]{android.R.attr.listDivider};
private Drawable mDivider;
public TestDecoration(Context context) {
TypedArray typedArray=context.obtainStyledAttributes(attrs);
mDivider=typedArray.getDrawable(0);
typedArray.recycle();
}
/**
* 進(jìn)行自定義繪制
* @param c
* @param parent
* @param state
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int top=parent.getPaddingTop();
int bottom=parent.getHeight()-parent.getPaddingBottom();
int childCount=parent.getChildCount();
for(int i=0;i<childCount;i++){
View child=parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams=(RecyclerView.LayoutParams)child.getLayoutParams();
intleft=child.getRight()+layoutParams.rightMargin;
intright=left+mDivider.getIntrinsicWidth();
mDivider.setBounds(left,top,right,bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect,View view, RecyclerView parent, RecyclerView.State state) {
outRect.set(0,0,mDivider.getIntrinsicWidth(),0);
}
}
- 最后給RecyclerView添加該分隔線即可:
//設(shè)置分割線
gallery_recycler.addItemDecoration(new TestDecoration(this));
-
運(yùn)行效果大致如下:
五、升級(jí)之分割線改造
仔細(xì)看上面的運(yùn)行效果,我們會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題,那就是分割線垂直分布,但是沒(méi)有自適應(yīng)控件的高度,直接延伸到界面的底部了。重新檢查了有關(guān)的所有布局文件發(fā)現(xiàn),高度都設(shè)置成了warp_content,但是實(shí)際的效果還是沒(méi)有自適應(yīng)。原來(lái)在哪里呢?
真正的原因是因?yàn)镽ecyclerView控件已經(jīng)不負(fù)責(zé)每一項(xiàng)VIew的顯示了,那我們來(lái)看LayoutManger(布局管理器)該進(jìn)行負(fù)責(zé)Item的布局顯示了,所以我們需要進(jìn)行實(shí)現(xiàn)一個(gè)LayoutManger,然后重寫(xiě)里邊的onMeasure()方法。計(jì)算高度即可,具體代碼如下:
public class CustomLinearLayoutManager extends LinearLayoutManager {
public CustomLinearLayoutManager(Context context) {
super(context);
}
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
Viewview=recycler.getViewForPosition(0);
if(view!=null){
measureChild(view,widthSpec,heightSpec);
int mWidth=View.MeasureSpec.getSize(widthSpec);
intmHeight=view.getMeasuredHeight();
setMeasuredDimension(mWidth,mHeight);
}
}
}
-
然后RecyclerView使用CustomLinearLayoutManger即可,運(yùn)行效果如下:
六、升級(jí)之添加刪除Item動(dòng)畫(huà)
- RecyclerView控件的一個(gè)優(yōu)美之處就是當(dāng)里邊Item發(fā)生變化的時(shí)候可以加入相應(yīng)的動(dòng)畫(huà)效果,涉及的類(lèi)為RecyclerView.ItemAnimatior。一般當(dāng)存在以下三種操作的時(shí)候可以加入動(dòng)畫(huà)效果:
Item 刪除
Item 添加
Item 移動(dòng)
- 當(dāng)我們的數(shù)據(jù)變化,或者移動(dòng)的時(shí)候,用Adapter給我們提供的以下兩個(gè)方法即可:
notifyItemInserted(int position)
notifyItemRemoved(int position)
- 那我們可以在Adapter中加入兩個(gè)方法,分別為添加Item和刪除Item的方法:
//添加數(shù)據(jù)
public void addItem(GalleryModel model, int position) {
models.add(position, model);
notifyItemInserted(position);
}
//刪除數(shù)據(jù)
public void removeItem(int position) {
models.remove(position);
notifyItemRemoved(position);
}
- 然后在外部進(jìn)行調(diào)用這兩個(gè)方法:
//添加數(shù)據(jù)
adapter.addItem(new GalleryModel(R.drawable.ic_item_gallery,"Item Add"),3);
//移除數(shù)據(jù)
dapter.removeItem(2);
- 最后千萬(wàn)不要忘記給RecyclerView設(shè)置動(dòng)畫(huà)效果,我這邊就直接采用默認(rèn)動(dòng)畫(huà)了。
//設(shè)置動(dòng)畫(huà)
gallery_recycler.setItemAnimator(new DefaultItemAnimator());
-
最終運(yùn)行效果如下:
七、最后總結(jié)
今天通過(guò)實(shí)例帶大家又重新把RecyclerView的相關(guān)使用講解了一遍,實(shí)現(xiàn)類(lèi)似Gallery效果,當(dāng)然實(shí)例中還有很多缺點(diǎn),需要進(jìn)一步優(yōu)化,后面的文章中也會(huì)繼續(xù)更新的~
本次具體實(shí)例注釋過(guò)的全部代碼已經(jīng)上傳到FastDev4Android項(xiàng)目中了。同時(shí)歡迎大家去Github站點(diǎn)進(jìn)行clone或者下載瀏覽:https://github.com/jiangqqlmj/FastDev4Android 同時(shí)歡迎大家star和fork整個(gè)開(kāi)源快速開(kāi)發(fā)框架項(xiàng)目~下一講我們會(huì)進(jìn)行RecyclerView集合AA(Android Annotations)注入框架來(lái)實(shí)現(xiàn)實(shí)例,敬請(qǐng)期待!
再次聲明:本文轉(zhuǎn)載自【江清清的博客】http://blog.csdn.net/developer_jiangqq/article/details/49946589


