RecyclerView 是 Android 中最重要的控件之一, 本文主要内容为如何解决 RecyclerView 包含大量数据, 有多种 item 类型, item 中需要加载图片, 监听多个子 view 的事件等等问题是我如何处理的, 本文只是简单介绍了一下方法, 而具体没有过多描述, 实现的细节远远复杂的多.
一. 对 item 进行优化 减少 item layout 层次, 减少过度绘制, 避免多次 measure , layout
1.在定义 Item 布局的时候, 应该尽量减少布局的层次. 使用 support 包中的 ConstraintLayout 即可避免多层嵌套, 这也是 google 官方推荐的. 大部分情况下, ConstraintLayout 都可以满足我们的需求.
3.当 item 布局简单的时候, 尽量使用代码创建 item, 这可以很大程度的提高布局创建的速度.
4.当 item 复杂的时候, 使用自定义 view, 针对情况进行加载优化. 部分数据预加载(根据情况决定提前加载的 item 数量), 部分数据只在 item 可见时加载. 重写 onMeasure.
5.使用 addItemTouchListener 替换 在 onBinViewHolder 中给 item 绑定事件.
以下为在 RecyclerView 中监听 Item 的事件, 以及 item ViewHolder, position.
public class ItemTouchListener implements RecyclerView.OnItemTouchListener {
private GestureDetectorCompat mGestureDetector;
private RecyclerView mRecyclerView;
public ItemTouchListener(final RecyclerView recyclerView){
this.mRecyclerView = recyclerView;
mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onSingleTapUp(MotionEvent e) {
detect(e, false);
return true;
}
@Override
public void onLongPress(MotionEvent e) {
detect(e, true);
}
private void detect(MotionEvent e, boolean longClick){
View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
if (null != childViewUnder) {
Object tag = childViewUnder.getTag();
int position = mRecyclerView.getChildAdapterPosition(childViewUnder);
if (longClick){
onClick(mRecyclerView.getChildViewHolder(childViewUnder), position, tag);
}else{
onLongClick(mRecyclerView.getChildViewHolder(childViewUnder), position, tag);
}
}
}
});
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept){
}
public void onClick(RecyclerView.ViewHolder viewHolder, int position, Object tag){
}
public void onLongClick(RecyclerView.ViewHolder viewHolder, int position, Object tag){
}
}
我们只需给 RecyclerView.addOnItemTouchListener() 根据需要重写 onClick 或者 onLongClick 方法即可
二. 针对 RecyclerView 优化
1.设置 item 等高, 如果 item 的高度是一样的, 则设置为等高可提高绘制速度
mRecyclerView.setHasFixedSize(true);
2.RecyclerViewPool 共用, 例如在一个 viewPager 中有多个 fragment, 或多个 recyclerview 并列, 其中的列表类型不同时, 可以使用同一个 recyclerviewpool 以复用, 减少创建不必要的 item, 但复用可能会出现很多问题, 使用时应注意.
3.避免在 onCreateViewHolder 和 onBindViewHolder 创建过多对象, 在滑动的时候回频繁地调用这两个方法, 如果创建过多临时对象则会引起频繁 gc , 造成卡顿
4.局部刷新, 在绑定数据的时候更新, 而不是瞬间加载完成所有数据
5.使用预加载, 为了避免在一瞬间创建大量数据造成卡顿, 我们可以先给 RecyclerView 创建一定数量的站位 Item , 这样在第一次加载的时候就只需要执行 onBindViewHolder 绑定数据, 在 onCreateViewHolder 中我们返回相同的 ViewHolder 即可.
6.对于性能较好的手机, 开启缓存可以很大程度的提高速度, 缓存过多会占用内存.
RecyclerView.setItemViewCacheSize(20);
RecyclerView.setDrawingCacheEnabled(true);
RecyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
7.在 onBindViewHolder 中尽量只绑定数据, 避免对数据的计算比较, 新建对象. 例如, 对 item 添加事件监听不必每次都新建一个, 应创只创建一个, 在监听器内进行判断是哪个 item 发起的事件.
尽量避免在滑动时刷新数据
重写 Adapter 的 getItemId 方法, 为每个 item 绑定一个固定 id, 并且 adapter.setHasStableIds(true) 这样在 notifyDataSetChanged 的时候, 不会把所有的item 都重新修建, 相同 id 的 item 不会调用 onCreateViewHolder, 注意 id 代表着数据源唯一性.
三. 添加 页脚 页头.
这个非常简单, 只需要在 Adapter 中进行 itemType 判断返回不同值, 然后在onCreateViewHolder 中判断, 返回不同的view 即可. 下面是我一个项目中的实例. 只需要调用 adapter.setHeaderView 即可添加页头.
public class ReplyRecyclerViewAdapter extends RecyclerView.Adapter小于RecyclerView.ViewHolder大于 {
private final int HEADER = 1;
final int FOOTER = 2;
private List小于Reply大于 mReplies;
private Context context;
private FrameLayout.LayoutParams mLayoutParams;
private ViewGroup mHeaderViewGroup;
public ReplyRecyclerViewAdapter(Context context, List小于Reply大于 replies){
this.context = context;
this.mReplies = replies;
}
public Context getContext() {
return context;
}
public void setHeaderView(ViewGroup view){
mHeaderViewGroup = view;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == HEADER){
return new HeaderViewHolder(mHeaderViewGroup);
}
ReplyView replyView = new ReplyView(parent.getContext());
replyView.setLayoutParams(mLayoutParams);
return new ItemViewHolder(replyView);
}
@Override
public int getItemViewType(int position) {
if (position == 0 && mHeaderViewGroup != null){
return HEADER;
}
if (position == getItemCount()){
return FOOTER;
}
return 0;
}
public void addReplies(List小于Reply大于 replies){
if (replies.size() == mReplies.size() &&
(mReplies.size() != 0 && replies.get(0).getId() == mReplies.get(0).getId())){
return;
}
mReplies.addAll(replies);
notifyDataSetChanged();
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ItemViewHolder){
int p = ((mHeaderViewGroup!=null) ? position-1 : position);
ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
Reply reply = mReplies.get(p);
reply.setFloor(position-1);
itemViewHolder.replyView.setReply(reply);
}
}
@Override
public int getItemCount() {
return mReplies.size() + (mHeaderViewGroup == null ? 0 : 1);
}
static class ItemViewHolder extends RecyclerView.ViewHolder{
ReplyView replyView;
ItemViewHolder(View itemView) {
super(itemView);
this.replyView = (ReplyView) itemView;
}
}
static class HeaderViewHolder extends RecyclerView.ViewHolder{
ViewGroup viewGroup;
HeaderViewHolder(View itemView) {
super(itemView);
this.viewGroup = (ViewGroup) itemView;
}
}
}
添加上拉刷新
上拉刷新是基于上面的适配器进行装饰的实现. 当刷新的时候, 就会调用 OnPullUpListener.onPullUp(), 在使用该 adapter 的地方实现该接口即可, 并添加相关刷新的代码即可.
public class PullRefreshReplyAdapter extends RecyclerView.Adapter小于RecyclerView.ViewHolder大于 {
private ReplyRecyclerViewAdapter mAdapter;
private ViewGroup mFooterViewGroup;
private FooterStatus mStatus = FooterStatus.LOADING;
private OnPullUpListener mOnPullUpListener;
public enum FooterStatus{
LOADING, HIDDEN, COMPLETE
}
public PullRefreshReplyAdapter(Context context, List小于Reply大于 replies){
this(new ReplyRecyclerViewAdapter(context, replies));
}
public PullRefreshReplyAdapter(ReplyRecyclerViewAdapter adapter){
this.mAdapter = adapter;
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200);
mFooterViewGroup = new FrameLayout(mAdapter.getContext());
mFooterViewGroup.setLayoutParams(layoutParams);
ProgressBar progressBar = new ProgressBar(mAdapter.getContext());
progressBar.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 80));
mFooterViewGroup.addView(progressBar);
mFooterViewGroup.setVisibility(View.INVISIBLE);
}
public void setStatus(FooterStatus mStatus) {
this.mStatus = mStatus;
if (mStatus == FooterStatus.COMPLETE){
mFooterViewGroup.removeAllViews();
TextView textView = new TextView(mAdapter.getContext());
textView.setText(mAdapter.getContext().getText(R.string.no_more));
textView.setTextSize(15);
textView.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
textView.setGravity(Gravity.CENTER);
mFooterViewGroup.addView(textView);
}
}
public void setOnPullUpListener(OnPullUpListener onPullUpListener) {
this.mOnPullUpListener = onPullUpListener;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == mAdapter.FOOTER){
return new FooterViewHolder(mFooterViewGroup);
}else{
return mAdapter.onCreateViewHolder(parent, viewType);
}
}
public void setHeaderView(LinearLayout mLlHeader) {
mAdapter.setHeaderView(mLlHeader);
}
public void notifyRangeChanged(int start, int count){
mAdapter.notifyItemRangeChanged(start, count);
}
public void notifyItem(int position){
mAdapter.notifyItemChanged(position);
}
public void notifyAllDataChanged(){
mAdapter.notifyDataSetChanged();
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof FooterViewHolder && position 大于 10){
switch (mStatus){
case HIDDEN:
mFooterViewGroup.setVisibility(View.GONE);
break;
case COMPLETE:
mFooterViewGroup.setVisibility(View.VISIBLE);
break;
case LOADING:
mFooterViewGroup.setVisibility(View.VISIBLE);
mOnPullUpListener.onPullUp();
break;
}
}else{
mAdapter.onBindViewHolder(holder, position);
}
}
public interface OnPullUpListener{
void onPullUp();
}
@Override
public int getItemViewType(int position) {
return mAdapter.getItemViewType(position);
}
@Override
public int getItemCount() {
return mAdapter.getItemCount() + 1;
}
private static class FooterViewHolder extends RecyclerView.ViewHolder{
ViewGroup viewGroup;
FooterViewHolder(View itemView) {
super(itemView);
this.viewGroup = (ViewGroup) itemView;
}
}
}
(完)