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

特別聲明:

一、前言

  • 話說RecyclerView已經面市很久,也在很多應用中得到廣泛的使用,在整個開發者圈子里面也擁有很不錯的口碑,那說明RecyclerView擁有比ListView,GridView之類控件有很多的優點,例如:數據綁定,Item View創建,View的回收以及重用等機制。那么今天開始我們來重點學習一下RecyclerView控件,本系列文章會包括到以下三個部分:

1. RecyclerView控件的基本使用,包括基礎,進階,高級部分,動畫之類
2. RecyclerView控件的實戰實例
3. RecyclerView控件集合AA(Android Annotations)注入框架實例

  • 今天使我們本系列文章的第二講主要是我們通過RecyclerView來打造一個新版類似Gallery控件的效果。本次講解所有用的Demo例子已經全部更新到下面的項目中了,歡迎大家star和fork。

FastDev4Android框架項目地址:https://github.com/jiangqqlmj/FastDev4Android

二、基本實現

  • 上一講我們已經對于RecyclerView的基本使用和進階部分做了講解(點擊進入),下面我們一步步的來打造一個新版Gallery效果控件。

  • 先來看一下和RecyclerView相關類:

類名 說明
RecyclerView.Adapter 可以托管數據集合,為每一項Item創建視圖并且綁定數據
RecyclerView.ViewHolder 承載Item視圖的子布局
RecyclerView.LayoutManager 負責Item視圖的布局的顯示管理
RecyclerView.ItemDecoration 給每一項Item視圖添加子View,可以進行畫分隔線之類的東西
RecyclerView.ItemAnimator 負責處理數據添加或者刪除時候的動畫效果
  • 那如果要實現Gallery的效果,里面的Item是橫向滑動的,也就是說我們的RecyclerView可以支持橫向滑動,這邊我們直接采用了LinearLayoutManager布局管理器,同時設置方向為:HORIZONTAL(水平)
下面來具體看代碼:

1、作為RecyclerView控件,我們需要設置每一項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="標題1"  
        android:layout_marginTop="5dp"  
        android:textSize="15sp" 
        android:layout_gravity="center_horizontal"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        />  
</LinearLayout>  
  • 這個布局中我們比較簡單,定義了一個圖片和一個標題,垂直方向布局。

2、間接著,和ListView寫法差不多,需要自定義適配器,來創建每一項布局視圖以及把數據和視圖綁定起來,所以這邊繼承RecyclerView.Adapter類創建一個自定義適配器GalleryRecyclerAdapter.java

  • 那么需要實現基類中的三個方法:
  • onCreateViewHolder(ViewGroup parent,int viewType) :創建Item View然后通過ViewHolder來承載
  • onBindViewHolder(ViewHolder holder,int position):進行視圖和數據綁定

  • getItemCount():獲取列表中視圖Item的數量

  • 具體GallerRecyclerAdapter實現代碼如下:

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);  
    }  
   
    /** 
     * 創建Item View  然后使用ViewHolder來進行承載 
     * @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;  
    }  
   
    /** 
     * 進行綁定數據 
     * @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,持有每個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實現一個自定義類ViewHolder,這個用來承載我們的子Item視圖,現在Google已經要求開發者必須要使用ViewHolder了。在ViewHolder中我們進行控件的初始化工作,然后保存View視圖。

   //自定義的ViewHolder,持有每個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中控件設置,例如布局管理器,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);  
        //創建布局管理器  
        LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);  
        //設置橫向  
        linearLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);  
        //設置布局管理器  
        gallery_recycler.setLayoutManager(linearLayoutManager);  
        //創建適配器  
        GalleryRecyclerAdapter adapter=new GalleryRecyclerAdapter(this);  
        //綁定適配器  
        gallery_recycler.setAdapter(adapter);  
    }  
    class CustomOnClickListener implements View.OnClickListener{  
        @Override  
        public void onClick(View v) {  
           RecyclerGalleryActivity.this.finish();  
        }  
    }  
}  

5、在看運行效果之前,我們先來看下上面的代碼,上面的代碼基本注釋已經全部加了,相應大家可以看的懂,不過我們需要來講一下上面的LayoutManager(布局管理器)。

  • 在上一講中我們也講到了,LayoutManger(布局管理器)該類負責將每一個Item視圖在RecyclerView中的布局。目前RecyclerView已經給我們提供三個內置管理器:

LinearLayoutManger
GridLayoutManger
StaggeredGridLayoutManager

  • 這邊的例子中我們是采用LinearLayoutManger而且設置了橫向水平布局了。當然LinearLayoutManger還給我們提供了以下幾個方法來讓開發者方便的獲取到屏幕上面的頂部item和頂部item相關的信息:

1、findFirstVisibleItemPosition()
2、findFirstCompletlyVisibleItemPosition()
3、findLastVisibleItemPosition()
4、findLastCompletlyVisibleItemPosition()

  • 這邊的具體設置代碼如下:
//創建布局管理器  
LinearLayoutManagerlinearLayoutManager=new LinearLayoutManager(this);  
//設置橫向  
inearLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);  
//設置布局管理器  
allery_recycler.setLayoutManager(linearLayoutManager);  

6、初步運行效果如下:

三、升級加入點擊事件

  • 通過上面的方式我們顯示了一個類似于Gallery的效果,但是還遠遠不如實際Gallery的效果,現在只是可以有多項Item以及可以左右滑動,但是沒有點擊事件,下面我們來加入點擊事件操作。

  • 對于ListView來講,我們可以為ListView加入setOnItemClickListener監聽事件,但是對于RecyclerView控件來講,RecyclerView已經不再負載Item視圖的布局和顯示,這些工作已經交給了LayoutManger來做了。所以RecyclerView也沒有給我們提供類似onItemClick事件,這樣如果非得要實現類似的功能,我們開發者也可以自定義模擬實現。來,我們繼續往下看….

1、我們最終要實現點擊列表上面每一項Item來回調點擊方法,那么我們可以在Adapter中的每一項View上面做文章,首先我們來看一下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、該方法創建出了Item 視圖,然后通過ViewHolder來進行承載了,既然這樣那我們可以在View加載出來之后給它設置一些屬性例如:顏色,大小,當然也可以是點擊事件等等。那這邊我們給View添加onClick事件,然后在onClick方法把View點擊觸發的事件回調出去,同時可以回調一些參數內容出去。OK,那么我們這邊就需要一個自定義的接口了,我們創建一個GallerRecyclerAdapter的內部類接口:
注意:內部類接口

   /** 
     * 類似ListView的 onItemClickListener接口 
     */  
    public interface OnRecyclerViewItemClickListener{  
        /** 
         * Item View發生點擊回調的方法 
         * @param view   點擊的View 
         * @paramposition  具體Item View的索引 
         */  
        void onItemClick(View view,intposition);  
    }  

3、然后定義接口,同時提供set和get方法,來讓外部傳入該接口,初始化:

    private OnRecyclerViewItemClickListener onRecyclerViewItemClickListener;  
   
    public OnRecyclerViewItemClickListener getOnRecyclerViewItemClickListener() {  
        return onRecyclerViewItemClickListener;  
    }  
    public void setOnRecyclerViewItemClickListener(OnRecyclerViewItemClickListener onRecyclerViewItemClickListener) {  
        this.onRecyclerViewItemClickListener =onRecyclerViewItemClickListener;  
    }  

4、現在開始在onCreateViewHolder()方法中給View添加一個onClick事件,然后相應處理,判斷onRecyclerViewItemClickListener是否存在,把事件回調出去:

view.setOnClickListener(newView.OnClickListener() {  
           @Override  
           public void onClick(View v) {  
              if(onRecyclerViewItemClickListener!=null){  
                  onRecyclerViewItemClickListener.onItemClick(view,(int)view.getTag());  
               }  
           }  
 });  

5、上面的代碼中大家可能注意到onItemClick()方法中的第二個參數,獲取了tag,因為這邊的position索引值是在onBindViewHolder()方法中設置的:

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這邊我們搞定了一個Item點擊監聽方法,接下去就是使用了:

adapter.setOnRecyclerViewItemClickListener(new GalleryRecyclerAdapter.OnRecyclerViewItemClickListener() {  
            @Override  
            public void onItemClick(View view,int position) {  
               Toast.makeText(RecyclerGalleryActivity.this,"您點擊的Item的索引為:"+position,Toast.LENGTH_SHORT).show();  
            }  
        });  

7、現在該功能代碼整完了,運行效果如下:

四、升級之加入分割線

  • 上面我們已經給每一項Item加入了點擊回調事件,但是總感覺還缺少點什么東西,例如分隔線。很遺憾的是,RecyclerView沒有提供ListView控件這樣設置分割線的方法,不過它給我們提供了ItemDecoration類。這個ItemDecoration可以使得每一個Item在視覺上面進行分隔開來。RecyclerView沒有要求ItemDecoration必須要設置,同樣作為開發者可以選擇不設置或者設置多個Decoration。然后RecyclerView會進行相應的繪制。

  • 我們這邊定義了一個TestDecoration類,該類繼承自RecyclerView.Decoration。只需要實現一下的兩個方法即可:

onDraw(Canvas c,RecyclerView parent,RecyclerView.State state)
getItemOffset(Rect outRect,int itemPosition,RecyclerView parent)

  • 具體實現代碼如下:
public class TestDecoration extends RecyclerView.ItemDecoration {  
    //采用系統內置的風格的分割線  
    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();  
    }  
   
    /** 
     * 進行自定義繪制 
     * @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添加該分隔線即可:
//設置分割線  
gallery_recycler.addItemDecoration(new TestDecoration(this));  
  • 運行效果大致如下:


五、升級之分割線改造

  • 仔細看上面的運行效果,我們會發現一個問題,那就是分割線垂直分布,但是沒有自適應控件的高度,直接延伸到界面的底部了。重新檢查了有關的所有布局文件發現,高度都設置成了warp_content,但是實際的效果還是沒有自適應。原來在哪里呢?

  • 真正的原因是因為RecyclerView控件已經不負責每一項VIew的顯示了,那我們來看LayoutManger(布局管理器)該進行負責Item的布局顯示了,所以我們需要進行實現一個LayoutManger,然后重寫里邊的onMeasure()方法。計算高度即可,具體代碼如下:

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即可,運行效果如下:


六、升級之添加刪除Item動畫

  • RecyclerView控件的一個優美之處就是當里邊Item發生變化的時候可以加入相應的動畫效果,涉及的類為RecyclerView.ItemAnimatior。一般當存在以下三種操作的時候可以加入動畫效果:

Item 刪除
Item 添加
Item 移動

  • 當我們的數據變化,或者移動的時候,用Adapter給我們提供的以下兩個方法即可:

notifyItemInserted(int position)
notifyItemRemoved(int position)

  • 那我們可以在Adapter中加入兩個方法,分別為添加Item和刪除Item的方法:
//添加數據  
  public void addItem(GalleryModel model, int position) {  
      models.add(position, model);  
      notifyItemInserted(position);  
  }  
  //刪除數據  
  public void removeItem(int position) {  
      models.remove(position);  
      notifyItemRemoved(position);  
  }  
  • 然后在外部進行調用這兩個方法:
//添加數據
 adapter.addItem(new GalleryModel(R.drawable.ic_item_gallery,"Item Add"),3);  
//移除數據
dapter.removeItem(2);  
  • 最后千萬不要忘記給RecyclerView設置動畫效果,我這邊就直接采用默認動畫了。
//設置動畫  
gallery_recycler.setItemAnimator(new DefaultItemAnimator());  
  • 最終運行效果如下:


七、最后總結

  • 今天通過實例帶大家又重新把RecyclerView的相關使用講解了一遍,實現類似Gallery效果,當然實例中還有很多缺點,需要進一步優化,后面的文章中也會繼續更新的~

  • 本次具體實例注釋過的全部代碼已經上傳到FastDev4Android項目中了。同時歡迎大家去Github站點進行clone或者下載瀏覽:https://github.com/jiangqqlmj/FastDev4Android 同時歡迎大家star和fork整個開源快速開發框架項目~下一講我們會進行RecyclerView集合AA(Android Annotations)注入框架來實現實例,敬請期待!

  • 再次聲明:本文轉載自【江清清的博客】http://blog.csdn.net/developer_jiangqq/article/details/49946589

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容