RecyclerView封裝Adapter之添加頭部和底部視圖
轉載請標明出處:
http://blog.csdn.net/lisdye2/article/details/52674879
本文出自:【Alex_MaHao的博客】
項目中的源碼已經共享到github,有需要者請移步【Alex_MaHao的github】
在ListView
中已經自帶了添加頭佈局和添加底部佈局的方法,但是在RecyclerView
中,卻沒有默認實現,這導致在實現一些特殊佈局中不是那麼的方便,本篇博客將通過封裝Adapter
方式添加頭部和底部佈局。
首先看一下實現效果
在閱讀本篇博客之前,推薦閱讀上一篇博客 RecyclerView 封裝Adapter之BaseRecyclerAdapter
理論分析
在實現的過程中,我們首先想到的便是複用的問題,如果對於ListView
和RecylerView
掌握的還可的話,知道在一個條目顯示時候,會複用之前已經隱藏的條目。如果添加頭部或者底部的視圖,他的樣式肯定適合我們普通顯示的條目是不同的,這時候,複用勢必會拋出錯誤,那麼如何解決呢?
細心的同學會發現,在創建RecyclerView.Adapter
時,其中的一個方法有些特別public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
,第一個參數是父控件,第二個參數,從參數的命名山可以理解爲視圖的類型,關鍵便就在此,我們可以根據viewType
的值不同,構造不同的ViewHoder
。 既然能夠獲取,肯定有指定的地方,繼續尋找,發現了下面的方法public int getItemViewType(int position)
,根據索引值返回對應條目的類型。那麼,一切都捋順了。思路如下:
- 根據
public int getItemViewType(int position)
,定義頭部,底部,正常條目的常量,根據索引值進行返回。 - 在
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
中,通過viewType
的不同值創建不同的ViewHolder
.
代碼實現
定義變量,添加頭部佈局和底部佈局
首先,從簡單的開始實現,如下
public abstract class BaseRecycleAdapter<T> extends RecyclerView.Adapter<BaseRecycleAdapter.BaseViewHolder> {
// 頭部控件
private View mHeaderView;
// 底部控件
private View mFooterView;
private boolean isHasHeader = false;
private boolean isHasFooter = false;
/**
* 添加頭部視圖
* @param header
*/
public void setHeaderView(View header){
this.mHeaderView = header;
isHasHeader = true;
notifyDataSetChanged();
}
/**
* 添加底部視圖
* @param footer
*/
public void setFooterView(View footer){
this.mFooterView = footer;
isHasFooter = true;
notifyDataSetChanged();
}
}
上面的代碼都很簡單,設置頭部和底部佈局,修改對應的標誌。
定義不同的視圖類型,用以確定viewType的值
// item 的三種類型
public static final int ITEM_TYPE_NORMAL = 0X1111; // 正常的item類型
public static final int ITEM_TYPE_HEADER = 0X1112; // header
public static final int ITEM_TYPE_FOOTER = 0X1113; // footer
定義了三種類型,因爲對於item
條目的類型,無非正常的,頭部,底部三種,當然如果有特殊需求,自行添加。
在getItemViewType()中,根據索引值,返回不同的佈局類型
@Override
public int getItemViewType(int position) {
// 根據索引獲取當前View的類型,以達到複用的目的
// 根據位置的索引,獲取當前position的類型
if(isHasHeader&&position==0){
return ITEM_TYPE_HEADER;
}
if(isHasHeader&&isHasFooter&&position==datas.size()+1){
// 有頭部和底部時,最後底部的應該等於size+!
return ITEM_TYPE_FOOTER;
}else if(!isHasHeader&&isHasFooter&&position==datas.size()){
// 沒有頭部,有底部,底部索引爲size
return ITEM_TYPE_FOOTER;
}
return ITEM_TYPE_NORMAL;
}
在onCreateViewHolder()中,根據viewType
類型構造不同的ViewHolder
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType==ITEM_TYPE_FOOTER){
// 如果是底部類型,返回底部視圖
return new BaseViewHolder(mFooterView);
}
if(viewType==ITEM_TYPE_HEADER){
return new BaseViewHolder(mHeaderView);
}
View view = LayoutInflater.from(parent.getContext()).inflate(getLayoutId(),parent,false);
return new BaseViewHolder(view);
}
因爲設置了頭部佈局或者是底部佈局,大小要發生不同的變化
@Override
public int getItemCount() {
int size = datas.size();
if(isHasFooter)
size ++;
if(isHasHeader)
size++;
return size;
}
最後,綁定數據方法的定義
如果看了上一篇博客,會明白通過bindData()
方法,將數據的綁定交給了子類,同樣,此時也是如此,不過我們需要添加一些基本的判斷。
@Override
public void onBindViewHolder(BaseRecycleAdapter.BaseViewHolder holder, final int position) {
if(isHasHeader&&isHasFooter){
// 有頭佈局和底部時,向前便宜一個,且最後一個不能綁定數據
if(position==0 ||position==datas.size()+1){
return;
}
bindData(holder,position-1);
}
if(position!=0&&isHasHeader&&!isHasFooter){
// 有頂部,但沒有底部
bindData(holder,position-1);
}
if(!isHasHeader&&isHasFooter){
// 沒有頂部,但有底部
if(position==datas.size()){
return;
}
bindData(holder,position);
}
if(!isHasHeader&&!isHasFooter){
// 沒有頂部,沒有底部
bindData(holder,position);
}
}
基本的邏輯,當有頭部時,基本的向子類的偏移量要+1,有底部佈局,則最後一個position
不會回調。
使用方式
public class PersonActivity extends AppCompatActivity {
private RecyclerView mRecycler;
private ArrayList<Person> mPersons;
private PersonAdapter mAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_person);
mRecycler = ((RecyclerView) findViewById(R.id.reycler));
LinearLayoutManager manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mRecycler.setLayoutManager(manager);
mPersons = new ArrayList<>();
for(int i = 0 ;i <50;i++){
mPersons.add(new Person(i+""));
}
mAdapter = new PersonAdapter(mPersons);
mRecycler.setAdapter(mAdapter);
// 設置頭部佈局
mAdapter.setHeaderView(createView(Color.BLUE,"HEADER"));
// 設置底部佈局
mAdapter.setFooterView(createView(Color.RED,"FOOTER"));
}
/**
* 創建一個簡單的佈局
* @param color
* @param str
* @return
*/
public View createView(int color,String str){
TextView text = new TextView(this);
RecyclerView.LayoutParams params = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,100);
text.setLayoutParams(params);
text.setBackgroundColor(color);
text.setText(str);
text.setGravity(Gravity.CENTER);
return text;
}
}
BaseRecycleAdapter的源碼
public abstract class BaseRecycleAdapter<T> extends RecyclerView.Adapter<BaseRecycleAdapter.BaseViewHolder> {
protected List<T> datas;
public BaseRecycleAdapter(List<T> datas) {
this.datas = datas;
}
// 頭部控件
private View mHeaderView;
// 底部控件
private View mFooterView;
// item 的三種類型
public static final int ITEM_TYPE_NORMAL = 0X1111; // 正常的item類型
public static final int ITEM_TYPE_HEADER = 0X1112; // header
public static final int ITEM_TYPE_FOOTER = 0X1113; // footer
private boolean isHasHeader = false;
private boolean isHasFooter = false;
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType==ITEM_TYPE_FOOTER){
// 如果是底部類型,返回底部視圖
return new BaseViewHolder(mFooterView);
}
if(viewType==ITEM_TYPE_HEADER){
return new BaseViewHolder(mHeaderView);
}
View view = LayoutInflater.from(parent.getContext()).inflate(getLayoutId(),parent,false);
return new BaseViewHolder(view);
}
@Override
public void onBindViewHolder(BaseRecycleAdapter.BaseViewHolder holder, final int position) {
if(isHasHeader&&isHasFooter){
// 有頭佈局和底部時,向前便宜一個,且最後一個不能綁定數據
if(position==0 ||position==datas.size()+1){
return;
}
bindData(holder,position-1);
}
if(position!=0&&isHasHeader&&!isHasFooter){
// 有頂部,但沒有底部
bindData(holder,position-1);
}
if(!isHasHeader&&isHasFooter){
// 沒有頂部,但有底部
if(position==datas.size()){
return;
}
bindData(holder,position);
}
if(!isHasHeader&&!isHasFooter){
// 沒有頂部,沒有底部
bindData(holder,position);
}
}
/**
* 添加頭部視圖
* @param header
*/
public void setHeaderView(View header){
this.mHeaderView = header;
isHasHeader = true;
notifyDataSetChanged();
}
/**
* 添加底部視圖
* @param footer
*/
public void setFooterView(View footer){
this.mFooterView = footer;
isHasFooter = true;
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
// 根據索引獲取當前View的類型,以達到複用的目的
// 根據位置的索引,獲取當前position的類型
if(isHasHeader&&position==0){
return ITEM_TYPE_HEADER;
}
if(isHasHeader&&isHasFooter&&position==datas.size()+1){
// 有頭部和底部時,最後底部的應該等於size+!
return ITEM_TYPE_FOOTER;
}else if(!isHasHeader&&isHasFooter&&position==datas.size()){
// 沒有頭部,有底部,底部索引爲size
return ITEM_TYPE_FOOTER;
}
return ITEM_TYPE_NORMAL;
}
/**
* 刷新數據
* @param datas
*/
public void refresh(List<T> datas){
this.datas.clear();
this.datas.addAll(datas);
notifyDataSetChanged();
}
/**
* 添加數據
* @param datas
*/
public void addData(List<T> datas){
this.datas.addAll(datas);
notifyDataSetChanged();
}
/**
* 綁定數據
* @param holder 具體的viewHolder
* @param position 對應的索引
*/
protected abstract void bindData(BaseViewHolder holder, int position);
@Override
public int getItemCount() {
int size = datas.size();
if(isHasFooter)
size ++;
if(isHasHeader)
size++;
return size;
}
/**
* 封裝ViewHolder ,子類可以直接使用
*/
public class BaseViewHolder extends RecyclerView.ViewHolder{
private Map<Integer, View> mViewMap;
public BaseViewHolder(View itemView) {
super(itemView);
mViewMap = new HashMap<>();
}
/**
* 獲取設置的view
* @param id
* @return
*/
public View getView(int id) {
View view = mViewMap.get(id);
if (view == null) {
view = itemView.findViewById(id);
mViewMap.put(id, view);
}
return view;
}
}
/**
* 獲取子item
* @return
*/
public abstract int getLayoutId();
/**
* 設置文本屬性
* @param view
* @param text
*/
public void setItemText(View view,String text){
if(view instanceof TextView){
((TextView) view).setText(text);
}
}
}