特別聲明:
一、前言
- 話說RecyclerView已經面市很久,也在很多應用中得到廣泛的使用,在整個開發者圈子里面也擁有很不錯的口碑,那說明RecyclerView擁有比ListView,GridView之類控件有很多的優點,例如:數據綁定,Item View創建,View的回收以及重用等機制。那么今天開始我們來重點學習一下RecyclerView控件,本系列文章會包括到以下三個部分:
1. RecyclerView控件的基本使用,包括基礎,進階,高級部分,動畫之類
2. RecyclerView控件的實戰實例
3. RecyclerView控件集合AA(Android Annotations)注入框架實例
- 那么今天我們首先來看第一部分:RecyclerView控件的基本使用,進階,動畫相關知識點。本次講解所有用的Demo例子已經全部更新到下面的項目中了,歡迎大家star和fork。
FastDev4Android框架項目地址:https://github.com/jiangqqlmj/FastDev4Android
二、RecyclerView基本介紹:
- 通過使用RecyclerView控件,我們可以在APP中創建帶有Material Design風格的復雜列表。RecyclerView控件和ListView的原理有很多相似的地方,都是維護少量的View來進行顯示大量的數據,不過RecyclerView控件比ListView更加高級并且更加靈活。當我們的數據因為用戶事件或者網絡事件發生改變的時候也能很好的進行顯示。
- 和ListView不同的是,RecyclerView不用在負責Item的顯示相關的功能,在這邊所有有關布局,繪制,數據綁定等都被分拆成不同的類進行管理,下面我這邊會一個個的進行講解。同時RecyclerView控件提供了以下兩種方法來進行簡化和處理大數量集合:
1. 采用LayoutManager來處理Item的布局
2. 提供Item操作的默認動畫,例如在增加或者刪除item的時候
-
你也可以自定義LayoutManager或者設置添加/刪除的動畫,整體的RecyclerView結構圖如下:
為了使用RecyclerView控件,我們需要創建一個Adapter和一個LayoutManager:
Adapter:繼承自RecyclerView.Adapetr類,主要用來將數據和布局item進行綁定。
LayoutManager:布局管理器,設置每一項view在RecyclerView中的位置布局以及控件item view的顯示或者隱藏。當View重用或者回收的時候,LayoutManger都會向Adapter來請求新的數據來進行替換原來數據的內容。這種回收重用的機制可以提供性能,避免創建很多的view或者是頻繁的調用findViewById方法。這種機制和ListView還是很相似的。
- RecyclerView提供了三種內置的LayoutManager:
1. LinearLayoutManager:線性布局,橫向或者縱向滑動列表
2. GridLayoutManager:表格布局
3. StaggeredGridLayoutManager:流式布局,例如瀑布流效果
當然除了上面的三種內部布局之外,我們還可以繼承RecyclerView.LayoutManager來實現一個自定義的LayoutManager。
Animations(動畫)效果:
RecyclerView對于Item的添加和刪除是默認開啟動畫的。我們當然也可以通過RecyclerView.ItemAnimator類定制動畫,然后通過RecyclerView.setItemAnimator()方法來進行使用。
- RecyclerView相關類:
類名 | 說明 |
---|---|
RecyclerView.Adapter | 可以托管數據集合,為每一項Item創建視圖并且綁定數據 |
RecyclerView.ViewHolder | 承載Item視圖的子布局 |
RecyclerView.LayoutManager | 負責Item視圖的布局的顯示管理 |
RecyclerView.ItemDecoration | 給每一項Item視圖添加子View,例如可以進行畫分隔線之類的 |
RecyclerView.ItemAnimator | 負責處理數據添加或者刪除時候的動畫效果 |
三、RecyclerView基本實現:
- 我這邊實例采用Android Studio 1.3.2。
1、添加庫依賴:
dependencies {
…….
compile'com.android.support:recyclerview-v7:23.1.1'
}
2、新建布局,引入RecyclerView控件:
<?xmlversionxmlversion="1.0" encoding="utf-8"?>
<LinearLayout xmlns:androidLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="match_parent"
android:layout_height="match_parent">
<includelayoutincludelayout="@layout/common_top_bar_layout"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView_one"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
></android.support.v7.widget.RecyclerView>
</LinearLayout>
3、在Activity中獲取RecyclerView控件然后進行設置LayoutManger以及Adapter即可,和ListView的寫法有點類似:
public class RecyclerViewTestActivity extends AppCompatActivity {
private RecyclerView recyclerView_one;
private RecyclerView.Adapter mAdapter;
private LinearLayoutManager mLayoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//開始設置RecyclerView
recyclerView_one=(RecyclerView)this.findViewById(R.id.recyclerView);
//設置固定大小
recyclerView_one.setHasFixedSize(true);
//創建線性布局
mLayoutManager = new LinearLayoutManager(this);
//垂直方向
mLayoutManager.setOrientation(OrientationHelper.VERTICAL);
//給RecyclerView設置布局管理器
recyclerView_one.setLayoutManager(mLayoutManager);
//創建適配器,并且設置
mAdapter = new TestRecyclerAdapter(this);
recyclerView_one.setAdapter(mAdapter);
}
}
4、自定義一個適配器來進行創建item view以及綁定數據
public class TestRecyclerAdapter extends RecyclerView.Adapter<TestRecyclerAdapter.ViewHolder>{
private LayoutInflater mInflater;
private String[] mTitles=null;
public TestRecyclerAdapter(Context context){
this.mInflater=LayoutInflater.from(context);
this.mTitles=new String[20];
for (int i=0;i<20;i++){
int index=i+1;
mTitles[i]="item"+index;
}
}
/**
* item顯示類型
* @param parent
* @param viewType
* @return
*/
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
//view.setBackgroundColor(Color.RED);
ViewHolder viewHolder=new ViewHolder(view);
return viewHolder;
}
/**
* 數據的綁定顯示
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.item_tv.setText(mTitles[position]);
}
@Override
public int getItemCount() {
return mTitles.length;
}
//自定義的ViewHolder,持有每個Item的的所有界面元素
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView item_tv;
public ViewHolder(View view){
super(view);
item_tv = (TextView)view.findViewById(R.id.item_tv);
}
}
}
這個自定義Adapter和我們在使用Listview時候的Adapter相比還是有點不太一樣的,首先這邊我們需要繼承RecyclerView.Adaper類,然后實現兩個重要的方法onBindViewHodler()以及onCreateViewHolder(),這邊我們看出來區別,使用RecyclerView控件我們就可以把Item View視圖創建和數據綁定這兩步進行分來進行管理,用法就更加方便而且靈活了,并且我們可以定制打造千變萬化的布局。同時這邊我們還需要創建一個ViewHolder類,該類必須繼承自RecyclerView.ViewHolder類,現在Google也要求我們必須要實現ViewHolder來承載Item的視圖。
-
該Demo運行效果如下:
上面的例子我們這邊比較簡單使用LinearLayoutManager來實現了,其中布局是采用垂直布局的,當然我們還可以設置線性布局的方向為橫向,只要如下設置即可:
mLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);
運行效果如下:
那么另外兩種內置的布局如下:
- 1、GridLayoutManger:使用如下設置:
GridLayoutManager girdLayoutManager=new GridLayoutManager(this,4);
recyclerView_one.setLayoutManager(girdLayoutManager);
運行效果如下:
- 2、StaggeredGridLayoutManager :使用如下設置:
StaggeredGridLayoutManager staggeredGridLayoutManager=new StaggeredGridLayoutManager(2,OrientationHelper.VERTICAL);
recyclerView_one.setLayoutManager(staggeredGridLayoutManager);
實現的是瀑布流的效果
四、RecyclerView分隔線實現(ItemDecoration):
- 大家肯定觀察到上面的顯示效果還是比較丑,例如就沒有分隔線這個效果,下面我們一起來實現以下分隔線的效果。還記得前面的一個表格中有寫關于RecyclerView的相關類:
RecyclerView.ItemDecoration | 給每一項Item視圖添加子View,可以進行畫分隔線之類的東西 |
---|
我們可以創建一個繼承RecyclerView.ItemDecoration類來繪制分隔線,通過ItemDecoration可以讓我們每一個Item從視覺上面相互分開來,例如ListView的divider非常相似的效果。當然像我們上面的例子ItemDecoration我們沒有設置也沒有報錯哦,那說明ItemDecoration我們并不是強制需要使用,作為我們開發者可以設置或者不設置Decoration的。
實現一個ItemDecoration,系統提供的ItemDecoration是一個抽象類,內部除去已經廢棄的方法以外,我們主要實現以下三個方法:
public static abstract class ItemDecoration {
public void onDraw(Canvas c,RecyclerView parent, State state) {
onDraw(c, parent);
}
public void onDrawOver(Canvas c,RecyclerView parent, State state) {
onDrawOver(c, parent);
}
public void getItemOffsets(RectoutRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect,((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}
又因為當我們RecyclerView在進行繪制的時候會進行繪制Decoration,那么會去調用onDraw和onDrawOver方法,那么這邊我們其實只要去重寫onDraw和getItemOffsets這兩個方法就可以實現啦。然后LayoutManager會進行Item布局的時候,回去調用getItemOffset方法來計算每個Item的Decoration合適的尺寸
下面我們來具體實現一個Decoration:
TestDecoration.java
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);
}
/**
* 進行自定義繪制
* @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);
}
}
- 我這邊實例中采用系統主題(android.R.attr.listDivider)來設置成分隔線的,然后來獲取尺寸,位置進行setBound(),繪制,接著通過outRect.set()來設置繪制整個區域范圍,最后不要忘記往RecyclerView中設置該自定義的分割線:
//添加分割線
recyclerView_one.addItemDecoration(newTestDecoration(this));
-
運行效果大致如下:
上面的分割線效果只是垂直畫了分割線,但是我們水平方向也要進行畫分割線,那么我們下面對于自定義的Decoration進行改進:
AdvanceDecoration.java
/**
* 當前類注釋:改進之后的自定義Decoration分割線
*/
public class AdvanceDecoration extends RecyclerView.ItemDecoration{
//采用系統內置的風格的分割線
private static final int[] attrs=newint[]{android.R.attr.listDivider};
private Drawable mDivider;
private int orientation;
public AdvanceDecoration(Contextcontext,int orientation) {
TypedArray typedArray=context.obtainStyledAttributes(attrs);
mDivider=typedArray.getDrawable(0);
typedArray.recycle();
this.orientation=orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
drawHDeraction(c,parent);
drawVDeraction(c,parent);
}
/**
* 繪制水平方向的分割線
* @param c
* @param parent
*/
private void drawHDeraction(Canvas c,RecyclerView parent){
int left=parent.getPaddingLeft();
intright=parent.getWidth()-parent.getPaddingRight();
int childCount=parent.getChildCount();
for(int i=0;i<childCount;i++){
View child=parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams=(RecyclerView.LayoutParams)child.getLayoutParams();
inttop=child.getBottom()+layoutParams.bottomMargin;
intbottom=top+mDivider.getIntrinsicHeight();
mDivider.setBounds(left,top,right,bottom);
mDivider.draw(c);
}
}
/**
* 繪制垂直方向的分割線
* @param c
* @param parent
*/
private void drawVDeraction(Canvas c,RecyclerView parent){
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.LayoutParamsla youtParams=(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) {
if(OrientationHelper.HORIZONTAL==orientation){
outRect.set(0, 0,mDivider.getIntrinsicWidth(), 0);
}else {
outRect.set(0, 0, 0,mDivider.getIntrinsicHeight());
}
}
}
- 改良之后的自定義分割器的構造函數中新增一個int參數,用來表示橫向還是縱向布局,這樣我們可以分別來繪制分割線。
- 具體使用方法:
recyclerView_one.addItemDecoration(new AdvanceDecoration(this,OrientationHelper.VERTICAL));
- 運行比較效果如下:
橫向效果
縱向效果
五、RecyclerView高級用戶(監聽事件處理)
我們知道在ListView使用的時候,該控件給我們提供一個onItemClickListener監聽器,這樣當我們的item發生觸發事件的時候,會回調相關的方法,以便我們方便處理Item點擊事件。
對于RecyclerView來講,非常可惜的時候,該控件沒有給我們提供這樣的內置監聽器方法,不過我們可以進行改造實現。我們先來看一下之前我們寫得TestRecyclerAdapter中的onCreateViewHolder()方法中的代碼:
public ViewHolder onCreateViewHolder(ViewGroupparent, int viewType) {
Viewview=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
//這邊可以做一些屬性設置,甚至事件監聽綁定
//view.setBackgroundColor(Color.RED);
ViewHolder viewHolder=newViewHolder(view);
return viewHolder;
}
- 該方法創建一個ViewHolder,其中承載的就是每一項Item View視圖,那么我們可以在view創建出來之后給它進行添加相應的屬性或者監聽方法,例如:背景顏色,大小,以及點擊事件。既然可以這樣解決,OK,我們給View添加一個onClickListener監聽器,然后點擊的時候回調onClick()方法。同時我們需要自定義一個類似于onItemClickListener()的監聽器來處理。
注意:這個監聽就在該adapter里定義
/**
* 自定義RecyclerView 中item view點擊回調方法
*/
interface OnRecyclerItemClickListener{
/**
* item view 回調方法
* @param view 被點擊的view
* @param position 點擊索引
*/
void onItemClick(View view, intposition);
}
- 然后聲明以及Adapter初始化的時候傳入進去:
public TestRecyclerAdapter(Contextcontext,OnRecyclerItemClickListener onRecyclerItemClickListener){
……
this.onRecyclerItemClickListener=onRecyclerItemClickListener;
}
- 然后我們在onClick回調方法中調用OnRecyclerItemClickListener接口的方法:
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(onRecyclerItemClickListener!=null){
onRecyclerItemClickListener.onItemClick(view, (int)view.getTag());
}
}
});
注意:這里的view就是onCreateViewHolder方法里返回的view,完整邏輯大致就是如下的樣子:
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
//這邊可以做一些屬性設置,甚至事件監聽綁定
//view.setBackgroundColor(Color.RED);
ViewHolder viewHolder=new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(onRecyclerItemClickListener!=null){
onRecyclerItemClickListener.onItemClick(view, (int)view.getTag());
}
}
});
return viewHolder;
}
- 上面的onItemClick中第二個參數的position,采用view.getTag的方法獲取,那么我們就需要在onBindViewHolder()方法中設置一個tag了:
public voidonBindViewHolder(ViewHolder holder, int position) {
holder.item_tv.setText(mTitles[position]);
holder.itemView.setTag(position);
}
- 最后我們在外部使用一下接口:
mAdapter = new TestRecyclerAdapter(this, new TestRecyclerAdapter.OnRecyclerItemClickListener() {
@Override
public void onItemClick(View view,int position) {
Toast.makeText(RecyclerViewTestActivity.this, "點擊了第"+position+"項", Toast.LENGTH_SHORT).show();
}
});
-
運行結果大致如下:
六、RecyclerView數據添加刪除處理
講了以上RecyclerView中各種用戶,處理之后,現在我們來看一下當數據發生變化之后的處理。例如我們在使用ListView的時候,當數據發生變化的時候可以通過notifyDatasetChange()來刷新界面。
對于RecyclerView控件來講,給我們提供更加高級的使用方法notifyItemInserted(position)和notifyItemRemoved(position)
我們可以在TestRecyclerAdapter中添加數據新增和數據刪除的方法如下:
//添加數據
public void addItem(String data, int position) {
mTitles.add(position, data);
notifyItemInserted(position);
}
//刪除數據
public void removeItem(String data) {
int position = mTitles.indexOf(data);
mTitles.remove(position);
notifyItemRemoved(position);
}
- 然后我們在Activity中進行調用即可:
//添加數據
mAdapter.addItem("additem",5);
//刪除數據
mAdapter.removeItem("item4");
- 在運行之前我們不要忘記RecyclerView給提供了動畫設置,我這邊就直接采用了默認動畫,設置方法如下:
//添加默認的動畫效果
recyclerView_one.setItemAnimator(new DefaultItemAnimator());
-
最終運行效果大致如下圖:
七、RecyclerView總結
到此為止就完成我們RecyclerView控件使用的第一講內容,其中包括控件的基本介紹,基本使用,高級用法(自定義間隔符,加入點擊監聽事件以及Item添加刪除動畫處理)。總體來講RecyclerView控件是非常不錯,尤其在布局以及數據綁定,動畫方面,除了系統內置的三種布局方式之外,我們還可以定制出我們自己的布局管理器。同時當item數據發生變化的時候還給我們提供非常炫的效果。相信大家在今天這一講之后,會越來越愛上RecyclerView控件的使用,從此可以拋棄ListView和GridView啦.
本次具體實例注釋過的全部代碼已經上傳到FastDev4Android項目中了。同時歡迎大家去Github站點進行clone或者下載瀏覽:
https://github.com/jiangqqlmj/FastDev4Android 同時歡迎大家star和fork整個開源快速開發框架項目~下一講我們會通過一個具體實例來自定義實現一個廣告條控件實例。-
本人錄制AA(Android Annotations)注入框架的視頻教程已經上線了,歡迎大家前往觀看。http://www.cniao5.com/course/10074
再次聲明:本文轉載自:【江清清的博客】http://blog.csdn.net/developer_jiangqq/article/details/49927631**