MaterialDesign-RecyleView探究及使用【三】-添加頭部和底部

概述

1供嚎、目的:為RecyclerView添加頭部和底部視圖

2、分析:RecyclerView在使用的過程中峭状,沒有發(fā)現(xiàn)想ListView中addHeadView()克滴、addFooterView()的方法

3、實現(xiàn)思路:模仿ListView的方式實現(xiàn)优床,研究ListView的添加頭部和底部視圖的實現(xiàn)源碼劝赔,依照同樣的模式實現(xiàn)

ListView添加頭部和底部的功能源碼解析

1、查看一下ListView的addHeaderView()方法:

從如下的方法中查看關(guān)鍵信息胆敞,會看到mHeaderViewInfos.add(info);就是將傳入的參數(shù)view通過mHeaderViewInfos進行存儲着帽,存放要添加的headerview

接著看到一個判斷語句 if (!(mAdapter instanceof HeaderViewListAdapter)),這個判斷中有一個方法wrapHeaderListAdapterInternal();進去看到的是mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter);

意思是如果這個mAdapte不是HeaderViewListAdapter竿秆,那么就把它轉(zhuǎn)化成一個HeaderViewListAdapter启摄,同時把這個原本的mAdapter通過構(gòu)造方法傳遞到HeaderViewListAdapter這個類中稿壁,傳入adapter的方式肯定是setAdapter方法幽钢,接著看setAdapter

addHeaderView源碼:

publicvoidaddHeaderView(View v, Object data,booleanisSelectable){

if(v.getParent() !=null&& v.getParent() !=this) {

if(Log.isLoggable(TAG, Log.WARN)) {

Log.w(TAG,"The specified child already has a parent. "

+"You must call removeView() on the child's parent first.");

}

}

finalFixedViewInfo info =newFixedViewInfo();

info.view = v;

info.data = data;

info.isSelectable = isSelectable;

mHeaderViewInfos.add(info);

mAreAllItemsSelectable &= isSelectable;

// Wrap the adapter if it wasn't already wrapped.

if(mAdapter !=null) {

if(!(mAdapterinstanceofHeaderViewListAdapter)) {

wrapHeaderListAdapterInternal();

}

// In the case of re-adding a header view, or adding one later on,

// we need to notify the observer.

if(mDataSetObserver !=null) {

mDataSetObserver.onChanged();

}

}

}

protectedvoidwrapHeaderListAdapterInternal(){

mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter);

}

2、查看一下ListView的setAdapter()方法:

從這個方法看到如下關(guān)鍵代碼塊:

if(mHeaderViewInfos.size() >0|| mFooterViewInfos.size() >0) {

mAdapter= wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);

}else{

mAdapter= adapter;

}

就是說setAdapter會先去判斷是否你前面添加過headerView或者添加過footerView傅是,以此來判斷應(yīng)該給你轉(zhuǎn)化為一個HeaderViewListAdapter還是直接給你 mAdapter = adapter匪燕。

這里體現(xiàn)了偷梁換柱的概念,就是說你添加了headerView或者footerView就用一個新的Adapter來替代你傳入的adapter,這個在設(shè)計模式上使用了裝飾設(shè)計模式喧笔,接下來查看一下HeaderViewListAdapter

setAdapter源碼:

@Override

publicvoidsetAdapter(ListAdapter adapter){

if(mAdapter !=null&& mDataSetObserver !=null) {

mAdapter.unregisterDataSetObserver(mDataSetObserver);

}

resetList();

mRecycler.clear();

if(mHeaderViewInfos.size() >0|| mFooterViewInfos.size() >0) {

mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);

}else{

mAdapter = adapter;

}

mOldSelectedPosition = INVALID_POSITION;

mOldSelectedRowId = INVALID_ROW_ID;

// AbsListView#setAdapter will update choice mode states.

super.setAdapter(adapter);

if(mAdapter !=null) {

mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();

mOldItemCount = mItemCount;

mItemCount = mAdapter.getCount();

checkFocus();

mDataSetObserver =newAdapterDataSetObserver();

mAdapter.registerDataSetObserver(mDataSetObserver);

mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());

intposition;

if(mStackFromBottom) {

position = lookForSelectablePosition(mItemCount -1,false);

}else{

position = lookForSelectablePosition(0,true);

}

setSelectedPositionInt(position);

setNextSelectedPositionInt(position);

if(mItemCount ==0) {

// Nothing selected

checkSelectionChanged();

}

}else{

mAreAllItemsSelectable =true;

checkFocus();

// Nothing selected

checkSelectionChanged();

}

requestLayout();

}

3帽驯、查看HeaderViewListAdapter關(guān)鍵源碼:

分析看到getView()方法有三處判斷,分別是返回了三種視圖书闸,頭部視圖尼变、內(nèi)容視圖、底部視圖浆劲;發(fā)現(xiàn)頭部視圖和底部視圖專門處理嫌术,內(nèi)容視圖直接復(fù)用了傳入的adapter的getView()方法。

publicViewgetView(intposition, View convertView, ViewGroup parent){

// Header (negative positions will throw an IndexOutOfBoundsException)

intnumHeaders = getHeadersCount();

if(position < numHeaders) {

returnmHeaderViewInfos.get(position).view;

}

// Adapter

finalintadjPosition = position - numHeaders;

intadapterCount =0;

if(mAdapter !=null) {

adapterCount = mAdapter.getCount();

if(adjPosition < adapterCount) {

returnmAdapter.getView(adjPosition, convertView, parent);

}

}

// Footer (off-limits positions will throw an IndexOutOfBoundsException)

returnmFooterViewInfos.get(adjPosition - adapterCount).view;

}

publicintgetItemViewType(intposition){

intnumHeaders = getHeadersCount();

if(mAdapter !=null&& position >= numHeaders) {

intadjPosition = position - numHeaders;

intadapterCount = mAdapter.getCount();

if(adjPosition < adapterCount) {

returnmAdapter.getItemViewType(adjPosition);

}

}

returnAdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;

}

綜上分析牌借,要實現(xiàn)RecyleView添加頭部和底部功能度气,

1、需要自定義一個RecyleView膨报,添加addHeaerView()磷籍、addFooterView()

2适荣、其次自定義一個HeaderViewRecyleViewAdapter,用于實現(xiàn)添加頭部和底部視圖的展示

自定義RecyleView實現(xiàn)WrapRecyleView類:

publicclassWrapRecyleViewextendsRecyclerView{

privateArrayList mHeaderViewInfos =newArrayList<>();

privateArrayList? mFooterViewInfos =newArrayList<>();

privateAdapter mAdapter;

publicWrapRecyleView(Context context,AttributeSet attrs){

super(context, attrs);

}

publicvoidaddHeaderView(View v){

mHeaderViewInfos.add(v);

// Wrap the adapter if it wasn't already wrapped.

if(mAdapter !=null) {

if(!(mAdapterinstanceofHeaderViewRecyleViewAdapter)) {

mAdapter =newHeaderViewRecyleViewAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);

}

}

}

publicvoidaddFooterView(View v){

mFooterViewInfos.add(v);

// Wrap the adapter if it wasn't already wrapped.

if(mAdapter !=null) {

if(!(mAdapterinstanceofHeaderViewRecyleViewAdapter)) {

mAdapter =newHeaderViewRecyleViewAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);

}

}

}

@Override

publicvoidsetAdapter(Adapter adapter){

if(mHeaderViewInfos.size() >0|| mFooterViewInfos.size() >0) {

mAdapter =newHeaderViewRecyleViewAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);

}else{

mAdapter = adapter;

}

super.setAdapter(mAdapter);

}

}

HeaderViewRecyleViewAdapter類實現(xiàn):

publicclassHeaderViewRecyleViewAdapterextendsRecyclerView.Adapter{

privateArrayList mHeaderViewInfos;

privateArrayList? mFooterViewInfos;

privateRecyclerView.Adapter mAdapter;

publicHeaderViewRecyleViewAdapter(ArrayList headerViewInfos, ArrayList footerViewInfos, RecyclerView.Adapter adapter){

mAdapter = adapter;

if(headerViewInfos ==null) {

mHeaderViewInfos =newArrayList<>();

}else{

mHeaderViewInfos = headerViewInfos;

}

if(footerViewInfos ==null) {

mFooterViewInfos =newArrayList<>();

}else{

mFooterViewInfos = footerViewInfos;

}

}

@NonNull

@Override

publicRecyclerView.ViewHolderonCreateViewHolder(@NonNull ViewGroup parent,intviewType){

//header

if(viewType==RecyclerView.INVALID_TYPE){

returnnewHeaderViewHolder(mHeaderViewInfos.get(0));

}elseif(viewType==RecyclerView.INVALID_TYPE-1){//footer

returnnewHeaderViewHolder(mFooterViewInfos.get(0));

}

// Footer (off-limits positions will throw an IndexOutOfBoundsException)

returnmAdapter.onCreateViewHolder(parent, viewType);

}

@Override

publicvoidonBindViewHolder(@NonNull RecyclerView.ViewHolder holder,intposition){

//劃分頭部院领、正常部分弛矛、尾部

intnumHeaders = getHeadersCount();

//頭部

if(position < numHeaders) {

return;

}

//adapter bod正常部分

finalintadjPosition = position - numHeaders;

intadapterCount =0;

if(mAdapter !=null) {

adapterCount = mAdapter.getItemCount();

if(adjPosition < adapterCount) {

mAdapter.onBindViewHolder(holder, adjPosition);

return;

}

}

//footer

}

@Override

publicintgetItemCount(){

if(mAdapter !=null) {

returngetFootersCount() + getHeadersCount() + mAdapter.getItemCount();

}else{

returngetFootersCount() + getHeadersCount();

}

}

@Override

publicintgetItemViewType(intposition){

//判斷當(dāng)前條目是什么類型,就渲染什么視圖給什么數(shù)據(jù)

intnumHeaders = getHeadersCount();

//頭部

if(position < numHeaders) {

returnRecyclerView.INVALID_TYPE;

}

//正常條目部分

// Adapter

finalintadjPosition = position - numHeaders;

intadapterCount =0;

if(mAdapter !=null) {

adapterCount = mAdapter.getItemCount();

if(adjPosition < adapterCount) {

returnmAdapter.getItemViewType(adjPosition);

}

}

//footer部分

returnRecyclerView.INVALID_TYPE-1;

}

privatestaticclassHeaderViewHolderextendsRecyclerView.ViewHolder{

publicHeaderViewHolder(View view){

super(view);

}

}

publicintgetHeadersCount(){

returnmHeaderViewInfos.size();

}

publicintgetFootersCount(){

returnmFooterViewInfos.size();

}

}

接下來就是應(yīng)用栅盲,需要使用WrapRecyleView 來代替RecyleView 汪诉,其它都是正常的實現(xiàn)

setContentView(R.layout.activity_rvheader);

recyclerView = (WrapRecyleView) findViewById(R.id.recyclerView);

addHeaderView();

addFooterView();

List list =newArrayList<>();

for(inti =0; i <6; i++) {

list.add("item "+i);

}

MyAdapter adapter =newMyAdapter(list);

recyclerView.setLayoutManager(newLinearLayoutManager(this));

recyclerView.setAdapter(adapter);

效果圖如下:

效果圖如下:

源碼地址:https://github.com/heiyl/recyleview

微信公眾號:

圖注:Android進化之路公眾號

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谈秫,隨后出現(xiàn)的幾起案子扒寄,更是在濱河造成了極大的恐慌,老刑警劉巖拟烫,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件该编,死亡現(xiàn)場離奇詭異,居然都是意外死亡硕淑,警方通過查閱死者的電腦和手機课竣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來置媳,“玉大人于樟,你說我怎么就攤上這事∧茨遥” “怎么了迂曲?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寥袭。 經(jīng)常有香客問我路捧,道長,這世上最難降的妖魔是什么传黄? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任杰扫,我火速辦了婚禮,結(jié)果婚禮上膘掰,老公的妹妹穿的比我還像新娘章姓。我一直安慰自己,他們只是感情好识埋,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布凡伊。 她就那樣靜靜地躺著,像睡著了一般惭聂。 火紅的嫁衣襯著肌膚如雪窗声。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天辜纲,我揣著相機與錄音笨觅,去河邊找鬼拦耐。 笑死,一個胖子當(dāng)著我的面吹牛见剩,可吹牛的內(nèi)容都是我干的杀糯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼苍苞,長吁一口氣:“原來是場噩夢啊……” “哼固翰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起羹呵,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤骂际,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后冈欢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體歉铝,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年凑耻,在試婚紗的時候發(fā)現(xiàn)自己被綠了太示。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡香浩,死狀恐怖类缤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情邻吭,我是刑警寧澤餐弱,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站镜盯,受9級特大地震影響岸裙,放射性物質(zhì)發(fā)生泄漏猖败。R本人自食惡果不足惜速缆,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望恩闻。 院中可真熱鬧艺糜,春花似錦、人聲如沸幢尚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尉剩。三九已至真慢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間理茎,已是汗流浹背黑界。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工管嬉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人朗鸠。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓蚯撩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親烛占。 傳聞我的和親對象是個殘疾皇子胎挎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容