前言: 自从上次忘记了某事之后,我就决定了在网上记录下自己学习的一些笔记(有错误请评论告诉我,谢谢
今天突然想起来之前写过的一个项目,首页用的是ScrollView的嵌套,其中里面还嵌套了RecyclerView,在滑动到RecyclerView那一部分的时候有明显的卡顿,觉得应该把整体当作一个RecyclerView不应该用嵌套
原项目效果图:
开始自定义一个可以添加Header和Footer的RecyclerView,由于RecyclerView没有自己的addHeaderView方法,所以参考了ListView的代码,整体的代码如下:
CustomRecyclerView 继承自RecyclerView
public class CustomRecyclerView extends RecyclerView {
private ArrayList<ViewConfig> mHeadCouListInfo; //保存头部的view
private ArrayList<ViewConfig> mFootCouListInfo; //保存尾部的view
private int headCount; //记录head的个数
private int footCount; //记录foot的个数
private Adapter mAdapter; //adapter,可能是customadapter, 可能是自定义adapter
private Context mContext;
public CustomRecyclerView(@NonNull Context context) {
this(context, null);
}
public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
mHeadCouListInfo = new ArrayList<>();
mFootCouListInfo = new ArrayList<>();
mContext = context;
}
/**
* 添加HeadView的方法
*
* @param view
*/
public void addHeadView(View view) {
this.addHeadView(view, 0);
}
public void addHeadView(View view, int index) {
headCount++;
setHeadViewConfig(view, ViewConfig.HEADVIEW, headCount, 100000);
if (mAdapter != null) {
if (!(mAdapter instanceof CustomAdapter)) {
wrapHeadAdapter();
}
}
}
public void addFootView(View view) {
this.addFootView(view, 0);
}
public void addFootView(View view, int index) {
footCount++;
setFootViewConfig(view, ViewConfig.FOOTVIEW, footCount, 100000);
if (mAdapter != null) {
if (!(mAdapter instanceof CustomAdapter)) {
wrapHeadAdapter();
}
}
}
/**
* 将adapter构建为customadapter用来填充头部尾部布局
*/
private void wrapHeadAdapter() {
mAdapter = new CustomAdapter(mHeadCouListInfo, mFootCouListInfo, mAdapter, mContext);
}
@Override
public void setAdapter(@Nullable Adapter adapter) {
if (mHeadCouListInfo.size() > 0 || mFootCouListInfo.size() > 0) {
mAdapter = new CustomAdapter(mHeadCouListInfo, mFootCouListInfo, adapter, mContext);
} else {
mAdapter = adapter;
}
super.setAdapter(mAdapter);
}
/**
* 配置头部view的信息
*
* @param view
* @param type
* @param count
* @param headCount
*/
private void setHeadViewConfig(View view, String type, int count, int headCount) {
ViewConfig viewConfig = new ViewConfig();
viewConfig.setTag(view.getClass() + type + count);
viewConfig.setType(headCount);
viewConfig.setView(R.layout.item_tv);
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null) {
parent.removeView(view);
}
viewConfig.setContentView(view);
mHeadCouListInfo.add(viewConfig);
}
/**
* 配置尾部view的信息
*
* @param view
* @param type
* @param count
* @param headCount
*/
private void setFootViewConfig(View view, String type, int count, int headCount) {
ViewConfig viewConfig = new ViewConfig();
viewConfig.setTag(view.getClass() + type + count);
viewConfig.setType(headCount);
viewConfig.setView(R.layout.item_tv);
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null) {
parent.removeView(view);
}
viewConfig.setContentView(view);
mFootCouListInfo.add(viewConfig);
}
public CustomAdapter getHeadAndFootAdapter() {
return (CustomAdapter) mAdapter;
}
public void setCustomClickListener(ICustomClickListener customClickListener) {
getHeadAndFootAdapter().setCustomClickListener(customClickListener);
}
}
setHeadViewConfig和setFootdViewConfig两个方法作用一致,只是两个添加的元素不一样
wrapHeadAdapter方法的作用是用于构造CustomAdapter用来处理头部尾部的view,如果处理不了, 将会自动转交给自定义的Adapter处理
类里部分参数没用到,但是我感觉后面我会用到的
下面是CustomAdapter的代码
CustomAdapter 继承自 RecyclerView.Adapter<? extends ViewHolder>
public class CustomAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener {
private List<ViewConfig> headConfig;
private List<ViewConfig> footConfig;
private ArrayList<ViewConfig> EMPTY_LIST = new ArrayList<>();
private LayoutInflater inflater;
private RecyclerView.Adapter mAdapter;
private int headcount = 0;
private int footcount = 0;
private Context mContext;
private ICustomClickListener customClickListener;
public CustomAdapter(List<ViewConfig> headConfig, List<ViewConfig> footConfig, RecyclerView.Adapter mAdapter, Context mContext) {
this.mAdapter = mAdapter;
this.inflater = LayoutInflater.from(mContext);
this.mContext = mContext;
if (headConfig == null) {
this.headConfig = EMPTY_LIST;
} else {
this.headConfig = headConfig;
}
if (footConfig == null) {
this.footConfig = EMPTY_LIST;
} else {
this.footConfig = footConfig;
}
}
/**
* 设置监听事件
*
* @param customClickListener
*/
public void setCustomClickListener(ICustomClickListener customClickListener) {
this.customClickListener = customClickListener;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int index) {
LogUtils.e("CustomAdapter", "index:" + index);
if (index == ViewConfig.HEADVIEW_TYPE) {
FrameLayout contentView = (FrameLayout) inflater.inflate(R.layout.item_head_foot_parent, viewGroup, false);
View cView = headConfig.get(headcount++).getContentView();
ViewGroup vg = (ViewGroup) cView.getParent();
if (vg != null) {
vg.removeView(cView);
}
contentView.addView(cView);
return new CustomViewHolder(contentView);
} else if (index == ViewConfig.FOOTVIEW_TYPE) {
FrameLayout contentView = (FrameLayout) inflater.inflate(R.layout.item_head_foot_parent, viewGroup, false);
View cView = footConfig.get(footcount++).getContentView();
ViewGroup vg = (ViewGroup) cView.getParent();
if (vg != null) {
vg.removeView(cView);
}
contentView.addView(cView);
return new CustomViewHolder(contentView);
} else {
return mAdapter.onCreateViewHolder(viewGroup, index);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, final int position) {
CustomViewHolder customViewHolder;
if (viewHolder instanceof CustomViewHolder) {
customViewHolder = (CustomViewHolder) viewHolder;
View mParent = customViewHolder.mParent;
//设置view的点击事件
if (mParent instanceof ViewGroup) {
ViewGroup parentGroup = (ViewGroup) mParent;
int childCount = parentGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
parentGroup.getChildAt(i).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (customClickListener != null) {
if (position < getHeadSize()) {
customClickListener.onClick(v, position, 0);
} else {
customClickListener.onClick(v, position - getHeadSize() - mAdapter.getItemCount(), 1);
}
}
}
});
}
}
mParent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (position < getHeadSize()) {
customClickListener.onClick(v, position, 0);
} else {
customClickListener.onClick(v, position - getHeadSize() - mAdapter.getItemCount(), 1);
}
}
});
} else {
mAdapter.onBindViewHolder(viewHolder, position - getHeadSize());
}
}
@Override
public int getItemCount() {
return mAdapter != null
? headConfig.size() + mAdapter.getItemCount() + footConfig.size()
: headConfig.size() + footConfig.size();
}
@Override
public void onClick(View v) {
LogUtils.e("CustomAdapter", "点击");
}
public class CustomViewHolder extends RecyclerView.ViewHolder {
View mParent;
public CustomViewHolder(@NonNull View itemView) {
super(itemView);
mParent = itemView;
}
}
@Override
public int getItemViewType(int position) {
int headSize = getHeadSize();
int adapterCount = getItemCount();//获取实际的个数
if (position < headSize) {
return ViewConfig.HEADVIEW_TYPE;
} else if (position >= headSize + mAdapter.getItemCount() && position < adapterCount) {
return ViewConfig.FOOTVIEW_TYPE;
}
return -1;
}
public int getHeadSize() {
return headConfig.size();
}
public int getFootSize() {
return footConfig.size();
}
}
getItemViewType中根据position当前的值返回类型
position < headsize (那么证明现在就是头部
position >= headsize && position < headSize + mAdapter.getItemCount (证明这个时候是实际的itemview
position >= headSize + mAdapter.getItemCount() && position < getItemCount (这个时候就是尾部了
在代码中 我们只要判断头部和尾部就行了, 其他一律返回-1 (其他数也可以)
onCreateViewHolder()
在这个方法中根据index判断view的类型,当然这个index只是在我的电脑上显示, 在你的上面显示可能是i也可能type,等等。
起先我用了
View contentView = inflater.inflate(headConfig.get(0).getView(), viewGroup, false);
contentView.addView(contentView);
这样的代码, 但是抛出了一个异常,大体意思就是复用的错误 后来改用了这样的写法
FrameLayout contentView = (FrameLayout) inflater.inflate(R.layout.item_head_foot_parent, viewGroup, false);
View cView = headConfig.get(headcount++).getContentView();
ViewGroup vg = (ViewGroup) cView.getParent();
if (vg != null) {
vg.removeView(cView);
}
contentView.addView(cView);
先得到一个FrameLayout,再将View添加进去, 但是在添加进去之前需要先判断view有没有parent,有的话要先用parent移除掉view, 不然会抛出
You must call removeView() on the child's parent first
这个异常
onBindViewHolder
这个方法中会先判断ViewHolder, 如果ViewHolder是CustomViewHolder的话会得到里面唯一的view, 再去判断该view是普通View还是ViewGroup。(正常情况来说都是ViewGroup,因为Viewholder里面的View实际就是FrameLayout
在得出FrameLayout中的子view, 为子view添加了点击事件和长按事件
这次的代码大概就这么多,还没来得及好好测一测,后面会在添加头部尾部的基础上添加上拉加载下拉刷新的逻辑。
部分demo调用代码和效果图如下
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.mData = new ArrayList<>();
this.mCustomRv = findViewById(R.id.m_customrv);
mCustomRv.setLayoutManager(new LinearLayoutManager(this));
RvAdapter rvAdapter = new RvAdapter(mData, this);
mCustomRv.addHeadView(getTextView("头部9527", Color.parseColor("#CA66F0")));
mCustomRv.addHeadView(getTextView("头部17131", Color.parseColor("#A6A5F1")));
mCustomRv.addFootView(getTextView("尾部9527", Color.parseColor("#05F066")));
mCustomRv.addFootView(getTextView("尾部17131", Color.parseColor("#90C56F")));
mCustomRv.setAdapter(rvAdapter);
mCustomRv.setCustomClickListener(new ICustomClickListener() {
@Override
public void onClick(View view, int position,int type, Object... data) {
LogUtils.e("MainActivity", "position:" + position);
}
@Override
public void onLongClick(View view, int position,int type, Object... data) {
}
});
/**
* 添加数据源
*/
for (int i = 0; i < 5; i++) {
mData.add("mData --- " + i);
}
}
项目已经发布到ghithub 并有gradle依赖
CustomRecyclerView