RecyclerView 教程,全面瞭解各種使用方法
RecyclerView 在2014年就已經出來了,15年的時候有了解一下,但是專案中一直沒用上,最近看到,發現RecyclerView 出現了很多拓展,它的出現就是為了代替ListView、GridView。所以介紹一下RecyclerView該如何使用,及梳理一下這些拓展應該怎麼用,是個什麼效果。
RecyclerView
RecyclerView 比 ListView 更高階且更具靈活性。 它是一個用於顯示龐大資料集的容器,可通過保持有限數量的檢視進行非常有效的滾動操作。 如果您有資料集合,其中的元素將因使用者操作或網路事件而在執行時發生改變,請使用 RecyclerView 。
從它類名上看,RecyclerView代表的意義是,我只管Recycler View,也就是說RecyclerView只管回收與複用View,其他的你可以自己去設定。可以看出其高度的解耦,給予你充分的定製自由(所以你才可以輕鬆的通過這個控制元件實現ListView,GirdView,瀑布流等效果)。
在ListView中 改變列表某一個item資料,然後重新整理列表,會回到最頂部,而RecyclerView可以保持原來滑動的位置不變。
要實現一個RecyclerView,會接觸到它的幾個小夥伴,其中1、2是必須的。剩下的3、4、5三項,可以讓RecyclerView更好看、效果更好。
想要控制其item們的排列方式,請使用佈局管理器LayoutManager
如果要建立一個介面卡,請使用RecyclerView.Adapter
想要控制Item間的間隔,請使用RecyclerView.ItemDecoration
想要控制Item增刪的動畫,請使用RecyclerView.ItemAnimator
CardView 擴充套件 FrameLayout 類並讓您能夠顯示卡片內的資訊,這些資訊在整個平臺中擁有一致的呈現方式。CardView 小部件可擁有陰影和圓角。
如果要使用 RecyclerView 小部件,必須指定一個Adapter和一個LayoutManager。
在下面會詳解這些類的使用
非常簡單的一個示例
這裡先給出一個簡單的例子,感受一下怎麼樣使用RecyclerView
1、首先要用這個控制元件,你需要在gradle檔案中新增包的引用
compile 'com.android.support:cardview-v7:21.0.3'
compile 'com.android.support:recyclerview-v7:21.0.3'
2、 然後是在XML檔案用使用RecyclerView
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler_view"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"/>
3、在Activity中設定它
public class MainActivity extends ActionBarActivity {
@InjectView(R.id.recycler_view)
RecyclerView mRecyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));//這裡用線性顯示 類似於listview
// mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));//這裡用線性宮格顯示 類似於grid view
// mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));//這裡用線性宮格顯示 類似於瀑布流
mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
4、Adapter 的Item的xml程式碼,使用CardView
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:id="@+id/cv_item"
card_view:cardCornerRadius="4dp">
<TextView
android:id="@+id/text_view"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v7.widget.CardView>
5、然後是介面卡程式碼
public class NormalRecyclerViewAdapter extends RecyclerView.Adapter<NormalRecyclerViewAdapter.NormalTextViewHolder> {
private final LayoutInflater mLayoutInflater;
private final Context mContext;
private String[] mTitles;
public NormalRecyclerViewAdapter(Context context) {
mTitles = context.getResources().getStringArray(R.array.titles);
mContext = context;
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public NormalTextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new NormalTextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
}
@Override
public void onBindViewHolder(NormalTextViewHolder holder, int position) {
holder.mTextView.setText(mTitles[position]);
}
@Override
public int getItemCount() {
return mTitles == null ? 0 : mTitles.length;
}
public static class NormalTextViewHolder extends RecyclerView.ViewHolder {
@InjectView(R.id.text_view)
TextView mTextView;
NormalTextViewHolder(View view) {
super(view);
ButterKnife.inject(this, view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("NormalTextViewHolder", "onClick--> position = " + getPosition());
}
});
}
}
}
至此一個簡單的RecyclerView就這樣很乖巧的趴在螢幕上,有圖有真相:
LinearLayoutManager樣式:
GridLayoutManager樣式:
下面來圍繞這個示例,對RecyclerView的其他小夥伴進行介紹:
二、LayoutManager
佈局管理器,通過設定不同的佈局管理器,來控制這些Item的排列方式。
RecyclerView提供的佈局管理器:
- LinearLayoutManager 以垂直或水平滾動列表方式顯示專案。
- GridLayoutManager 在網格中顯示專案。
- StaggeredGridLayoutManager 在分散對齊網格中顯示專案。
使用這個函式來設定mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
三、Adapter
給recycleView提供資料的類,使用方法如下
mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this));
可以看到資料介面卡與BaseAdapter比較發生了相當大的變化,主要有3個方法:
getItemCount() 獲取總的條目數
onCreateViewHolder() 建立ViewHolder
onBindViewHolder() 將資料繫結至ViewHolder
可見,RecyclerView對ViewHolder也進行了一定的封裝,但是如果你仔細觀察,你會發出一個疑問,ListView裡面有個getView,它返回View為Item的佈局,那麼RecyclerView這個Item的樣子在哪控制?
其實是這樣的,我們建立的ViewHolder必須繼承RecyclerView.ViewHolder,這個RecyclerView.ViewHolder構造時必須傳入一個View,這個View相當於我們ListView getView中的convertView (即:inflate的item佈局需要傳入)。
還有一點,ListView中convertView是複用的,在RecyclerView中,是把ViewHolder類作為快取的單位了,然後convertView作為ViewHolder的成員變數保持在ViewHolder中,也就是說,假設螢幕顯示10個條目,則會建立10個ViewHolder快取起來,每次複用的是ViewHolder,所以他把getView這個方法變為了onCreateViewHolder。
可以對Adapter進行一些個性化操作,實現不同的功能,下面給出兩個示例:
瀑布式佈局
ok,接下來準備看大招,如果讓你去實現個瀑布流,最起碼不是那麼隨意就可以實現的吧?但是,如果使用RecyclerView,分分鐘的事。
那麼如何實現?還是使用StaggeredGridLayoutManage,只需要在Adapter的onBindViewHolder()為我們的item設定個隨機的高度,下面僅給出Adapter的程式碼(下面會給出全部工程程式碼):
public class WaterpallStaggeredAdapter extends
RecyclerView.Adapter<WaterpallStaggeredAdapter.MyViewHolder> {
private List<String> mDatas;
private LayoutInflater mInflater;
private List<Integer> mHeights;
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
private OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
public WaterpallStaggeredAdapter(Context context, List<String> datas) {
mInflater = LayoutInflater.from(context);
mDatas = datas;
mHeights = new ArrayList<Integer>();
for (int i = 0; i < mDatas.size(); i++) {
mHeights.add((int) (100 + Math.random() * 300));
}
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder holder = new MyViewHolder(mInflater.inflate(
R.layout.item_staggered_home, parent, false));
return holder;
}
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
LayoutParams lp = holder.tv.getLayoutParams();
lp.height = mHeights.get(position);
holder.tv.setLayoutParams(lp);
holder.tv.setText(mDatas.get(position));
// 如果設定了回撥,則設定點選事件
if (mOnItemClickListener != null) {
holder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getLayoutPosition();
mOnItemClickListener.onItemClick(holder.itemView, pos);
}
});
holder.itemView.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = holder.getLayoutPosition();
mOnItemClickListener.onItemLongClick(holder.itemView, pos);
removeData(pos);
return false;
}
});
}
}
@Override
public int getItemCount() {
return mDatas.size();
}
public void addData(int position) {
mDatas.add(position, "Insert One");
mHeights.add((int) (100 + Math.random() * 300));
notifyItemInserted(position);
}
public void removeData(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
class MyViewHolder extends ViewHolder {
TextView tv;
public MyViewHolder(View view) {
super(view);
tv = (TextView) view.findViewById(R.id.id_num);
}
}
}
看下效果圖:
CursorAdapter(使用LoaderManager和CursorLoader實現)
如果你用RecyclerView,你會發現CursorAdapter這個類沒有了,既然沒有了,那我們就自己仿照著ListView的CursorAdapter類來實現,具體的程式碼沒什麼大的出入,無非就是註冊兩個觀察者去監聽資料庫資料的變化。
在recycleView中實現CursorAdapter,有兩個地方需要注意一下,一個就是hasStableIds() 這個方法RecyclerView.Adapter中不能複寫父類的方法,需要在初始化的時候呼叫setHasStableIds(true); 來完成相同功能,第二個就是notifyDataSetInvalidated() 這個方法沒有,統一修改成notifyDataSetChanged() 方法即可。
關於RecyclerView的Adapter 各種解決方案
四、ItemDecoration 新增分割線
使用方法:
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,
DividerItemDecoration.VERTICAL_LIST));
ItemDecoration類很好的實現了RecyclerView新增分割線(當使用LayoutManager為LinearLayoutManager時)。 該實現類可以看到通過讀取系統主題中的 Android.R.attr.listDivider作為Item間的分割線,並且支援橫向和縱向。
該分割線是系統預設的,你可以在theme.xml中找到該屬性的使用情況。
在styles.xml找使用的android:listDivider的xml——shape_divider,你也可以對其進行自定義
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:listDivider">@drawable/shape_divider</item>
</style>
</resources>
下面給出DividerItemDecoration的程式碼
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
來看看使用分割線和沒有使用分割線的區別:
五、ItemAnimator
刪除和新增的動畫效果
ItemAnimator也是一個抽象類,好在系統為我們提供了一種預設的實現類
藉助預設的實現,當Item新增和移除的時候,新增動畫效果很簡單:
// 設定item動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
系統為我們提供了一個預設的實現,我們為我們的瀑布流新增以上一行程式碼,效果為:
如果是GridLayoutManager呢?動畫效果為:
六、CardView
使用方法:在item的xml佈局檔案中,直接使用CardView即可。
CardView 擴充套件 FrameLayout 類並讓您能夠顯示卡片內的資訊,這些資訊在整個平臺中擁有一致的呈現方式。CardView 小部件可擁有陰影和圓角。
如果要使用陰影建立卡片,請使用 card_view:cardElevation 屬性。CardView 在 Android 5.0(API 級別 21)及更高版本中使用真實高度與動態陰影,而在早期的 Android 版本中則返回程式設計陰影實現。
使用這些屬性自定義 CardView 小部件的外觀:
- 如果要在您的佈局中設定圓角半徑,請使用 card_view:cardCornerRadius 屬性。
- 如果要在您的程式碼中設定圓角半徑,請使用 CardView.setRadius 方法。
- 如果要設定卡片的背景顏色,請使用 card_view:cardBackgroundColor 屬性。
CardView示例圖:
使用CardView很簡單,只需要在xml,建立CardView佈局,見上面示例的第4點,然後在adapter中使用這個xml即可。
原始碼:
參考:
上面圖片都是來自,參考部落格中,給出的程式碼實現和圖片有些許差異。
關注我的公眾號,輕鬆瞭解和學習更多技術