學(xué)習(xí)資料:
- 鴻洋大神為RecyclerView打造通用Adapter讓RecyclerView更加好用
- 鴻洋大神Android優(yōu)雅的為RecyclerView添加HeaderView和FooterView
之前使用RecyclerView.Adapter
鸟蜡,基本就類似套用公式,死步驟,對Adapter
感到既熟悉又陌生后室。從去年我開始接觸學(xué)習(xí)Android
之時,RecyclerView
已經(jīng)開始大量被運用举庶,逐步取代ListView
琳水。遂借嗽,正好琳轿,那就先直接學(xué)習(xí)RecyclerView.Adapter
相關(guān)知識
1. RecyclerView.Adapter適配器
RecyclerView.Adapter
判沟,一個抽象類,并支持泛型
public static abstract class Adapter<VH extends ViewHolder> {
...
}
定義一個MyRecyclerViewAdapter
繼承RecyclerView.Adapter
后利赋,Android Stuido
提醒需要重寫3個方法,在重寫3
個方法前猩系,一般會先定義一個Holder
繼承RecycelrView.ViewHolder
媚送,之后直接在MyRecyclerViewAdapter
上,指定泛型就是RecyclerHolder
3個需要必須重寫的方法:
方法1:public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
方法2:public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
方法3:public int getItemCount()
在指定了泛型為RecyclerHoler
后寇甸,方法2
也會根據(jù)泛型改變onBindViewHolder(RecyclerHolder holder, int position)
1.1 onCreateViewHolder(ViewGroup parent, int viewType)創(chuàng)建Holder
源碼:
/**
* Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent an item.
*
* @param parent The ViewGroup into which the new View will be added after it is bound to an adapter position.
* @param viewType The view type of the new View.
*
* @return A new ViewHolder that holds a View of the given view type.
*/
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
-
ViewGroup parent:可以簡單理解為
item
的根ViewGroup
塘偎,item
的子控件加載在其中 -
int viewType:
item
的類型,可以根據(jù)viewType
來創(chuàng)建不同的ViewHolder
拿霉,來加載不同的類型的item
這個方法就是用來創(chuàng)建出一個新的ViewHolder
吟秩,可以根據(jù)需求的itemType
,創(chuàng)建出多個ViewHolder
绽淘。創(chuàng)建多個itemType
時涵防,需要getItemViewType(int position)
方法配合
1.2 onBindViewHolder(RecyclerHolder holder, int position)綁定ViewHolder
源碼:
**
*Called by RecyclerView to display the data at the specified position.
*This method should update the contents of the {@link ViewHolder#itemView} to reflect the item at the given position.
*
*@param holder The ViewHolder which should be updated to represent the contents of the item at the given position in the data set.
*@param position The position of the item within the adapter's data set.
*/
public abstract void onBindViewHolder(VH holder, int position);
-
VH holder:就是在
onCreateViewHolder()
方法中,創(chuàng)建的ViewHolder
-
int position:
item
對應(yīng)的DataList
數(shù)據(jù)源集合的postion
postion
就是adapter position
沪铭,RecycelrView
中item
的數(shù)量壮池,就是根據(jù)DataList
數(shù)據(jù)源集合的數(shù)量來創(chuàng)建的
1.3 getItemCount()獲取Item的數(shù)目
源碼:
/**
* Returns the total number of items in the data set held by the adapter.
*
* @return The total number of items in this adapter.
*/
public abstract int getItemCount();
這個方法的返回值,便是RecyclerView
中實際item
的數(shù)量杀怠。有些情況下椰憋,當(dāng)增加了HeaderView
或者FooterView
后,需要注意考慮這個返回值
1.4 簡單shi yong
一個最簡單的RecyclerViewAdapter
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.RecyclerHolder> {
private Context mContext;
private List<String> dataList = new ArrayList<>();
public MyRecyclerViewAdapter(RecyclerView recyclerView) {
this.mContext = recyclerView.getContext();
}
public void setData(List<String> dataList) {
if (null != dataList) {
this.dataList.clear();
this.dataList.addAll(dataList);
notifyDataSetChanged();
}
}
@Override
public RecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout, parent, false);
return new RecyclerHolder(view);
}
@Override
public void onBindViewHolder(RecyclerHolder holder, int position) {
holder.textView.setText(dataList.get(position));
}
@Override
public int getItemCount() {
return dataList.size();
}
class RecyclerHolder extends RecyclerView.ViewHolder {
TextView textView;
private RecyclerHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.tv__id_item_layout);
}
}
}
我的個人習(xí)慣是單獨使用一個setData()
方法將DataList
傳遞進Adapter
赔退,看到網(wǎng)上有一些博客中會通過構(gòu)造方法傳遞橙依。我一般會在網(wǎng)絡(luò)請求前就初始化Adapter
,當(dāng)異步網(wǎng)絡(luò)請求拿到解析過的JSON
數(shù)據(jù)后硕旗,調(diào)用這個方法將數(shù)據(jù)加載進Adapter
窗骑,即使做了分頁,也可以比較方漆枚。但感覺這種方法終究會浪費一點性能
注意慧域,在 onCreateViewHolder()方法中:
View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout, parent, false);
inflate()
方法使用的是3個參數(shù)的方法。
1.4.1 問題
以前使用2個參數(shù)的方法inflate(@LayoutRes int resource, @Nullable ViewGroup root)
浪读,下面的形式
View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout,null);
遇到的一個問題
兩種方法昔榴,使用的是同一套布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv__id_item_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:textAllCaps="false"
android:textColor="@android:color/white"
android:textSize="20sp" />
</LinearLayout>
item
內(nèi)的TextView
的寬是match_parent
辛藻,但使用兩個參數(shù)的方法時,看起來卻是wrap_content
的效果
1.4.2嘗試從源碼中找問題
兩個參數(shù)的方法源碼
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
兩個參數(shù)的方法內(nèi)部調(diào)用了3個參數(shù)的方法互订,此時inflate(resourceId, null, false)
吱肌,root
為null
,attachToRoot
為false
最終來到了這里仰禽,只保留了部分代碼:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
...
View result = root;
...
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
...
if (root != null) {
...
params = root.generateLayoutParams(attrs);
...
}
...
rInflateChildren(parser, temp, attrs, true);
...
if (root == null || !attachToRoot) {
result = temp;
}
}
使用兩個參數(shù)的inflate()
方法氮墨,ViewGroup.LayoutParams params
最終為null
;而如果使用3個參數(shù)的方法吐葵,最終params = params = root.generateLayoutParams(attrs)
這里為了減少出現(xiàn)問題的出現(xiàn)规揪,就使用3個參數(shù)的方法inflate(R.layout.id_rv_item_layout, parent, false)
這里看源碼也就看了這小段一段,inflate()方法的完整過程還是比較復(fù)雜的温峭,比較淺顯的知道問題出在哪里后猛铅,沒有深挖
1.4 點擊事件
完整代碼:
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.RecyclerHolder> {
private Context mContext;
private List<String> dataList = new ArrayList<>();
private onRecyclerItemClickerListener mListener;
public MyRecyclerViewAdapter(RecyclerView recyclerView) {
this.mContext = recyclerView.getContext();
}
/**
* 增加點擊監(jiān)聽
*/
public void setItemListener(onRecyclerItemClickerListener mListener) {
this.mListener = mListener;
}
/**
* 設(shè)置數(shù)據(jù)源
*/
public void setData(List<String> dataList) {
if (null != dataList) {
this.dataList.clear();
this.dataList.addAll(dataList);
notifyDataSetChanged();
}
}
public List<String> getDataList() {
return dataList;
}
@Override
public RecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout, parent, false);
// View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout,null);
return new RecyclerHolder(view);
}
@Override
public void onBindViewHolder(RecyclerHolder holder, int position) {
holder.textView.setText(dataList.get(position));
holder.textView.setOnClickListener(getOnClickListener(position));
}
private View.OnClickListener getOnClickListener(final int position) {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != mListener && null != v) {
mListener.onRecyclerItemClick(v, dataList.get(position), position);
}
}
};
}
@Override
public int getItemCount() {
return dataList.size();
}
class RecyclerHolder extends RecyclerView.ViewHolder {
TextView textView;
private RecyclerHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.tv__id_item_layout);
}
}
/**
* 點擊監(jiān)聽回調(diào)接口
*/
public interface onRecyclerItemClickerListener {
void onRecyclerItemClick(View view, Object data, int position);
}
}
定義一個接口onRecyclerItemClickerListener
,這樣可以在Actiivty
設(shè)置監(jiān)聽對象凤藏,之后為TextView
設(shè)置點擊監(jiān)聽事件奸忽,在TextView
的點擊事件方法中,使用onRecyclerItemClick()
進行回調(diào)
在Activity中使用:
//設(shè)置點擊事件
adapter.setItemListener(new MyRecyclerViewAdapter.onRecyclerItemClickerListener() {
@Override
public void onRecyclerItemClick(View view, Object data, int position) {
String s = (String) data;
adapter.getDataList().set(position, s + "---->hi");
adapter.notifyItemChanged(position);
}
});
在監(jiān)控對象回調(diào)方法中揖庄,使用了notifyItemChanged(position)
來進行局部刷新
但這種方式會new
出一大堆View.OnClickListener
栗菜,還有一種思路是利用RecycelrView
的onTouchListener
和GestureDetector
手勢來進行設(shè)置,可以看看三種方式實現(xiàn)RecyclerView的Item點擊事件
1.5 一系列的notifyData方法
一共有10個方法
方法 | 作用 |
---|---|
notifyDataSetChanged() |
通知RecycelrView 進行全局刷新 |
notifyItemChanged(int position) |
通知RecycelrView 在adapter position 處局進行部刷新 |
notifyItemRemoved(int position) |
通知RecyclerView 移除在adapter position 處的item |
notifyItemMoved(int fromPosition, int toPosition) |
通知RecyclerView 移除從fromPosition 到toPosition 的item
|
notifyItemRangeRemoved(int positionStart, int itemCount) |
通知RecyclerView 移除從positionStart 開始的itemCount 個item
|
notifyItemChanged(int position, Object payload) |
通知RecyclerView 改變指定position 的item 的object
|
notifyItemRangeChanged(int positionStart,int itemCount) |
通知RecyclerView 從positionStart 開始改變itemCount 個item
|
notifyItemRangeChanged(int positionStart,int itemCount,Object payload) |
通知RecyclerView 從positionStart 開始改變itemCount 個item 的對象 |
notifyItemInserted(int position) |
通知RecyclerView 在position 處插入一個item
|
notifyItemRangeInserted(int positionStart, int itemCount) |
通知RecyclerView 從positionStart 開始插入itemCount 個item
|
有些情況下蹄梢,方法需要考慮組合使用疙筹,否則可能出現(xiàn)position
錯亂,例如
在Adapter中移除或者插入item
/**
* 移除指定Position的Item
*/
public void remove(int position) {
if (dataList.size() == 0) return;
dataList.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, dataList.size() - position);
}
//在Activity中使用 禁炒,設(shè)置點擊事件
adapter.setItemListener(new MyRecyclerViewAdapter.onRecyclerItemClickerListener() {
@Override
public void onRecyclerItemClick(View view, Object data, int position) {
adapter.remove(position);
// String s = (String) data;
// adapter.inserted(position,s+"---->hi");
}
});
//在指定位置插入一個item
public void inserted(int position, String s) {
if (dataList.size() == 0) return;
dataList.add(position, s);
notifyItemInserted(position);
notifyItemRangeChanged(position, dataList.size() - position);
}
notifyItemRemoved(position)
雖然通知移除了RecycelrView
在position
位置上的itemA
腌歉,但itemA
之后的一系列item
也需要進行改變,也需要通知RecyclerView
進行改變
但這兩個方法性能上都有問題齐苛,卡頓比較明顯翘盖,應(yīng)該會有更好的動態(tài)改變或者動態(tài)插入item
的方法,以后學(xué)到了再補充
1.5 簡易的封裝
通用的ViewHolder:
public class BaseViewHolder extends RecyclerView.ViewHolder {
private final SparseArray<View> sparseArray;
public BaseViewHolder(View itemView) {
super(itemView);
this.sparseArray = new SparseArray<>(8); //一般一個Item 不會超過8種控件
}
public <T extends View> T getView(int viewId) {
View view = sparseArray.get(viewId);
if (view == null) {
view = itemView.findViewById(viewId);
sparseArray.put(viewId, view);
}
return (T) view;
}
public BaseViewHolder setText(int viewId, String text) {
TextView tv = getView(viewId);
if (tv != null) {
tv.setText(text);
}
return this;
}
}
主要思路就是使用SparseArray<View>
將控件存起來
適配器:
public abstract class CommonBaseAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> {
protected List<T> data = new ArrayList<>();
protected int itemLayoutId;
protected Context mContext;
private onRecyclerItemClickerListener mListener;
public CommonBaseAdapter(RecyclerView rv, @LayoutRes int itemLayoutId) {
this.itemLayoutId = itemLayoutId;
this.mContext = rv.getContext();
}
public void setData(List<T> data) {
if (data != null) {
this.data.clear();
this.data.addAll(data);
notifyDataSetChanged();
}
}
/**
* 增加點擊監(jiān)聽
*/
public void setItemListener(onRecyclerItemClickerListener mListener) {
this.mListener = mListener;
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//這里使用3個參數(shù)的方法
View view = LayoutInflater.from(mContext).inflate(itemLayoutId, parent, false);
return new BaseViewHolder(view);
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
bindViewData(holder, data.get(position), position);
holder.itemView.setOnClickListener(getOnClickListener(position));
}
private View.OnClickListener getOnClickListener(final int position) {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mListener != null && v != null) {
mListener.onRecyclerItemClick(v, data.get(position), position);
}
}
};
}
@Override
public int getItemCount() {
return this.data.size();
}
public abstract void bindViewData(BaseViewHolder holder, T item, int position);
interface onRecyclerItemClickerListener {
void onRecyclerItemClick(View view, Object data, int position);
}
}
內(nèi)部提供一個抽象方法bindViewData()
凹蜂,子類重寫抽象方法來做一些具體的操作馍驯。
封裝的很簡單,但平常學(xué)習(xí)使用也能減少一些重復(fù)代碼玛痊。網(wǎng)上有很多強大的封裝汰瘫,可以再深入學(xué)習(xí)一下為RecyclerView打造通用Adapter讓RecyclerView更加好用
使用:
public class RecyclerViewAdapter extends CommonBaseAdapter<String> {
public RecyclerViewAdapter(RecyclerView rv, @LayoutRes int itemLayoutId, @IdRes int resId) {
super(rv, itemLayoutId);
}
@Override
public void bindViewData(BaseViewHolder holder, String item, int position) {
holder.setText(R.id.textViewId, item);
}
}
在Activity
中就可以進行使用
這個簡易的封裝并沒有對添加加載圖片的方法。加載圖片的方法一開始也我封裝在了這個CommonBaseAdapter
中擂煞,但后來發(fā)現(xiàn)直接封裝在這里并不是好的思路
圖片需要做的處理比較多混弥,而且主流的庫有3個,為了易于維護,還是將圖片的操作單獨再封裝在一個工具類中蝗拿,在CommonBaseAdapter
中使用操作圖片的工具類比較好
1.6 添加HeaderView和FooterViewiew
學(xué)的鴻洋大神的代碼和思路晾捏,涉及到了裝飾模式。還有一種添加方式是直接通過使用多種item
直接在現(xiàn)有的CommonBaseAdapter
來修改哀托,但感覺這種思路需要對CommonBaseAdapter
改動的代碼太多惦辛,點擊事件的position
也需要考慮,不如鴻洋大神的這種思路易于開發(fā)和維護
代碼:
public class HeaderAndFooterAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private CommonBaseAdapter mAdapter;
private static final int HEADER_VIEW_TYPE = 2 << 6;
private static final int FOOTER_VIEW_TYPE = 2 << 5;
private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
private SparseArrayCompat<View> mFooterViews = new SparseArrayCompat<>();
public HeaderAndFooterAdapter(CommonBaseAdapter mAdapter) {
this.mAdapter = mAdapter;
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (null != mHeaderViews.get(viewType)) {
return new HeaderAndFooterHolder(mHeaderViews.get(viewType));
} else if (null != mFooterViews.get(viewType)) {
return new HeaderAndFooterHolder(mFooterViews.get(viewType));
}
return mAdapter.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
if (isHeaderViewPosition(position)) return;
if (isFooterViewPosition(position)) return;
mAdapter.onBindViewHolder(holder, position - getHeaderViewCount());
}
@Override
public int getItemViewType(int position) {
if (isHeaderViewPosition(position)) {
return mHeaderViews.keyAt(position);
} else if (isFooterViewPosition(position)) {
return mFooterViews.keyAt(position-getHeaderViewCount()-getAdapterItemCount());
}
return mAdapter.getItemViewType(position - getHeaderViewCount());
}
@Override
public int getItemCount() {
return getHeaderViewCount() + getFooterViewCount() + getAdapterItemCount();
}
/**
* 加入HeaderView
*/
public void addHeaderView(View view) {
mHeaderViews.put(mHeaderViews.size() + HEADER_VIEW_TYPE, view);
}
/**
* 加入FooterView
*/
public void addFootView(View view) {
mFooterViews.put(mFooterViews.size() + FOOTER_VIEW_TYPE, view);
}
/**
* HeaderView 的數(shù)目
*/
public int getHeaderViewCount() {
return mHeaderViews.size();
}
/**
* FooterView 的數(shù)目
*/
public int getFooterViewCount() {
return mFooterViews.size();
}
/**
* 是不是HeaderView的Position
*/
private boolean isHeaderViewPosition(int position) {
return position < getHeaderViewCount();
}
/**
* 是不是FooterView的Position
*/
private boolean isFooterViewPosition(int position) {
return position >= getHeaderViewCount() + getAdapterItemCount();
}
/**
* 得到Adapter中Item的數(shù)目
*/
private int getAdapterItemCount() {
return mAdapter.getItemCount();
}
private class HeaderAndFooterHolder extends BaseViewHolder {
private HeaderAndFooterHolder(View itemView) {
super(itemView);
}
}
}
封裝的思路:
將add
進來的view
進行保存仓手,當(dāng)加載item
時胖齐,利用itemType
對view
進行類型判斷,如果是HeaderView
或者FooterView
就創(chuàng)建HeaderAndFooterHolder
嗽冒,然后綁定只是用來顯示并沒有對HeaderView
或者FooterView
做其他更多事件的處理
使用也比較方便:
//數(shù)據(jù)適配器
RecyclerViewAdapter adapter = new RecyclerViewAdapter(rv, R.layout.id_rv_item_layout, R.id.tv__id_item_layout);
//頭View適配器
HeaderAndFooterAdapter headerAndFooterAdapter = new HeaderAndFooterAdapter(adapter);
//HeaderView
TextView headerView = new TextView(this);
headerView.setBackgroundColor(Color.BLACK);
headerView.setTextColor(Color.WHITE);
headerView.setWidth(1080);
headerView.setTextSize(50);
headerView.setText("我是頭");
headerAndFooterAdapter.addHeaderView(headerView);
//設(shè)置適配器
rv.setAdapter(headerAndFooterAdapter);
//添加數(shù)據(jù)
addData(adapter);
RecyelrView
設(shè)置的適配器是headerAndFooterAdapter
呀伙,而添加數(shù)據(jù)使用的是adapter
。HeaderAndFooterAdapter
是支持添加多個HeaderView
的
1.6.1 GridLayoutManger和StaggeredGridLayoutManager跨列問題
上面添加HeaderView
時添坊,使用的LinerLayoutManager
剿另,當(dāng)使用GridLayoutManger
時,便會有問題
HeaderView不能單獨占據(jù)一行
加入針對GridLayoutManager跨列處理的代碼:
/**
*當(dāng)RecyelrView開始觀察Adapter會被回調(diào)
*/
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
mAdapter.onAttachedToRecyclerView(recyclerView);
final RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) manager;
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int viewType = getItemViewType(position);
//如果是HeaderView或者是FooterView帅腌,設(shè)置占據(jù)gridLayoutManager.getSpanCount()列
if (null != mHeaderViews.get(viewType) || null != mFooterViews.get(viewType)) {
return gridLayoutManager.getSpanCount();
}
return 1;
}
});
}
}
當(dāng)布局管理器為
GridLayouManger
時驰弄,對當(dāng)前要的添加的item
進行判斷麻汰,如果是HeaderView或者是FooterView速客,就進行跨列處理,單獨占據(jù)一行
加入針對StaggeredGridLayoutManager跨列處理的代碼:
/**
* 一個item通過adapter開始顯示會被回調(diào)
*/
@Override
public void onViewAttachedToWindow(BaseViewHolder holder) {
super.onViewAttachedToWindow(holder);
int position = holder.getLayoutPosition();
if (isHeaderViewPosition(position)||isFooterViewPosition(position)){
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (null != lp && lp instanceof StaggeredGridLayoutManager.LayoutParams){
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);//占滿一行
}
}
}
當(dāng)布局管理器為
StaggeredGridLayoutManager
時五鲫,對當(dāng)前要的添加的item
進行判斷溺职,如果是HeaderView或者是FooterView,就設(shè)置setFullSpan(true)
位喂,占滿一行
2. 最后
RecyclerView.Adapter
暫時大致就學(xué)習(xí)這些
本人很菜浪耘,有錯誤請指出
共勉 :)