【Android】画面卡顿优化列表流畅度五之下拉刷新上拉加载更多组件RefreshLayout修改

之前也写过类似组件的介绍:

地址:下拉刷新&上拉加载更多组件SmartRefreshLayout

本来打算用这个替换的,但在进行仔细研究发现不太合适。功能都很好,但嵌入不了当前的工程体系里。原因就是那啥体制懂的都懂。这样的组件需要改的工程配置参数会有不兼容。所以也就暂时用不了。

如果能用这个替换也不会组件问题了,大概是这样吧。

当前也是一款开源组件

回顾一下列表布局和逻辑处理:

xml布局

xml 复制代码
 <android.support.v4.widget.NestedScrollView
            android:id="@+id/nestedScrollView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <live.bingoogolapple.refreshlayout.BGARefreshLayout
                android:id="@+id/mRefreshLayout"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior">

                <android.support.v7.widget.RecyclerView
                    android:id="@+id/recycleView_playback"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="#FFFFFFFF"
                    android:nestedScrollingEnabled="false"
                    android:paddingLeft="20dp"
                    android:paddingRight="10dp"
                    android:paddingBottom="8dp"
                    app:layout_behavior="@string/appbar_scrolling_view_behavior" />
            </live.bingoogolapple.refreshlayout.BGARefreshLayout>
        </android.support.v4.widget.NestedScrollView>

java逻辑处理:

java 复制代码
 BGARefreshViewHolder bgaNormalRefreshViewHolder = new BGANormalRefreshViewHolder(this, true);
        mRefreshLayout.setRefreshViewHolder(bgaNormalRefreshViewHolder);

        nestedScrollView = findViewById(R.id.nestedScrollView);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            nestedScrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
                @Override
                public void onScrollChange(View view, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    NestedScrollView toNesstedScrollView = (NestedScrollView) view;
                   
                    if (scrollY < 5
                            || toNesstedScrollView.getChildAt(0).getMeasuredHeight()
                            == view.getMeasuredHeight()) {
                        return;
                    }
                    int height = toNesstedScrollView.getChildAt(0).getMeasuredHeight()
                            - view.getMeasuredHeight();
                    if (scrollY == height) {
                        // 为BGARefreshLayout 设置代理
                        if(mRefreshLayout.getDelegate()==null){
                             mRefreshLayout.setDelegate(XXXXActivity.this);
                        }
                        mRefreshLayout.beginLoadingMore();
                    }
                }
            });
        }

这个布局是为了达到类似如下图这样的头部效果的:

这个之前也有过这样布局的博客介绍:【Android】折叠效果CoordinatorLayout+AppBarLayout首页效果&& CoordinatorLayout抖动问题解决方案--100个经典UI设计模板(95/100)

问题症结点

NestedScrollView嵌套子组件BGARefreshLayout;BGARefreshLayout嵌套子组件:RecyclerView;

滑动控制监听器是NestedScrollView组件里的OnScrollChangeListener:

java 复制代码
nestedScrollView = findViewById(R.id.nestedScrollView);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            nestedScrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
                @Override
                public void onScrollChange(View view, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    NestedScrollView toNesstedScrollView = (NestedScrollView) view;
                  
                    if (scrollY < 5
                            || toNesstedScrollView.getChildAt(0).getMeasuredHeight()
                            == view.getMeasuredHeight()) {
                        return;
                    }
                    int height = toNesstedScrollView.getChildAt(0).getMeasuredHeight()
                            - view.getMeasuredHeight();
                    if (scrollY == height) {
                        // 为BGARefreshLayout 设置代理
                        if(mRefreshLayout.getDelegate()==null){
                             mRefreshLayout.setDelegate(XXXXActivity.this);
                            mRefreshLayout.setParentView(nestedScrollView);
                        }
                        mRefreshLayout.beginLoadingMore();
                    }
                }
            });
        }

导致了上拉刷新和下拉加载更多出现了监听问题

BGARefreshLayout里监听逻辑如下:

java 复制代码
@Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        // 被添加到窗口后再设置监听器,这样开发者就不必烦恼先初始化RefreshLayout还是先设置自定义滚动监听器
        if (!mIsInitedContentViewScrollListener && mLoadMoreFooterView != null) {
            setRecyclerViewOnScrollListener();
            setAbsListViewOnScrollListener();

            addView(mLoadMoreFooterView, getChildCount());

            mIsInitedContentViewScrollListener = true;
        }
    }

预期setRecyclerViewOnScrollListener()里的设置就没有生效:

java 复制代码
/**
     *
     * 具体效果要看xml布局结构
     * 比如下面这样的就不适用:
     * NestedScrollView
     *          BGARefreshLayout
     *                  RecyclerView
     *
     * */
    private void setRecyclerViewOnScrollListener() {
        if (mRecyclerView != null) {
            mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    if ((newState == RecyclerView.SCROLL_STATE_IDLE
                            || newState == RecyclerView.SCROLL_STATE_SETTLING)
                            && shouldHandleRecyclerViewLoadingMore(mRecyclerView)) {
                        beginLoadingMore();
                        return;
                    }

                }

                /**
                 * dx : 水平滚动距离
                 * dy : 垂直滚动距离
                 * */
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
//                    Log.d(TAG, "dx = " + dx + " ,dy= " + dy);
                    if (dy < 0) {//时为手指向下滚动,列表滚动显示上面的内容
                        mLoadMoreFooterView.setVisibility(GONE);
                    }
                }
            });
        }
    }

找谁说理去,不生效就算了,问题是如果不留意。根本就发现不了这样的组件问题

NestedScrollView组件里的OnScrollChangeListener和RecyclerView.OnScrollListener效果是一致的都是开始加载更多逻辑beginLoadingMore():

java 复制代码
/**
     * 开始上拉加载更多,会触发delegate的onBGARefreshLayoutBeginRefreshing方法
     */
    public void beginLoadingMore() {
        Log.d(TAG, "beginLoadingMore: called!");
        if (!mIsLoadingMore && mLoadMoreFooterView != null
                && mDelegate != null
                && mDelegate.onBGARefreshLayoutBeginLoadingMore(this)) {
            mIsLoadingMore = true;
            Log.d(TAG, "run:mIsLoadingMore=" + mIsLoadingMore);
            if (mIsShowLoadingMoreView) {
                showLoadingMoreView();
            }
        }
    }

/**
     * 显示上拉加载更多控件
     */
    public void showLoadingMoreView() {
        mRefreshViewHolder.changeToLoadingMore();
        mLoadMoreFooterView.setVisibility(VISIBLE);
        mLoadMoreFooterView.findViewById(R.id.layout_loading).setVisibility(VISIBLE);
        mLoadMoreFooterView.findViewById(R.id.tv_normal_refresh_footer_status_finish).setVisibility(GONE);

      BGARefreshScrollingUtil.scrollToBottom(mParentNestedScrollView);
        BGARefreshScrollingUtil.scrollToBottom(mScrollView);
        BGARefreshScrollingUtil.scrollToBottom(mRecyclerView);
        BGARefreshScrollingUtil.scrollToBottom(mAbsListView);
        if (mStickyNavLayout != null) {
            mStickyNavLayout.scrollToBottom();
        }
    }

BGARefreshScrollingUtil.scrollToBottom(mParentNestedScrollView) 这个是优化后新加的;

如果没有这个就会出现了滑到底部就直接进行加载更多了,不会出现加载更多进度条的提示。需要在滑动到列表底部后再次向上拉一下才能有提示UI画面。这个太隐蔽了,每次感觉怪怪的,但就是没有发现这个交互画面有情况。主要是也不会朝这方面去想。因为看上去都很自然且正常。

showLoadingMoreView里这句代码

BGARefreshScrollingUtil.scrollToBottom(mParentNestedScrollView)是告诉列表当滑动到底部的时候显示提示mLoadMoreFooterView视图后再自动的向上滑动一下,把LoadMoreFooterView显示在屏幕里。

BGARefreshScrollingUtil.scrollToBottom(mParentNestedScrollView)逻辑如下:

java 复制代码
 /**
     * nestedScrollView 滚动组件设置滚动到底部
     * 采用post(new runnable)方式在滚动组件渲染完成之后滚动到底部
     *
     * @param nestedScrollView 滚动组件
     * */
    public static void scrollToBottom(final NestedScrollView nestedScrollView) {
        if (nestedScrollView != null) {
            nestedScrollView.post(new Runnable() {
                @Override
                public void run() {
                    nestedScrollView.fullScroll(ScrollView.FOCUS_DOWN);
                }
            });
        }
    }

小结一下

就是因为这个滑动效果不兼容所以导致了后面一系列的魔幻情况:

1、加载更多上拉交互的时候看不到加载更多提示UI,因为没有自动向上滑动到底;所以很多时候是没有看到

2、然后就会导致发起新一页数据的api请求调用的间隔时间混乱,有在短时间内多次调用的情况发生,然后再这个时间段内加载了大量的数据导致了页面卡住了。页面越卡,发起请求连续性导致的问题就越严重。然后用户会滑动更多次。陷入了死循环里了。检测到的某次调试的时候发起的连续的和预期不符的api数据请求记录如下图。

3、然后必然的内存消耗急剧加大,内存压力在很短的时间陡然上升(事实上优化到最后和内存的关系不是特别大,优化完以后也不过是使用了clearMemory

以上这些问题都是RefreshLayout使用不当导致的。但不投入大量的精力去仔细的分析网络数据、内存、交互等几乎发现不了。因为手机画面表现的很像是内存问题,因此在内存上折腾了两三天也没起到缓解卡顿的预期。

解决方案

最后附上优化后的RefreshLayout逻辑处理:

java 复制代码
import live.bingoogolapple.refreshlayout.util.BGARefreshScrollingUtil;

/**
 * 作者:王浩 邮件:bingoogolapple@gmail.com
 * 创建时间:15/5/21 22:35
 * 描述:下拉刷新、上拉加载更多、可添加自定义(固定、可滑动)头部控件(例如慕课网app顶部的广告位)
 */
public class BGARefreshLayout extends LinearLayout {
  
    /**
     * 目前已经适配的滚动组件有:
     *  AbsListView
     *  ScrollView
     *  NestedScrollView
     *  RecyclerView
     *
     *  在滚动到底部时自动在底部增加弹出"正在加载中的"进度条提示
     * */
    private AbsListView mAbsListView;
    private ScrollView mScrollView;
    private NestedScrollView mParentNestedScrollView;// 外层父组件嵌套 NestedScrollView
    private RecyclerView mRecyclerView;
    private View mNormalView;
    private WebView mWebView;
    private BGAStickyNavLayout mStickyNavLayout;
    private View mContentView;

    private float mInterceptTouchDownX = -1;
    private float mInterceptTouchDownY = -1;
    /**
     * 按下时整个头部控件的paddingTop
     */
    private int mWholeHeaderViewDownPaddingTop = 0;
    /**
     * 记录开始下拉刷新时的downY
     */
    private int mRefreshDownY = -1;

    /**
     * 是否已经设置内容控件滚动监听器
     */
。。。

    /**
     * xml布局中构造
     * */
    public BGARefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOrientation(LinearLayout.VERTICAL);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mHandler = new Handler(Looper.getMainLooper());
        initWholeHeaderView();
    }

  。。。
    /**
     * {@link BGARefreshLayout} 外层嵌套一个父滚动组件适配
     * 目前适配了NestedScrollView 作为父组件
     *
     * */
    public void setParentView(View view) {

        if (null == view)
            return;
        /**
         * 布局嵌套为
         * NestedScrollView
         *          BGARefreshLayout
         *                  RecyclerView
         * */
        if (view instanceof NestedScrollView) {
            mParentNestedScrollView = (NestedScrollView) view;
        }
    }

   
    /**
     * 初始化上拉加载更多控件
     *
     * @return
     */
    private void initLoadMoreFooterView() {
        mLoadMoreFooterView = mRefreshViewHolder.getLoadMoreFooterView();
        if (mLoadMoreFooterView != null) {
            // 测量上拉加载更多控件的高度
            mLoadMoreFooterView.measure(0, 0);
            mLoadMoreFooterViewHeight = mLoadMoreFooterView.getMeasuredHeight();
            mLoadMoreFooterView.setVisibility(GONE);
            mLoadMoreFooterView.findViewById(R.id.layout_loading).setVisibility(GONE);
            mLoadMoreFooterView.findViewById(R.id.tv_normal_refresh_footer_status_finish).setVisibility(GONE);
        }
    }



    /**
     *
     * 具体效果要看xml布局结构
     * 比如下面这样的就不适用:
     * NestedScrollView
     *          BGARefreshLayout
     *                  RecyclerView
     *
     * */
    private void setRecyclerViewOnScrollListener() {
        if (mRecyclerView != null) {
            mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    if ((newState == RecyclerView.SCROLL_STATE_IDLE
                            || newState == RecyclerView.SCROLL_STATE_SETTLING)
                            && shouldHandleRecyclerViewLoadingMore(mRecyclerView)) {
                        beginLoadingMore();
                        return;
                    }

                }

                /**
                 * dx : 水平滚动距离
                 * dy : 垂直滚动距离
                 * */
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
//                    Log.d(TAG, "dx = " + dx + " ,dy= " + dy);
                    if (dy < 0) {//时为手指向下滚动,列表滚动显示上面的内容
                        mLoadMoreFooterView.setVisibility(GONE);
                    }
                }
            });
        }
    }

 

    /**
  
    /**
     * 开始上拉加载更多,会触发delegate的onBGARefreshLayoutBeginRefreshing方法
     */
    public void beginLoadingMore() {
        Log.d(TAG, "beginLoadingMore: called!");
        if (!mIsLoadingMore && mLoadMoreFooterView != null
                && mDelegate != null
                && mDelegate.onBGARefreshLayoutBeginLoadingMore(this)) {
            mIsLoadingMore = true;
            Log.d(TAG, "run:mIsLoadingMore=" + mIsLoadingMore);
            if (mIsShowLoadingMoreView) {
                showLoadingMoreView();
            }
        }
    }

    /**
     * 显示上拉加载更多控件
     */
    public void showLoadingMoreView() {
        mRefreshViewHolder.changeToLoadingMore();
        mLoadMoreFooterView.setVisibility(VISIBLE);
        mLoadMoreFooterView.findViewById(R.id.layout_loading).setVisibility(VISIBLE);
        mLoadMoreFooterView.findViewById(R.id.tv_normal_refresh_footer_status_finish).setVisibility(GONE);

        BGARefreshScrollingUtil.scrollToBottom(mParentNestedScrollView);
        BGARefreshScrollingUtil.scrollToBottom(mScrollView);
        BGARefreshScrollingUtil.scrollToBottom(mRecyclerView);
        BGARefreshScrollingUtil.scrollToBottom(mAbsListView);
        if (mStickyNavLayout != null) {
            mStickyNavLayout.scrollToBottom();
        }
    }

    /**
     * 结束上拉加载更多
     */
    public void endLoadingMore() {
        if (mIsShowLoadingMoreView) {
            // 避免WiFi环境下请求数据太快,加载更多控件一闪而过
            mRecyclerView.postDelayed(mDelayHiddenLoadingMoreViewTask, 300);
        }
    }
    public void setStatusFinish() {
        mLoadMoreFooterView.setVisibility(VISIBLE);
        mLoadMoreFooterView.findViewById(R.id.tv_normal_refresh_footer_status_finish)
                .setVisibility(VISIBLE);
        BGARefreshScrollingUtil.scrollToBottom(mParentNestedScrollView);
        BGARefreshScrollingUtil.scrollToBottom(mScrollView);
        BGARefreshScrollingUtil.scrollToBottom(mRecyclerView);
        BGARefreshScrollingUtil.scrollToBottom(mAbsListView);
        if (mStickyNavLayout != null) {
            mStickyNavLayout.scrollToBottom();
        }
    }

    

主要是新增了对NestedScrollView的处理逻辑;也是为了适配如下这样的xml布局:

xml 复制代码
 /**
         * 布局嵌套为
         * NestedScrollView
         *          BGARefreshLayout
         *                  RecyclerView
         * */

再加上 BGARefreshScrollingUtil.scrollToBottom(mParentNestedScrollView);处理完成了这个上拉加载更多的数据分页加载逻辑衔接处理。

优化这个组件之后加载顺滑度立马就干到数据量800条以内滑动和加载都没有明显的卡顿感了。

但问题还没有彻底解决,因为数据量干到1000条左右就出现了ANR问题和OOM内存问题了。虽然这两个问题没有得到彻底的解决,但大致的解决方案也有方向。

先到这里歇一歇,后面还有一篇

smartApi接口开发工具推荐

历时一年半多开发终于smartApi-v1.0.0版本在2023-09-15晚十点正式上线

smartApi是一款对标国外的postman的api调试开发工具,由于开发人力就作者一个所以人力有限,因此v1.0.0版本功能进行精简,大功能项有:

  • api参数填写
  • api请求响应数据展示
  • PDF形式的分享文档
  • Mock本地化解决方案
  • api列表数据本地化处理
  • 再加上UI方面的打磨

下面是一段smartApi使用介绍:

下载地址:

https://pan.baidu.com/s/1kFAGbsFIk3dDR64NwM5y2A?pwd=csdn

相关推荐
长亭外的少年7 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿9 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神10 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛10 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法11 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter12 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快13 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl14 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江14 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
Dnelic-14 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记