【FastDev4Android框架開發】RecyclerView完全解析之打造新版類Gallery效果(二十九)
轉載請標明出處:
(一).前言:
【好訊息】個人網站已經上線執行,後面部落格以及技術乾貨等精彩文章會同步更新,請大家關注收藏:http://www.lcode.org
話說RecyclerView已經面市很久,也在很多應用中得到廣泛的使用,在整個開發者圈子裡面也擁有很不錯的口碑,那說明RecyclerView擁有比ListView,GridView之類控制元件有很多的優點,例如:資料繫結,Item View建立,View的回收以及重用等機制。本系列文章會包括到以下三個部分:
- RecyclerView控制元件的基本使用,包括基礎,進階,高階部分,動畫之類(點選進入
- RecyclerView控制元件的實戰例項
- RecyclerView控制元件集合AA(Android Annotations)注入框架例項
今天使我們本系列文章的第二講主要是我們通過RecyclerView來打造一個新版類似Gallery控制元件的效果。本次講解所有用的Demo例子已經全部更新到下面的專案中了,歡迎大家star和fork。
(二).基本實現
上一講我們已經對於RecyclerView的基本使用和進階部分做了講解(點選進入),下面我們一步步的來打造一個新版Gallery效果控制元件。先來看一下和RecyclerView相關類:
類名 | 說明 |
RecyclerView.Adapter | 可以託管資料集合,為每一項Item建立檢視並且繫結資料 |
RecyclerView.ViewHolder | 承載Item檢視的子佈局 |
RecyclerView.LayoutManager | 負責Item檢視的佈局的顯示管理 |
RecyclerView.ItemDecoration | 給每一項Item檢視新增子View,可以進行畫分隔線之類的東西 |
RecyclerView.ItemAnimator | 負責處理資料新增或者刪除時候的動畫效果 |
那如果要實現Gallery的效果,裡面的Item是橫向滑動的,也就是說我們的RecyclerView可以支援橫向滑動,這邊我們直接採用了LinearLayoutManager
下面來具體看程式碼:
1.作為RecyclerView控制元件,我們需要設定每一項Item的佈局:
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns: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) 建立ItemView然後通過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;
private LinearLayout top_bar_linear_back;
private TextView top_bar_title;
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.recycler_gallery_layout);
top_bar_linear_back=(LinearLayout)this.findViewById(R.id.top_bar_linear_back);
top_bar_linear_back.setOnClickListener(new CustomOnClickListener());
top_bar_title=(TextView)this.findViewById(R.id.top_bar_title);
top_bar_title.setText("RecyclerView打造Gallery效果");
//初始化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相關的資訊:
- findFirstVisibleItemPosition()
- findFirstCompletlyVisibleItemPosition()
- findLastVisibleItemPosition()
- findLastCompletlyVisibleItemPosition()
這邊的具體設定程式碼如下:
//建立佈局管理器
LinearLayoutManagerlinearLayoutManager=new LinearLayoutManager(this);
//設定橫向
linearLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);
//設定佈局管理器
gallery_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 voidonBindViewHolder(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();
intbottom=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(Contextcontext) {
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, intposition) {
models.add(position, model);
notifyItemInserted(position);
}
//刪除資料
public void removeItem(int position) {
models.remove(position);
notifyItemRemoved(position);
}
然後在外部進行呼叫這兩個方法:
adapter.addItem(newGalleryModel(R.drawable.ic_item_gallery,"Item Add"),3);
adapter.removeItem(2);
最後千萬不要忘記給RecyclerView設定動畫效果,我這邊就直接採用預設動畫了。
//設定動畫
gallery_recycler.setItemAnimator(newDefaultItemAnimator());
最終執行效果如下:
(七).最後總結
今天通過例項帶大家又重新把RecyclerView的相關使用講解了一遍,實現類似Gallery效果,當然例項中還有很多缺點,需要進一步優化,後面的文章中也會繼續更新的~
本次具體例項註釋過的全部程式碼已經上傳到FastDev4Android專案中了。同時歡迎大家去Github站點進行clone或者下載瀏覽:
https://github.com/jiangqqlmj/FastDev4Android同時歡迎大家star和fork整個開源快速開發框架專案~下一講我們會進行RecyclerView集合AA(Android Annotations)注入框架來實現例項,敬請期待!
關注我的訂閱號,每天分享移動開發技術(Android/IOS),專案管理以及部落格文章!
關注我的微博,可以獲得更多精彩內容