上一篇 初識RecyclerView婚瓜,是RecylerView的入門篇腐泻,主要講解了什么是RecylerView,RecylerView的優(yōu)勢以及三種布局管理器的區(qū)別毁兆,我們對RecyclerVIew有了初步了解
本篇是Recylerview的進(jìn)階篇主到,我將一步步帶領(lǐng)大家為RecylerView添加HeaderView, FooterView, EmptyView, 以及完成對GloriousRecyclerView的封裝茶行,使我們的開發(fā)更加便捷
為RecyclerView添加HeaderView
參考ListView
我們使用ListView的時候知道,要為ListView添加HeaderView是非常方便的登钥,我們只需要調(diào)用ListView的
addHeaderView(View v)
方法即可畔师。
于是,果斷翻看RecyclerView的源碼
遺憾的是牧牢,我們并沒有找到添加HeaderView的方法看锉,退而求其次
從LayoutManager入手
我們找到在RecyclerView的內(nèi)部靜態(tài)抽象類LayoutManager中有addView()方法
由上一篇我們已經(jīng)知道,RecyclerView已經(jīng)實現(xiàn)了三種布局管理器结执,這里度陆,我們就用最簡單的LinearLayoutManager來嘗試下添加HeaderView
mLayoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
mLayoutManager.addView(LayoutInflater.from(this).inflate(R .layout.layout_header, null, false), 0);
不幸的是,在運行時爆出空指針異常
查看RecyclerView 7075行献幔,發(fā)現(xiàn)holder為空
而holder由7074行得來
final ViewHolder holder = getChildViewHolderInt(child);
繼續(xù)查看getChildViewHolderInt(child)方法
holder是由子View的LayoutParams得來
這個LayoutParams是RecyclerView內(nèi)部靜態(tài)內(nèi),里面包含了一個ViewHolder mViewHolder 成員變量
由于我們添加的HeaderView是普通的 View / ViewGroup 趾诗,所以并沒有什么ViewHolder, 于此蜡感,此路不通耶
從ViewGroup入手
本著不拋棄蹬蚁,不放棄的精神,讓我們來大開腦洞吧郑兴,由于RecyclerView是繼承自ViewGroup
的犀斋,我們知道ViewGroup有addView(View child, int index)
方法,那我們試試不妨
mLayoutManager = new LinearLayoutManager(this, orientation, false);
mRecyclerView.setLayoutManager(mLayoutManager);
View header = LayoutInflater.from(this).inflate(R .layout.layout_header, null, false);
mRecyclerView.addView(header, 0);
悲劇的是情连,在運行時依然爆出空指針異常
繼續(xù)查看源碼(這里我就不貼了叽粹,有興趣的可以自己翻看),發(fā)現(xiàn)依然是缺少ViewHolder的緣故却舀,由此看來虫几,這條路也不通了
從Adapter入手
思路
屢次受挫,確實是有點動搖軍心挽拔,仿佛看不到希望辆脸,但是有句話叫做
Nerver say Nerver
, 好吧,至少我們知道了一點螃诅,我們要想在RecylerView中添加任何子View啡氢,那么這個View必須要有ViewHolder
我們在上一篇中講到:RecyclerView的Adapter默認(rèn)要求使用ViewHolder ,嗯术裸,似乎我們找到了正道倘是。
我們創(chuàng)建RecyclerView的Adapter時,必須要實現(xiàn)三個方法
其中
onCreateViewHolder(ViewGroup parent, int viewType)
第二個參數(shù)袭艺,int viewType搀崭,由名字我們猜測是Item的類型,如果沒錯的話匹表,那么我們的Header和正常的Item就是兩種類型了门坷,那么,這個類型是怎么得來的呢袍镀?
查看源碼默蚌,叫我們參考getItemViewType(int)
方法:
public int getItemViewType(int position) {
return 0;
}
源碼里,傳入一個position,默認(rèn)返回0
這里我們得到了啟發(fā)苇羡,我們在自己的Adapter里:
實現(xiàn)
- 首先定義:
private int ITEM_TYPE_NORMAL = 0;
private int ITEM_TYPE_HEADER = 1;
getItemViewType()中绸吸,假如position傳入0,我們的返回值返回 ITEM_TYPE_HEADER设江,其他的position锦茁,我們返回 ITEM_TYPE_NORMAL,這樣就區(qū)分了viewType
onCreateViewHolder()中叉存,我們根據(jù)不同的viewType返回不同的ViewHolder
onBindViewHolder()中码俩,我們首先根據(jù)positon調(diào)用getItemViewType(int position)方法,得到不同的viewType歼捏,如果得到 ITEM_TYPE_HEADER 稿存,我們直接return,如果得到 ITEM_TYPE_NORMAL笨篷,那么,由于有Header的存在瓣履,我們在設(shè)置Item的數(shù)據(jù)時率翅,應(yīng)該把position -1
getItemCount()中,由于我們多了HeaderView袖迎,所以要在真實數(shù)據(jù)個數(shù)中+1
效果展示
為RecyclerView添加EmptyView, FooterView
為RecyclerView添加FooterView冕臭,EmptyView 其實和添加HeaderView是類似的,這里就不多言了燕锥,直接把同時添加Header辜贵,F(xiàn)ooter和Empty View的源碼貼上
public class DemoAdapter extends RecyclerView.
Adapter<RecyclerView.ViewHolder> {
private List<String> mDatas = new ArrayList<>();
private Context mContext;
private View mHeaderView;
private View mFooterView;
private View mEmptyView;
private int ITEM_TYPE_NORMAL = 0;
private int ITEM_TYPE_HEADER = 1;
private int ITEM_TYPE_FOOTER = 2;
private int ITEM_TYPE_EMPTY = 3;
public DemoAdapter(Context context) {
mContext = context;
}
public void setDatas(List<String> datas) {
mDatas = datas;
notifyDataSetChanged();
}
// 創(chuàng)建視圖
@Override
public RecyclerView.ViewHolder
onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_HEADER) {
return new ViewHolder(mHeaderView);
} else if (viewType == ITEM_TYPE_EMPTY) {
return new ViewHolder(mEmptyView);
} else if (viewType == ITEM_TYPE_FOOTER) {
return new ViewHolder(mFooterView);
} else {
View v = LayoutInflater.from(mContext)
.inflate(
R.layout.layout_recyclerview_item_view,
parent,
false);
return new ViewHolder(v);
}
}
@Override
public int getItemViewType(int position) {
if (null != mHeaderView && position == 0) {
return ITEM_TYPE_HEADER;
}
if (null != mFooterView
&& position == getItemCount() - 1) {
return ITEM_TYPE_FOOTER;
}
if (null != mEmptyView && mDatas.size() == 0){
return ITEM_TYPE_EMPTY;
}
return ITEM_TYPE_NORMAL;
}
// 為Item綁定數(shù)據(jù)
@Override
public void onBindViewHolder
(RecyclerView.ViewHolder holder, int position) {
int type = getItemViewType(position);
if (type == ITEM_TYPE_HEADER
|| type == ITEM_TYPE_FOOTER
|| type == ITEM_TYPE_EMPTY) {
return;
}
int realPos = getRealItemPosition(position);
((DemoAdapter.ViewHolder) holder)
.mTextView
.setText(mDatas.get(realPos));
}
private int getRealItemPosition(int position) {
if (null != mHeaderView) {
return position - 1;
}
return position;
}
@Override
public int getItemCount() {
int itemCount = mDatas.size();
if (null != mEmptyView && itemCount == 0) {
itemCount++;
}
if (null != mHeaderView) {
itemCount++;
}
if (null != mFooterView) {
itemCount++;
}
return itemCount;
}
public void addHeaderView(View view) {
mHeaderView = view;
notifyItemInserted(0);
}
public void addFooterView(View view) {
mFooterView = view;
notifyItemInserted(getItemCount() - 1);
}
public void setEmptyView(View view) {
mEmptyView = view;
notifyDataSetChanged();
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView mTextView;
ViewHolder(View v) {
super(v);
mTextView = (TextView)
v.findViewById(R.id.item_title);
}
}
}
Activity中
View footer = LayoutInflater.from(this).inflate(R.layout.layout_footer, mRecyclerView, false);
View header = LayoutInflater.from(this).inflate(R.layout.layout_header, mRecyclerView, false);
View empty = LayoutInflater.from(this).inflate(R.layout.layout_empty, mRecyclerView, false);
adapter.addHeaderView(header);
adapter.addFooterView(footer);
adapter.setEmptyView(empty);
下圖展示的是同時添加了Header和Footer View
下圖展示的是Empty View
封裝
其實上面的代碼在一般情況對添加
HeaderView ,FooterView ,Empty View
已經(jīng)夠用了,不過麻煩的是脯宿,我們在不同的地方念颈,需要重復(fù)Copy一些代碼,顯然连霉,這是不能容忍的
那么榴芳,我們能不能像ListView那樣 把什么addHeaderView()
,addFooterView()
,setEmptyView()
直接封裝在RecyclerView里呢?答案是肯定的跺撼!
技巧
這里我們用到了裝飾模式窟感,我們用自定義的RecyclerView
中的內(nèi)部類Adapter
來裝飾原始從Activity
傳入的Adapter
,我們可以毫無影響之前的邏輯來添加這些額外的Header,Footer,Empty View
實現(xiàn)
廢話不多說歉井,直接上源碼柿祈,拿走,不謝
GloriousRecyclerView
/*
* Copyright (C) 2017 CXP 277371483@qq.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xpc.recylerviewdemo;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* Created on 17-2-14
*
* @author cxp
*/
public class GloriousRecyclerView extends RecyclerView {
private View mHeaderView;
private View mFooterView;
private View mEmptyView;
private GloriousAdapter mGloriousAdapter;
public GloriousRecyclerView(Context context) {
super(context);
}
public GloriousRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public GloriousRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void addHeaderView(View view) {
mHeaderView = view;
mGloriousAdapter.notifyItemInserted(0);
}
public void addFooterView(View view) {
mFooterView = view;
mGloriousAdapter.notifyItemInserted(mGloriousAdapter.getItemCount() - 1);
}
public void setEmptyView(View view) {
mEmptyView = view;
mGloriousAdapter.notifyDataSetChanged();
}
@Override
public void setAdapter(Adapter adapter) {
if (adapter != null) {
mGloriousAdapter = new GloriousAdapter(adapter);
}
super.setAdapter(mGloriousAdapter);
}
private class GloriousAdapter extends RecyclerView.Adapter<ViewHolder> {
private Adapter mOriginalAdapter;
private int ITEM_TYPE_NORMAL = 0;
private int ITEM_TYPE_HEADER = 1;
private int ITEM_TYPE_FOOTER = 2;
private int ITEM_TYPE_EMPTY = 3;
//聰明的人會發(fā)現(xiàn)我們這里用了一個裝飾模式
public GloriousAdapter(Adapter originalAdapter) {
mOriginalAdapter = originalAdapter;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_HEADER) {
return new GloriousViewHolder(mHeaderView);
} else if (viewType == ITEM_TYPE_EMPTY) {
return new GloriousViewHolder(mEmptyView);
} else if (viewType == ITEM_TYPE_FOOTER) {
return new GloriousViewHolder(mFooterView);
} else {
return mOriginalAdapter.onCreateViewHolder(parent, viewType);
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
int type = getItemViewType(position);
if (type == ITEM_TYPE_HEADER || type == ITEM_TYPE_FOOTER || type == ITEM_TYPE_EMPTY) {
return;
}
int realPosition = getRealItemPosition(position);
mOriginalAdapter.onBindViewHolder(holder, realPosition);
}
@Override
public int getItemCount() {
int itemCount = mOriginalAdapter.getItemCount();
//加上其他各種View
if (null != mEmptyView && itemCount == 0) itemCount++;
if (null != mHeaderView) itemCount++;
if (null != mFooterView) itemCount++;
return itemCount;
}
@Override
public int getItemViewType(int position) {
if (null != mHeaderView && position == 0) return ITEM_TYPE_HEADER;
if (null != mFooterView && position == getItemCount() - 1) return ITEM_TYPE_FOOTER;
if (null != mEmptyView && mOriginalAdapter.getItemCount() == 0) return ITEM_TYPE_EMPTY;
return ITEM_TYPE_NORMAL;
}
private int getRealItemPosition(int position) {
if (null != mHeaderView) {
return position - 1;
}
return position;
}
/**
* ViewHolder 是一個抽象類
*/
class GloriousViewHolder extends ViewHolder {
GloriousViewHolder(View itemView) {
super(itemView);
}
}
}
}
Activity
public class GloriousActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_glorious_recycler_view);
GloriousRecyclerView recyclerView = (GloriousRecyclerView) findViewById(R.id.recycler_view);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
recyclerView.setLayoutManager(layoutManager);
NormalAdapter adapter = new NormalAdapter(this);
adapter.setDatas(constructTestDatas());
View footer = LayoutInflater.from(this).inflate(R.layout.layout_footer, recyclerView, false);
View header = LayoutInflater.from(this).inflate(R.layout.layout_header, recyclerView, false);
View empty = LayoutInflater.from(this).inflate(R.layout.layout_empty, recyclerView, false);
recyclerView.setAdapter(adapter);
recyclerView.addHeaderView(header);
recyclerView.addFooterView(footer);
recyclerView.setEmptyView(empty);
}
private List<String> constructTestDatas() {
List<String> datas = new ArrayList<>();
datas.add("劉一");
//...
datas.add("鄭十");
return datas;
}
}
layout
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#888">
<com.xpc.recylerviewdemo.GloriousRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
總結(jié)
本章一步步帶領(lǐng)大家為RecylerView添加HeaderView,FooterView,EmptyView,以及完成了對GloriousRecyclerView的封裝哩至,使我們的開發(fā)更加的便捷躏嚎。
網(wǎng)上或許有類似的解決方案,但是都沒有講解為什么要這樣做菩貌,正所謂知其然不知其所以然卢佣。如果你認(rèn)真讀了這篇文章,相信會對你有所幫助箭阶。
上一篇
RecyclerView從入門到入神——初識RecyclerView(一)