Android性能优化之RecyclerView分页加载组件功能详解

目录
  • 引言
  • 1 分页加载组件
    • 1.1 功能定制
    • 1.2 手写分页列表
    • 1.3 生命周期管理
  • 2 github

引言

在Android应用中,列表有着举足轻重的地位,几乎所有的应用都有列表的身影,但是对于列表的交互体验一直是一个大问题。在性能比较好的设备上,列表滑动几乎看不出任何卡顿,但是放在低端机上,卡顿会比较明显,而且列表中经常会伴随图片的加载,卡顿会更加严重,因此本章从手写分页加载组件入手,并对列表卡顿做出对应的优化

1 分页加载组件

为什么要分页加载,通常列表数据存储在服务端会超过100条,甚至上千条,如果服务端一次性返回,我们一次性接受直接加载,如果其中有图片加载,肯定直接报OOM,应用崩溃,因此我们通常会跟服务端约定分页的规则,服务端会按照页码从0开始给数据,或者在数据中返回下一页对应的索引,当出发分页加载时,就会拿到下一页的页码请求新一页的数据。

目前在JetPack组件中,Paging是使用比较多的一个分页加载组件,但是Paging使用的场景有限,因为流的限制,导致只能是单一数据源,而且数据不能断,只能全部加载进来,因此决定手写一个分页加载组件,适用多种场景。

1.1 功能定制

如果想要自己写一个分页加载库,首先需要明白,分页加载组件需要做什么事?

对于RecyclerView来说,它的主要功能就是创建视图并绑定数据,因此我们先定义分页列表的基础能力,绑定视图和数据

interface IPagingList<T> {
    fun bindView(context: Context,lifecycleOwner: LifecycleOwner, recyclerView: RecyclerView,adapter: PagingAdapter<T>,mode: ListMode) {}
    fun bindData(model: List<BasePagingModel<T>>) {}
}

bindData:

bindData就不多说了,就是绑定数据,首先我们拿到的数据一定是一个列表数据,因为并不知道业务方需要展示的数据类型是啥样的,因此需要泛型修饰,那么BasePagingModel是干什么的呢?

open class BasePagingModel<T>(
    var pageCount: String = "", //页码
    var type: Int = 1, //分页类型 1 带日期 2 普通列表
    var time: String = "", //如果是带日期的model,那么需要传入此值
    var itemData: T? = null
)

首先BasePagingModel是分页列表中数据的基类,其中存储的元素包括pageCount,代表传进来的数据列表是哪一页,type用来区分列表数据类型,time可以代表当前数据在服务端的时间(主要场景就是列表中数据展示需要带时间,并根据某一天进行数据聚合),itemData代表业务层需要处理的数据。

bindView:

对于RecyclerView来说,创建视图、展示数据需要适配器,因此这里传入了RecyclerView还有通用的适配器PagingAdapter

abstract class PagingAdapter<T> : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private var datas: List<BasePagingModel<T>>? = null
    private var maps: MutableMap<String, MutableList<BasePagingModel<T>>>? = null
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return buildBusinessHolder(parent, viewType)
    }
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (datas != null) {
            bindBusinessData(holder, position, datas)
        } else if (maps != null) {
            bindBusinessMapData(holder, position, maps)
        }
    }
    abstract fun getHolderWidth(context: Context):Int
    override fun getItemCount(): Int {
        return if (datas != null) datas!!.size else 0
    }
    open fun bindBusinessMapData(
        holder: RecyclerView.ViewHolder,
        position: Int,
        maps: MutableMap<String, MutableList<BasePagingModel<T>>>?
    ) {
    }
    open fun bindBusinessData(
        holder: RecyclerView.ViewHolder,
        position: Int,
        datas: List<BasePagingModel<T>>?
    ) {
    }
    abstract fun buildBusinessHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
    fun setPagingData(datas: List<BasePagingModel<T>>) {
        this.datas = datas
        notifyDataSetChanged()
    }
    fun setPagingMapData(maps: MutableMap<String, MutableList<BasePagingModel<T>>>) {
        this.maps = maps
        notifyDataSetChanged()
    }
}

这一章,我们先介绍使用场景比较多的单数据列表

PagingAdapter是一个抽象类,携带的数据同样是业务方需要处理的数据,是一个泛型,创建视图方法buildBusinessHolder交给业务方实现,这里我们关注两个数据相关的方法 bindBusinessData和setPagingData,当调用setPagingData方法时,将处理好的数据列表发进来,然后调用notifyDataSetChanged方法刷新列表,这个时候会调用bindBusinessData将列表中的数据绑定并展示出来。

这里我们还需要关注一个方法,这个方法业务方必须要实现,这个方法有什么作用呢?

abstract fun getHolderWidth(context: Context):Int

这个方法用于返回列表中每个ItemView的尺寸宽度,因为在分页组件中会判断当前列表可见的ItemView有多少个。这里大家可能会有疑问,RecyclerView的LayoutManager不是有对应的api吗,像

findFirstVisibleItemPosition()
findLastVisibleItemPosition()
findFirstCompletelyVisibleItemPosition()
findLastCompletelyVisibleItemPosition()

为什么不用呢?因为我们的分页组件是要兼容多种视图形式的,虽然我们今天讲到的普通列表用这个是没有问题的,但是有些视图类型是不能兼容这个api的,后续会介绍。

1.2 手写分页列表

先把第一版的代码贴出来,有个完整的体系

class PagingList<T> : IPagingList<T>, IModelProcess<T>, LifecycleEventObserver {
    private var mTotalScroll = 0
    private var mCallback: IPagingCallback? = null
    private var currentPageIndex = ""
    //模式
    private var mode: ListMode = ListMode.DATE
    private var adapter: PagingAdapter<T>? = null
    //支持的类型 普通列表
    private val dateMap: MutableMap<String, MutableList<BasePagingModel<T>>> by lazy {
        mutableMapOf()
    }
    private val simpleList: MutableList<BasePagingModel<T>> by lazy {
        mutableListOf()
    }
    override fun bindView(
        context: Context,
        lifecycleOwner: LifecycleOwner,
        recyclerView: RecyclerView,
        adapter: PagingAdapter<T>,
        mode: ListMode
    ) {
        this.mode = mode
        this.adapter = adapter
        recyclerView.adapter = adapter
        recyclerView.layoutManager =
            LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
        addRecyclerListener(recyclerView)
        lifecycleOwner.lifecycle.addObserver(this)
    }
    private fun addRecyclerListener(recyclerView: RecyclerView) {
        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    if (!recyclerView.canScrollHorizontally(1) && currentPageIndex == "-1" && mTotalScroll > 0) {
                        //滑动到底部
                        mCallback?.scrollEnd()
                    }
                    //获取可见item的个数
                    val visibleCount = getVisibleItemCount(recyclerView.context, recyclerView)
                    if (recyclerView.childCount > 0 && visibleCount >= (getListCount(mode) ?: 0)) {
                        if (currentPageIndex != "-1") {
                            //请求下一页数据
                            mCallback?.scrollRefresh()
                        }
                    }
                } else {
                    //暂停刷新
                    mCallback?.scrolling()
                }
            }
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                if (!recyclerView.canScrollHorizontally(1) && currentPageIndex == "-1" && mTotalScroll > 0) {
                    //滑动到底部
                    mCallback?.scrollEnd()
                }
                mTotalScroll += dx
                //滑动超出2屏
//                binding.ivBackFirst.visibility =
//                    if (mTotalScroll > ScreenUtils.getScreenWidth(requireContext()) * 2) View.VISIBLE else View.GONE
            }
        })
    }
    override fun bindData(model: List<BasePagingModel<T>>) {
        //处理数据
        dealPagingModel(model)
        //adapter刷新数据
        if (mode == ListMode.DATE) {
            adapter?.setPagingMapData(dateMap)
        } else {
            adapter?.setPagingData(simpleList)
        }
    }
    fun setScrollListener(callback: IPagingCallback) {
        this.mCallback = callback
    }
    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        if (event == Lifecycle.Event.ON_RESUME) {
            //TODO 加载图片
//            Glide.with(requireContext()).resumeRequests()
        } else if (event == Lifecycle.Event.ON_PAUSE) {
            //TODO 停止加载图片
        } else if (event == Lifecycle.Event.ON_DESTROY) {
            //TODO 页面销毁不会加载图片
        }
    }
    /**
     * 获取可见的item个数
     */
    private fun getVisibleItemCount(context: Context, recyclerView: RecyclerView): Int {
        var totalCount = 0
        //首屏假设全部占满
        totalCount +=
            ScreenUtils.getScreenWidth(recyclerView.context) / adapter?.getHolderWidth(context)!!
        totalCount += mTotalScroll / adapter?.getHolderWidth(context)!!
        return (totalCount + 1)
    }
    override fun getTotalCount(): Int? {
        return getListCount(mode)
    }
    override fun dealPagingModel(data: List<BasePagingModel<T>>) {
        this.currentPageIndex = updateCurrentPageIndex(data)
        if (mode == ListMode.DATE) {
            data.forEach { model ->
                val time = DateFormatterUtils.check(model.time)
                if (dateMap.containsKey(time)) {
                    model.itemData?.let {
                        dateMap[time]?.add(model)
                    }
                } else {
                    val list = mutableListOf<BasePagingModel<T>>()
                    list.add(model)
                    dateMap[time] = list
                }
            }
        } else {
            simpleList.addAll(data)
        }
    }
    private fun updateCurrentPageIndex(data: List<BasePagingModel<T>>): String {
        if (data.isNotEmpty()) {
            return data[0].pageCount
        }
        return "-1"
    }
    private fun getListCount(mode: ListMode): Int? {
        var count = 0
        if (mode == ListMode.DATE) {
            dateMap.keys.forEach { key ->
                //获取key下的元素个数
                count += dateMap[key]?.size ?: 0
            }
        } else {
            count = simpleList.size
        }
        return count
    }
}

首先,PagingList实现了IPagingList接口,我们先看实现,在bindView方法中,其实就是给RecyclerView设置了适配器,然后注册了RecyclerView的滑动监听,我们看下监听器中的主要实现。

onScrollStateChanged方法主要用于监听列表是否在滑动,当列表的状态为SCROLL_STATE_IDLE时,代表列表停止了滑动,这里做了两件事:

(1)首先判断列表是否滑动到了底部

if (!recyclerView.canScrollHorizontally(1) && currentPageIndex == "-1" && mTotalScroll > 0) {
    //滑动到底部
    mCallback?.scrollEnd()
}

这里需要满足三个条件:recyclerView.canScrollHorizontally(1)如果返回了false,那么代表列表不能继续滑动;还有就是会判断currentPageIndex是否是最后一页,如果等于-1那么就是最后一页,同样需要判断滑动的距离,综合来说就是【如果列表滑动到了最后一页而且不能再继续滑动了,那么就是到底了】,这里可以展示尾部的到底UI。

(2)判断是否能够触发分页加载

/**
 * 获取可见的item个数
 */
private fun getVisibleItemCount(context: Context, recyclerView: RecyclerView): Int {
    var totalCount = 0
    //首屏假设全部占满
    totalCount +=
        ScreenUtils.getScreenWidth(recyclerView.context) / adapter?.getHolderWidth(context)!!
    totalCount += mTotalScroll / adapter?.getHolderWidth(context)!!
    return (totalCount + 1)
}

首先这里会判断展示了多少ItemView,之前提到的适配器中的getHolderWidth这里就用到了,首先我们会假设首屏全部占满了ItemView,然后根据列表滑动的距离,判断后续有多少ItemView展示出来,最终返回结果。

我们先不看下面的逻辑,因为分页加载涉及到了数据的处理,因此我们先看下bindData的实现

override fun bindData(model: List<BasePagingModel<T>>) {
    //处理数据
    dealPagingModel(model)
    //adapter刷新数据
    if (mode == ListMode.DATE) {
        adapter?.setPagingMapData(dateMap)
    } else {
        adapter?.setPagingData(simpleList)
    }
}

在调用bindData时会传入一页的数据,dealPagingModel方法用于处理数据,首先获取当前数据的页码,用于判断是否需要继续分页加载。

override fun dealPagingModel(data: List<BasePagingModel<T>>) {
    this.currentPageIndex = updateCurrentPageIndex(data)
    if (mode == ListMode.DATE) {
        data.forEach { model ->
            val time = DateFormatterUtils.check(model.time)
            if (dateMap.containsKey(time)) {
                model.itemData?.let {
                    dateMap[time]?.add(model)
                }
            } else {
                val list = mutableListOf<BasePagingModel<T>>()
                list.add(model)
                dateMap[time] = list
            }
        }
    } else {
        simpleList.addAll(data)
    }
}

剩下的工作用于组装数据,simpleList用于存储全部的列表数据,每次传入一页数据,都会存在这个集合中。处理完数据之后,将数据塞进adapter,用于刷新数据。

然后我们回到前面,我们在拿到了可见的ItemView的个数之后,首先会判断recyclerView展示的ItemView个数,如果等于0,那么就说明没有数据,就不需要触发分页加载。

if (recyclerView.childCount > 0 && visibleCount >= (getListCount(mode) ?: 0)) {
    if (currentPageIndex != "-1") {
        //请求下一页数据
        mCallback?.scrollRefresh()
    }
}

假设每页展示10条数据,这个时候getListCount方法返回的就是总的数据个数(10),如果visibleCount超过了List的总个数,那么就需要触发分页加载,因为之前我们提到,最后一页的index就是-1,所以这里判断如果是最后一页,就不需要分页加载了。

1.3 生命周期管理

在PagingList中,我们实现了LifecycleEventObserver接口,这里的作用是什么呢?

就是我们知道,在列表中经常会有图片的加载,那么在图片加载时如果滑动列表,那么势必会产生卡顿,因此我们在滑动的过程中不会去加载图片,而是在滑动停止时,重新加载,这个优化体验是没有问题,用户不会关注滑动时的状态。

那么这里会存在一个问题,例如我们在滑动的过程中退出到后台,这个时候列表滑动停止时加载图片,可能存在上下文找不到的场景导致应用崩溃,因此我们传入生命周期的目的在于:让列表具备感知生命周期的能力,当列表处在不可见的状态时,不能进行多余的网络请求。

2022-09-04 15:41:43.541 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:43.651 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:43.661 2763-2763/com.lay.paginglist E/MainActivity: scrollRefresh--
2022-09-04 15:41:43.668 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:43.674 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:43.877 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:43.885 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:43.950 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:44.101 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:44.175 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:44.318 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:44.467 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:44.475 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:45.188 2763-2777/com.lay.paginglist I/.lay.paginglis: WaitForGcToComplete blocked RunEmptyCheckpoint on ProfileSaver for 12.247ms
2022-09-04 15:41:47.008 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.099 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.186 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:47.322 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.403 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.404 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:47.514 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:47.606 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.650 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:47.683 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.781 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:47.889 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.950 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.963 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:48.156 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:48.182 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData --- 
2022-09-04 15:41:48.231 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:48.489 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:48.533 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:48.593 2763-2763/com.lay.paginglist E/MainActivity: scrollEnd--

我们可以看下具体的实现效果就是,当触发分页加载时,scrollRefresh会被回调,这里可以进行网络请求,拿到数据之后再次调用bindData方法,然后继续往下滑动,当滑动到最后一页时,scrollEnd被回调,具体的使用,可以在demo中查看。

2 github

之前有小伙伴提到这个事情,希望在github上放出源码,所以就做了 github.com/LLLLLaaayyy…

大家可以在v1.0分支查看源码,在app模块中有一个demo大家可以看具体的使用方式,分页列表的代码在paging模块中

以上就是Android性能优化之RecyclerView分页加载组件功能详解的详细内容,更多关于Android RecyclerView分页加载的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android RecyclerView实现九宫格效果

    RecyclerView更加优化的复用机制和方便实现UI效果,几乎替代Listview和GridView的使用.但是分割线的实现,需要自己继承ItemDecoration来绘制. 效果图 item的布局文件 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

  • Android实现recyclerview城市字母索引列表

    转拼音的依赖 implementation 'com.github.SilenceDut:jpinyin:v1.0' FastIndexView实现列表右侧字母索引列表 public class FastIndexView extends View { private static final String INDEX_NAME = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private OnLetterUpdateListener listener; priva

  • Android中RecyclerView实现简单购物车功能

    Android中RecyclerView实现简单的购物车,供大家参考,具体内容如下 我们知道在ListView中用setTag来解决Item的复用问题,但是RecyclerView中已经帮我们封装好了复用,如果在项目中出现了RecyclerView的复用性问题时我们又该如何解决.先来看看效果图: 图片可能比较大也没有动态图片但效果是这样的!几天后就该有的都会有好了废话不说,进入正题 复用错误分析: RecyclerView设置数据源时加入了if判断,导致item重用时没有进入if判断.继续复用原

  • Android Recyclerview实现左滑删除功能

    本文实例为大家分享了Android Recyclerview实现左滑删除的具体代码,供大家参考,具体内容如下 1.先创建一个工具类 SlideRecyclerView public class SlideRecyclerView extends RecyclerView {       private static final String TAG = "SlideRecyclerView";     private static final int INVALID_POSITION

  • Android自定义recyclerView实现时光轴效果

    时光轴效果在很多app上都有出现,例如淘宝中快递的跟踪,本文将使用recyclerView实现时光轴效果,我们会到自定义控件,首先先看一下效果图: 接下来是步骤分析 1自定义属性 这个大家应该都了解了,根据我们之前的分析,直接在attrs.xml中进行声明 <declare-styleable name="TimeLine">     <attr name="beginLine" format="reference|color"

  • Android中RecyclerView实现商品分类功能

    本文实例为大家分享了Android中RecyclerView实现商品分类功能的具体代码,供大家参考,具体内容如下 三个个RecyclerView实现 //左边的布局  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:layout_height="50d

  • Android性能优化之RecyclerView分页加载组件功能详解

    目录 引言 1 分页加载组件 1.1 功能定制 1.2 手写分页列表 1.3 生命周期管理 2 github 引言 在Android应用中,列表有着举足轻重的地位,几乎所有的应用都有列表的身影,但是对于列表的交互体验一直是一个大问题.在性能比较好的设备上,列表滑动几乎看不出任何卡顿,但是放在低端机上,卡顿会比较明显,而且列表中经常会伴随图片的加载,卡顿会更加严重,因此本章从手写分页加载组件入手,并对列表卡顿做出对应的优化 1 分页加载组件 为什么要分页加载,通常列表数据存储在服务端会超过100条

  • Android性能优化之plt hook与native线程监控详解

    目录 背景 native 线程创建 PLT PLT Hook xhook bhook plt hook总结 背景 我们在android超级优化-线程监控与线程统一可以知道,我们能够通过asm插桩的方式,进行了线程的监控与线程的统一,通过一系列的黑科技,我们能够将项目中的线程控制在一个非常可观的水平,但是这个只局限在java层线程的控制,如果我们项目中存在着native库,或者存在着很多其他so库,那么native层的线程我们就没办法通过ASM或者其他字节码手段去监控了,但是并不是就没有办法,还有

  • PHP+jQuery实现滚屏无刷新动态加载数据功能详解

    本文实例讲述了PHP+jQuery实现滚屏无刷新动态加载数据功能.分享给大家供大家参考,具体如下: index.php <?php require_once('connect.php'); //连接数据库 $user = array('demo1','demo2','demo3','demo3','<de></de>mo4'); //模拟了几个用户 ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Trans

  • Android性能优化之利用Rxlifecycle解决RxJava内存泄漏详解

    前言: 其实RxJava引起的内存泄漏是我无意中发现了,本来是想了解Retrofit与RxJava相结合中是如何通过适配器模式解决的,结果却发现了RxJava是会引起内存泄漏的,所有想着查找一下资料学习一下如何解决RxJava引起的内存泄漏,就查到了利用Rxlifecycle开源框架可以解决,今天周末就来学习一下如何使用Rxlifecycle. 引用泄漏的背景: RxJava作为一种响应式编程框架,是目前编程界网红,可谓是家喻户晓,其简洁的编码风格.易用易读的链式方法调用.强大的异步支持等使得R

  • Android布局性能优化之按需加载View

    有时应用程序中会有一些很少用到的复杂布局.在需要它们的时候再加载可以降低内存的消耗,同时也可以加快界面的渲染速度. 定义ViewStub ViewStub是一个轻量级的View,它没有高宽,也不会绘制任何东西.所以它的加载与卸载的成本很低.每个ViewStub都可以使用android:layout属性指定要加载的布局. 下面这个ViewStub用于一个半透明的ProgressBar的加载.它只有在新工作开始时才会显示. <ViewStub android:id="@+id/stub_imp

  • Android之ListView分页加载数据功能实现代码

    什么是ListView分页加载数据功能呢?在现在的大数据时代,我们不可能把某些数据全部展示到界面,好比我们经常会看的QQ空间一样,当你看动态的时候,系统不可能会把所有好友的动态都展示在上面,你能看到的一般都是最新好友更新的动态,假如你要看非最新的好友动态,通常你都会手指向上滑动屏幕然后去查看,当界面下滑到一定数量的时候,就会看到一个"查看更多",然后突然停顿一下,系统会通过网络去给你刷新其他动态信息,这样的功能我们一般叫做数据下拉刷新功能,也就是我们的分页加载功能,具体的实现是怎样的呢

  • Android编程实现分页加载ListView功能示例

    本文实例讲述了Android编程实现分页加载ListView功能.分享给大家供大家参考,具体如下: package eoe.listview; import android.app.Activity; import android.database.Cursor; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import andr

  • vue终极性能优化方案(解决首页加载慢问题)

    目录 前言 1.路由懒加载 2.打包文件中去掉map文件 3.CDN引入第三方库 4.gzip打包 1.npmi-Dcompression-webpack-plugin 2.在vue.config.js中配置 3.在NGINX中配置 5.终极大招,预渲染 1.cnpminstallprerender-spa-plugin--save-dev 2.vue.config.js 3.router.js 4.main.js 总结 前言 用vue开发项目上线以后,发现首页加载速度非常慢,如果项目比较大,甚

  • 前端JS图片懒加载原理方案详解

    目录 背景 原理 方案 方案一:img的loading属性设为“lazy” 使用方法 优点 兼容性 缺点 方案二:通过offsetTop来计算是否在可视区域内 优化 优点 缺点 方案三:通过getBoundingClientRect来计算是否在可视区域内 方案四:使用IntersectionObserver来判断是否在可视区域内 兼容性 优点 缺点 问题 布局抖动 响应式图片 SEO不友好 插件 背景 懒加载经常出现在前端面试中,是前端性能优化的常用技巧.懒加载也叫延迟加载,把非关键资源先不加载

  • JS图片懒加载库VueLazyLoad详解

    目录 背景 说明 实现原理 1. placeholder 的实现很细致和灵活 2. 添加图片缓存 3. 事件监听使用节流 4. 监听事件不止滚动事件 5. 事件列队的方式来处理懒加载 6. 支持 data-srcset 7. 自定义控制可视区的判定范围 待完善 1. 没有解决布局抖动 2. 跳过已经加载图片的判断方式 3. 局部懒加载 4. 性能不是很好 5. observer 模式配置简单 6. SEO 不友好 总结 背景 上篇<图片懒加载原理方案详解>中详细解析了图片懒加载的原理和方案.主

随机推荐