RecyclerView完全解析(二)——打造新版類(lèi)Gallery效果

特別聲明:

一、前言

  • 話說(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容