ListView嵌套RecycleView滑动卡顿问题的优化方案

针对ListView嵌套RecycleView导致的性能问题,提出一种利用自定义水平ScrollView+NestFullListView的解决方案,通过预加载和延时刷新提升用户体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

抛出问题

ListView嵌套RecycleView(或者ListView、GridView)时会存在性能问题,是由于内层RecycleView做为外层Listview的item加载时,该RecycleView又会一次性加载它自身的子item项,子item越复杂、手机性能越差,滑动时卡顿现象越明显、越不流畅。
RecycleView虽然自身有RecycleViewPool的概念,可以多个RecycleView共用一个RecycleViewPool,但是也存在不能预加载及延时间隔刷新的功能
使用场景如图:
这里写图片描述

优化方案

A.使用自定义的水平ScrollView 嵌套线性布局(NestFullListView) 替代 RecycleView,实现可以预加载及延时间隔刷新item的功能;
B.当竖向的Listview加载到NestFullListView项时,延时刷新里面的每个子item(间隔50ms),只刷新屏幕可见子item,当用户滑动NestFullListView时,再刷新其他项;
C.ListView在初始化adapter时预加载N个NestFullListView(NestFullListView的子item也预加载),解决滑动时,recycleview初始化时一次性加载所有的可见item,其inflate、onlayout及bindviewholder等耗时太多导致的卡顿问题
D.NestFullListView支持adapter和viewholder,支持动态加载
F.UI效果保持和recycleview的保持一致

具体实现:

1.预加载NestFullListView及其子item

private void preLoadHorizontalCard() {
        mHCardList = new ArrayList<>();
        LayoutInflater inflater = LayoutInflater.from(mContext);
        for(int i = 0 ; i< PRELOAD_LISTVIEW_COUNT; i++) {
            NestFullListView view = (NestFullListView) inflater.inflate(R.layout.nestlist, null);
            view.preLoad();
            mHCardList.add(view);
        }
    }

2.获取预加载的NestFullListView并替换listview项中的NestFullListView
依次取出来用

private NestFullListView getCardView(){
        if(mCurCard < PRELOAD_LISTVIEW_COUNT){
            NestFullListView view = mHCardList.get(mCurCard);
            mCurCard++;
            if(LogUtils.isDebug()) {
                LogUtils.d("NestFullListView", "getCardView from preload:" + view);
            }
            return view;
        }
        return null;
}

在listview的adapter中bindholder时替换预加载好的NestFullListView

NestFullListView recycler = (NestFullListView) holder.getView(R.id.nest_view);
        MyHorizontalScrollView parent = (MyHorizontalScrollView) holder.getView(R.id.nest_view_parent);
        if(recycler.getChildCount() == 0){
            NestFullListView preloadRecycle = getCardView();
            if(preloadRecycle != null) {
                parent.removeView(recycler);
                recycler = preloadRecycle;
                holder.putView(R.id.nest_view, recycler);
                parent.addView(recycler);
            }
        }
        recycler.setFocusable(false);

        if (recycler.getAdapter() == null) {
            GroupItemAdapter groupitemadapter = new GroupItemAdapter(mContext, recommendData.mList, R.layout.nest_view_item);
            recycler.setAdapter(groupitemadapter);
        } else {
            Integer lasScroll = positionScrolls.get(position);
            int lastResult = lasScroll == null? 0 : lasScroll;
            recycler.setList(recommendData.mList, (lastResult != 0));
        }

记录scrollview的位置

public void resetRecyclerPosition(final MyHorizontalScrollView recycler, final NestFullListView nestFullListView, final int position){
        recycler.setOnScrollListener(new MyHorizontalScrollView.OnScrollListener() {
            @Override
            public void onScrollChanged(int l, int t, int oldl, int oldt) {
                if(nestFullListView != null){
                    nestFullListView.showAllItem();
                }
                if(l != oldl) {
                    positionScrolls.put(position, l);
                }
            }
        });
        int curScroll = recycler.getScrollX();
        Integer lasScroll = positionScrolls.get(position);
        int lastResult = lasScroll == null? 0 : lasScroll;
        if(lastResult - curScroll != 0){
            recycler.scrollBy(lastResult - curScroll, 0);
        }
    }

3.延时间隔刷新子item的方法在NestFullListView类里面,这里不贴代码了;

上代码

NestFullListView.java

public class NestFullListView extends LinearLayout {

    private static final String TAG = "NestFullListView";
    private static final int DEFAULT_SHOW_COUNT = 3;
    private static final int DEFAULT_PRELOAD_COUNT = 3;
    private static int CALCULATE_SHOW_COUNT = 0;
    private static final long SHOW_DELAY_TIME = 50;
    private static int MARGIN_1P5 = DensityUtils.dip2px(1.5f);

    private LayoutInflater mInflater;
    private List<RecyclerViewAdapter.BaseRecyclerViewHolder> mVHCahces;//缓存ViewHolder,按照add的顺序缓存,
    private Handler mHandler;
    private AppRecyclerViewAdapter mAdapter;
    private int mCurrentItem;
    private boolean isShowAll = false;


    public NestFullListView(Context context) {
        super(context);
        init(context);
    }

    public NestFullListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mInflater = LayoutInflater.from(context);
        mVHCahces = new ArrayList<>();
        mHandler = new Handler();
        calculateShowCount(context);
    }

    private void calculateShowCount(Context context){
        if(CALCULATE_SHOW_COUNT == 0){
            CALCULATE_SHOW_COUNT = ( DeviceUtils.getScreenWidth(context) / context.getResources().getDimensionPixelSize(R.dimen.recommend_group_list_item_width) ) + 1;
            if(LogUtils.isDebug()){
                LogUtils.d(TAG, "calculateShowCount :" + CALCULATE_SHOW_COUNT);
            }
        }
    }

    /**
     * 外部调用  同时刷新视图
     *
     * @param mAdapter
     */
    public void setAdapter(AppRecyclerViewAdapter mAdapter) {
        this.mAdapter = mAdapter;
        mCurrentItem = 0;
        initUI();
        mHandler.post(mUpdateRunnable);
    }

    Runnable mUpdateRunnable = new Runnable() {
        @Override
        public void run() {
            if (mCurrentItem >= mAdapter.getItemCount()) {
                if(LogUtils.isDebug()) {
                    LogUtils.d(TAG, "mCurrentItem >= mAdapter.getDatas().size()");
                }
                return;
            }

            if(!isShowAll && mCurrentItem >= ( CALCULATE_SHOW_COUNT == 0 ? DEFAULT_SHOW_COUNT : CALCULATE_SHOW_COUNT)){
                if(LogUtils.isDebug()) {
                    LogUtils.d(TAG, "!isShowAll && mCurrentItem >= DEFAULT_SHOW_COUNT");
                }
                return;
            }

            updateUI(mCurrentItem);
            mCurrentItem ++;

            mHandler.postDelayed(mUpdateRunnable, SHOW_DELAY_TIME);
        }
    };

    private void initUI(){
        if(LogUtils.isDebug()) {
            LogUtils.d(TAG, "initUI");
        }
        if (null != mAdapter && mAdapter.getItemCount() > 0) {
            //数据源有数据
            int childCount = getChildCount();
            int dataSize = mAdapter.getItemCount();
            if (dataSize >= childCount) {//数据源大于现有子View不清空

                for (int j = 0; j < childCount - 1; j++) {
                    getChildAt(j).setVisibility(VISIBLE);
                }

            } else if (dataSize < childCount) {//数据源小于现有子View,删除后面多的
                if(LogUtils.isDebug()) {
                    LogUtils.d(TAG, "removeViews from " + mAdapter.getItemCount() + " count " + (getChildCount() - mAdapter.getItemCount()));
                }
                for (int j = dataSize; j < getChildCount(); j++) {
                    getChildAt(j).setVisibility(GONE);
                }
            }
        } else {
            if(LogUtils.isDebug()) {
                LogUtils.d(TAG, "removeAllViews");
            }
            removeAllViews();//数据源没数据 清空视图
        }
    }

    private void updateUI(final int i) {
        if(LogUtils.isDebug()) {
            LogUtils.d(TAG, "updateUI :" + i);
        }

        if (null != mAdapter && mAdapter.getItemCount() > 0) {

            if (i < mAdapter.getItemCount()) {
                RecyclerViewAdapter.BaseRecyclerViewHolder holder;
                if (mVHCahces.size() - 1 >= i) {//说明有缓存,不用inflate,否则inflate
                    holder = mVHCahces.get(i);
                    if(LogUtils.isDebug()) {
                        LogUtils.d(TAG, "bind item :" + i + " bind from cache");
                    }
                } else {
                    holder = mAdapter.onCreateViewHolder(this, 0);
                    if(LogUtils.isDebug()) {
                        LogUtils.d(TAG, "bind item :" + i + " bind from new");
                    }
                    mVHCahces.add(holder);//inflate 出来后 add进来缓存
                }
                mAdapter.onBindViewHolder(holder, i);
                //如果View没有父控件 添加
                if (null == holder.getConvertView().getParent()) {
                    if(LogUtils.isDebug()) {
                        LogUtils.d(TAG, "bind item :" + i + " add to parent");
                    }
                    addViewWithPadding(holder.getConvertView());
                }
                if (holder.getConvertView().getVisibility() == GONE) {
                    holder.getConvertView().setVisibility(VISIBLE);
                }
            } else {
                if(LogUtils.isDebug()) {
                    LogUtils.d(TAG, "i > mAdapter.getDatas().size()");
                }
            }
        } else {
            if(LogUtils.isDebug()) {
                LogUtils.d(TAG, "removeAllViews");
            }
            removeAllViews();//数据源没数据 清空视图
        }

    }

    public AppRecyclerViewAdapter getAdapter(){
        return mAdapter;
    }

    public void setList(List<ApkResInfo> data, boolean isShowAll){
        mHandler.removeCallbacksAndMessages(null);
        mAdapter.setList(data);
        mCurrentItem = 0;
        initUI();
        this.isShowAll = isShowAll;
        mHandler.post(mUpdateRunnable);
    }

    public void showAllItem(){
        if(isShowAll == false) {
            if(LogUtils.isDebug()) {
                LogUtils.d(TAG, "showAllItem");
            }
            this.isShowAll = true;
            mHandler.post(mUpdateRunnable);
        }
    }

    public void preLoad(){
        if(LogUtils.isDebug()) {
            LogUtils.d(TAG, "preLoad");
        }
        for(int i = 0 ; i < DEFAULT_PRELOAD_COUNT; i ++){
            View contentView = LayoutInflater.from(getContext()).inflate(R.layout.recommend_list_group_item, this, false);
            RecyclerViewAdapter.BaseRecyclerViewHolder holder = new RecyclerViewAdapter.BaseRecyclerViewHolder(contentView);
            mVHCahces.add(holder);
            addViewWithPadding(contentView);
        }
    }

    private void addViewWithPadding(View contentView){
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) contentView.getLayoutParams();
        params.leftMargin = MARGIN_1P5;
        params.rightMargin = MARGIN_1P5;
        contentView.setLayoutParams(params);
        addView(contentView);
    }

    @Override
    protected void onDetachedFromWindow() {
        mHandler.removeCallbacksAndMessages(null);
        super.onDetachedFromWindow();
    }
}

RecyclerViewAdapter,ViewHolder

public abstract class RecyclerViewAdapter<T> extends RecyclerView.Adapter<RecyclerViewAdapter.BaseRecyclerViewHolder> {

    protected final Context mContext;
    private final int mItemLayoutId;
    protected List<T> datas;

    public abstract void onConvert(BaseRecyclerViewHolder holder, final T item, int position);

    public RecyclerViewAdapter(Context context, List<T> datas, int itemLayoutId) {
        this.mContext = context;
        this.datas = datas;
        this.mItemLayoutId = itemLayoutId;
    }

    @Override
    public BaseRecyclerViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View contentView = LayoutInflater.from(mContext).inflate(mItemLayoutId, viewGroup, false);
        return new BaseRecyclerViewHolder(contentView);
    }

    public void setList(List<T> data) {
        clearHolderCache();
        this.datas = data;
        notifyDataSetChanged();
    }


    public void setList(List<T> data,int firstVisiblePosition,int lastVisiblePosition) {
        clearHolderCache();
        if (data != null && datas != null && data.size() == datas.size() && lastVisiblePosition < data.size()) {
            this.datas = data;
            notifyItemRangeChanged(firstVisiblePosition, lastVisiblePosition);
        }else{
            this.datas = data;
            notifyDataSetChanged();
        }
    }

    @Override
    public void onBindViewHolder(BaseRecyclerViewHolder holder, int position) {
        onConvert(holder, datas.get(position), position);
    }

    @Override
    public int getItemCount() {
        return datas == null ? 0 : datas.size();
    }

    protected void clearHolderCache() {

    }

    public static class BaseRecyclerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        private final SparseArray<View> mViews;
        private final View mConvertView;
        private OnItemClickListener mOnItemClickListener;

        public BaseRecyclerViewHolder(View itemView) {
            super(itemView);
            mConvertView = itemView;
            mConvertView.setOnClickListener(this);
            mViews = new SparseArray<>();
        }

        public SparseArray<View> getAllHolderViews() {
            return mViews;
        }

        public View getConvertView() {
            return mConvertView;
        }

        /**
         * 对外部提供获取对应view的方法
         */
        public View getView(int viewId) {
            return retrieveView(viewId);
        }

        private View retrieveView(int viewId) {
            View view = mViews.get(viewId);
            if (view == null) {
                view = mConvertView.findViewById(viewId);
                mViews.put(viewId, view);
            }
            return view;
        }

        /**
         * 为TextView设置字符串
         */
        public BaseRecyclerViewHolder setText(int viewId, CharSequence text) {
            TextView view = (TextView) retrieveView(viewId);
            view.setText(text);
            return this;
        }

        /**
         * 为ImageView设置图片
         */
        public BaseRecyclerViewHolder setImageByUrl(int viewId, String url) {
            SimpleDraweeView view = (SimpleDraweeView) retrieveView(viewId);
            FrescoImageLoaderHelper.setImageByUrl((SimpleDraweeView) view, url);
            return this;
        }

        public BaseRecyclerViewHolder setVisible(int viewId, boolean visible) {
            View view = retrieveView(viewId);
            view.setVisibility(visible ? View.VISIBLE : View.GONE);
            return this;
        }

        public BaseRecyclerViewHolder setOnClickListener(int viewId, View.OnClickListener listener) {
            View view = retrieveView(viewId);
            view.setOnClickListener(listener);
            return this;
        }

        public BaseRecyclerViewHolder setOnItemClickListener(OnItemClickListener onItemClickListener) {
            mOnItemClickListener = onItemClickListener;
            return this;
        }

        @Override
        public void onClick(View v) {
            if (mOnItemClickListener != null)
                mOnItemClickListener.onItemClick();
        }

    }

    public interface OnItemClickListener {
        void onItemClick();
    }
}

缺点

这种实现方式的不足之处是,在没有被横向滑动时只加载屏幕能显示的子item个数,但是一旦被滑动,将加载所有的子item;如果子item太多也会有性能问题,如果能借鉴listview的方式,只显示可见item与view复用,将会大大提高该种方式的扩展性;
如果有某个大神直接能在listview或者RecycleView的基础上实现预加载及延时间隔刷新item,请@我,万分感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值