上一篇 初识RecyclerView,是RecylerView的入门篇,主要讲解了什么是RecylerView,RecylerView的优势以及三种布局管理器的区别,我们对RecyclerVIew有了初步了解
本篇是Recylerview的进阶篇,我将一步步带领大家为RecylerView添加HeaderView, FooterView, EmptyView, 以及完成对GloriousRecyclerView的封装,使我们的开发更加便捷
为RecyclerView添加HeaderView
参考ListView
我们使用ListView的时候知道,要为ListView添加HeaderView是非常方便的,我们只需要调用ListView的
addHeaderView(View v)
方法即可。
于是,果断翻看RecyclerView的源码
RecyclerView_SourceCode_1.png RecyclerView_SourceCode_2.png遗憾的是,我们并没有找到添加HeaderView的方法,退而求其次
从LayoutManager入手
我们找到在RecyclerView的内部静态抽象类LayoutManager中有addView()方法
RecyclerView_SourceCode_3.png由上一篇我们已经知道,RecyclerView已经实现了三种布局管理器,这里,我们就用最简单的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_Header_Error_1.png查看RecyclerView 7075行,发现holder为空
RecyclerView_SourceCode_4.png而holder由7074行得来
final ViewHolder holder = getChildViewHolderInt(child);
继续查看getChildViewHolderInt(child)方法
RecyclerView_SourceCode_5.pngholder是由子View的LayoutParams得来
这个LayoutParams是RecyclerView内部静态内,里面包含了一个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);
悲剧的是,在运行时依然爆出空指针异常
RecyclerView_Header_Error_2.png继续查看源码(这里我就不贴了,有兴趣的可以自己翻看),发现依然是缺少ViewHolder的缘故,由此看来,这条路也不通了
从Adapter入手
思路
屡次受挫,确实是有点动摇军心,仿佛看不到希望,但是有句话叫做
Nerver say Nerver
, 好吧,至少我们知道了一点,我们要想在RecylerView中添加任何子View,那么这个View必须要有ViewHolder
我们在上一篇中讲到:RecyclerView的Adapter默认要求使用ViewHolder ,嗯,似乎我们找到了正道。
我们创建RecyclerView的Adapter时,必须要实现三个方法
RecyclerView_SourceCode_6.png其中
onCreateViewHolder(ViewGroup parent, int viewType)
第二个参数,int viewType,由名字我们猜测是Item的类型,如果没错的话,那么我们的Header和正常的Item就是两种类型了,那么,这个类型是怎么得来的呢?
查看源码,叫我们参考getItemViewType(int)
方法:
public int getItemViewType(int position) {
return 0;
}
源码里,传入一个position,默认返回0
这里我们得到了启发,我们在自己的Adapter里:
实现
- 首先定义:
private int ITEM_TYPE_NORMAL = 0;
private int ITEM_TYPE_HEADER = 1;
-
getItemViewType()中,假如position传入0,我们的返回值返回 ITEM_TYPE_HEADER,其他的position,我们返回 ITEM_TYPE_NORMAL,这样就区分了viewType
-
onCreateViewHolder()中,我们根据不同的viewType返回不同的ViewHolder
-
onBindViewHolder()中,我们首先根据positon调用getItemViewType(int position)方法,得到不同的viewType,如果得到 ITEM_TYPE_HEADER ,我们直接return,如果得到 ITEM_TYPE_NORMAL,那么,由于有Header的存在,我们在设置Item的数据时,应该把position -1
-
getItemCount()中,由于我们多了HeaderView,所以要在真实数据个数中+1
效果展示
RecyclerView_With_Header.png为RecyclerView添加EmptyView, FooterView
为RecyclerView添加FooterView,EmptyView 其实和添加HeaderView是类似的,这里就不多言了,直接把同时添加Header,Footer和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();
}
// 创建视图
@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绑定数据
@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
RecyclerView_With_Header_Footer.png下图展示的是Empty View
RecyclerView_With_Empty.png封装
其实上面的代码在一般情况对添加
HeaderView ,FooterView ,Empty View
已经够用了,不过麻烦的是,我们在不同的地方,需要重复Copy一些代码,显然,这是不能容忍的
那么,我们能不能像ListView那样 把什么addHeaderView()
,addFooterView()
,setEmptyView()
直接封装在RecyclerView里呢?答案是肯定的!
技巧
这里我们用到了装饰模式,我们用自定义的RecyclerView
中的内部类Adapter
来装饰原始从Activity
传入的Adapter
,我们可以毫无影响之前的逻辑来添加这些额外的Header,Footer,Empty View
实现
废话不多说,直接上源码,拿走,不谢
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;
//聪明的人会发现我们这里用了一个装饰模式
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>
总结
本章一步步带领大家为RecylerView添加HeaderView,FooterView,EmptyView,以及完成了对GloriousRecyclerView的封装,使我们的开发更加的便捷。
网上或许有类似的解决方案,但是都没有讲解为什么要这样做,正所谓知其然不知其所以然。如果你认真读了这篇文章,相信会对你有所帮助。
上一篇
RecyclerView从入门到入神——初识RecyclerView(一)
网友评论
at java.util.ArrayList.get(ArrayList.java:411)
at com.chad.library.adapter.base.BaseQuickAdapter.onBindViewHolder(BaseQuickAdapter.java:818)
at com.chad.library.adapter.base.BaseQuickAdapter.onBindViewHolder(BaseQuickAdapter.java:67)
at .widget.GloriousRecyclerView$GloriousAdapter.onBindViewHolder(GloriousRecyclerView.java:92)
https://github.com/titanchen2000/GloriousRecyclerView
mOriginalAdapter.onBindViewHolder(holder, realPosition);
前面加那个注释就没警告了
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);
}这个方法最后一行 会有警告信息,不影响使用,就是有点看着不太爽,有没有方法去掉