马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
RecyclerView与ListView的优化
一、根本概念对比
1.1 ListView与RecyclerView概述
ListView和RecyclerView都是Android中用于展示列表数据的告急控件,但RecyclerView是更当代化的办理方案,提供了更多的机动性和性能上风。
ListView特点
- Android早期提供的列表控件
- 利用简单,上手容易
- 内置了常见的分割线、选择模式等功能
- 性能优化相对有限
RecyclerView特点
- Android Support Library(现AndroidX)提供的当代列表控件
- 更加机动,支持多种结构方式
- 欺凌利用ViewHolder模式
- 提供了丰富的动画API
- 通过LayoutManager实现差别的结构结果
1.2 根本利用对比
ListView根本利用
- // 定义Adapter
- class MyListAdapter(context: Context, data: List<String>) :
- ArrayAdapter<String>(context, android.R.layout.simple_list_item_1, data) {
- // 可以重写getView方法进行优化
- }
- // 在Activity中使用
- val listView = findViewById<ListView>(R.id.list_view)
- val data = listOf("Item 1", "Item 2", "Item 3")
- listView.adapter = MyListAdapter(this, data)
复制代码 RecyclerView根本利用
- // 定义ViewHolder和Adapter
- class MyAdapter(private val data: List<String>) :
- RecyclerView.Adapter<MyAdapter.ViewHolder>() {
-
- class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
- val textView: TextView = view.findViewById(R.id.text_view)
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.item_view, parent, false)
- return ViewHolder(view)
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- holder.textView.text = data[position]
- }
-
- override fun getItemCount() = data.size
- }
- // 在Activity中使用
- val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
- recyclerView.layoutManager = LinearLayoutManager(this)
- val data = listOf("Item 1", "Item 2", "Item 3")
- recyclerView.adapter = MyAdapter(data)
复制代码 二、ListView优化本领
2.1 ViewHolder模式
在ListView中,ViewHolder模式不是欺凌的,但利用它可以显着进步性能:
- class OptimizedAdapter(context: Context, private val data: List<String>) :
- ArrayAdapter<String>(context, R.layout.list_item, data) {
-
- private class ViewHolder {
- var textView: TextView? = null
- }
-
- override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- var view = convertView
- val holder: ViewHolder
-
- if (view == null) {
- view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false)
- holder = ViewHolder()
- holder.textView = view.findViewById(R.id.text_view)
- view.tag = holder
- } else {
- holder = view.tag as ViewHolder
- }
-
- holder.textView?.text = data[position]
- return view!!
- }
- }
复制代码 2.2 其他ListView优化本领
- 公道设置ListView高度
- 制止在ScrollView中嵌套ListView
- 利用android:layout_height="wrap_content"时需审慎
- 镌汰getView()中的复杂利用
- 制止在getView()中举行耗时利用
- 图片加载利用异步方式
- 利用分页加载
- 实现滑动到底部加载更多数据
- 制止一次性加载大量数据
三、RecyclerView优化本领
3.1 结构优化
- class MyDiffCallback(private val oldList: List<String>, private val newList: List<String>) :
- DiffUtil.Callback() {
-
- override fun getOldListSize() = oldList.size
-
- override fun getNewListSize() = newList.size
-
- override fun areItemsTheSame(oldPos: Int, newPos: Int) =
- oldList[oldPos] == newList[newPos]
-
- override fun areContentsTheSame(oldPos: Int, newPos: Int) =
- oldList[oldPos] == newList[newPos]
- }
- // 使用DiffUtil更新数据
- fun updateData(newData: List<String>) {
- val diffResult = DiffUtil.calculateDiff(MyDiffCallback(data, newData))
- data.clear()
- data.addAll(newData)
- diffResult.dispatchUpdatesTo(this)
- }
复制代码- recyclerView.setHasFixedSize(true) // 当列表项大小固定时使用
复制代码
- 利用RecycledViewPool共享ViewHolder
- // 在嵌套RecyclerView的场景中
- val viewPool = RecyclerView.RecycledViewPool()
- parentRecyclerView.setRecycledViewPool(viewPool)
复制代码 3.2 数据处置处罚优化
- CoroutineScope(Dispatchers.IO).launch {
- val data = loadDataFromDatabase()
- withContext(Dispatchers.Main) {
- adapter.updateData(data)
- }
- }
复制代码 利用Paging库实现高效分页:
- // 定义DataSource
- class MyDataSource : PageKeyedDataSource<Int, Item>() {
- override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Item>) {
- // 加载初始数据
- val items = fetchDataFromNetwork(1, params.requestedLoadSize)
- callback.onResult(items, null, 2)
- }
-
- override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Item>) {
- // 加载后续页面
- val items = fetchDataFromNetwork(params.key, params.requestedLoadSize)
- callback.onResult(items, params.key + 1)
- }
-
- override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Item>) {
- // 通常不需要实现
- }
- }
复制代码 3.3 滑动优化
- recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
- override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
- super.onScrolled(recyclerView, dx, dy)
- val layoutManager = recyclerView.layoutManager as LinearLayoutManager
- val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
- val totalItemCount = layoutManager.itemCount
-
- // 当滑动到倒数第5个item时预加载下一页数据
- if (lastVisibleItem >= totalItemCount - 5) {
- loadMoreData()
- }
- }
- })
复制代码- recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
- override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
- super.onScrollStateChanged(recyclerView, newState)
- if (newState == RecyclerView.SCROLL_STATE_IDLE) {
- // 滑动停止时加载图片
- imageLoader.resumeLoading()
- } else {
- // 滑动时暂停图片加载
- imageLoader.pauseLoading()
- }
- }
- })
复制代码 四、实战案例
4.1 多范例列表实现
- class MultiTypeAdapter(private val items: List<Any>) :
- RecyclerView.Adapter<RecyclerView.ViewHolder>() {
-
- companion object {
- const val TYPE_HEADER = 0
- const val TYPE_NORMAL = 1
- }
-
- override fun getItemViewType(position: Int): Int {
- return if (position == 0) TYPE_HEADER else TYPE_NORMAL
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
- return when (viewType) {
- TYPE_HEADER -> {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.item_header, parent, false)
- HeaderViewHolder(view)
- }
- else -> {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.item_normal, parent, false)
- NormalViewHolder(view)
- }
- }
- }
-
- override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
- when (holder) {
- is HeaderViewHolder -> holder.bind(items[position] as Header)
- is NormalViewHolder -> holder.bind(items[position] as NormalItem)
- }
- }
-
- override fun getItemCount() = items.size
-
- class HeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) {
- fun bind(header: Header) {
- // 绑定头部数据
- }
- }
-
- class NormalViewHolder(view: View) : RecyclerView.ViewHolder(view) {
- fun bind(item: NormalItem) {
- // 绑定普通项数据
- }
- }
- }
复制代码 4.2 网格结构与瀑布流实现
- // 网格布局
- val gridLayoutManager = GridLayoutManager(this, 3) // 3列网格
- recyclerView.layoutManager = gridLayoutManager
- // 设置跨列显示的项
- gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
- override fun getSpanSize(position: Int): Int {
- return if (adapter.getItemViewType(position) == MultiTypeAdapter.TYPE_HEADER) {
- 3 // 头部占据3列
- } else {
- 1 // 普通项占据1列
- }
- }
- }
- // 瀑布流布局
- val staggeredGridLayoutManager =
- StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) // 2列瀑布流
- recyclerView.layoutManager = staggeredGridLayoutManager
复制代码 五、性能监测与调优
5.1 性能监测工具
- Systrace
- Android Profiler
- 监控
内存利用情况
- 分析CPU利用率
- 自界说性能监测
- class PerformanceMonitorAdapter<VH : RecyclerView.ViewHolder>(private val wrappedAdapter: RecyclerView.Adapter<VH>) :
- RecyclerView.Adapter<VH>() {
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
- val startTime = System.nanoTime()
- val holder = wrappedAdapter.onCreateViewHolder(parent, viewType)
- val endTime = System.nanoTime()
- Log.d("Performance", "onCreateViewHolder took ${(endTime - startTime) / 1000000}ms")
- return holder
- }
-
- override fun onBindViewHolder(holder: VH, position: Int) {
- val startTime = System.nanoTime()
- wrappedAdapter.onBindViewHolder(holder, position)
- val endTime = System.nanoTime()
- Log.d("Performance", "onBindViewHolder for position $position took ${(endTime - startTime) / 1000000}ms")
- }
-
- override fun getItemCount() = wrappedAdapter.itemCount
- }
- // 使用方式
- recyclerView.adapter = PerformanceMonitorAdapter(myAdapter)
复制代码 5.2 常见性能题目及办理方案
- 结构层级过深
- 利用Hierarchy Viewer分析结构层级
- 利用ConstraintLayout镌汰嵌套
- 频仍创建对象
- 利用对象池复用临时对象
- 制止在onBindViewHolder中创建新对象
- 主线程壅闭
- 耗时利用放到背景线程
- 利用协程或RxJava处置处罚异步任务
六、口试题分析
Q1:RecyclerView相比ListView有哪些上风?
答:RecyclerView相比ListView的重要上风:
- 欺凌利用ViewHolder模式,进步性能
- 通过LayoutManager支持多种结构方式(线性、网格、瀑布流)
- 提供了ItemAnimator实现丰富的动画结果
- 通过ItemDecoration自界说分割线和装饰
- 支持局部革新,镌汰不须要的重绘
- 提供了更好的变乱处置处罚机制
- 与DiffUtil联合,高效处置处罚数据厘革
Q2:怎样优化RecyclerView的性能?
答:优化RecyclerView性能的重要方法:
- 利用DiffUtil举行高效的数据更新
- 对于固定巨细的列表,利用setHasFixedSize(true)
- 复杂结构利用多级缓存和RecycledViewPool
- 制止在onBindViewHolder中举行耗时利用
- 利用异步加载和图片缓存
- 实现分页加载,制止一次加载大量数据
- 滑动时停息加载图片等耗时利用
- 优化item结构,镌汰嵌套和太过绘制
Q3:ListView和RecyclerView的缓存机制有什么区别?
答:ListView和RecyclerView的缓存机制区别:
- ListView的缓存机制:
- 一级缓存:mActiveViews,屏幕内可见的View
- 二级缓存:mScrapViews,脱离屏幕的View
- RecyclerView的缓存机制:
- 一级缓存:mAttachedScrap,屏幕内可见的ViewHolder
- 二级缓存:mCachedViews,脱离屏幕的ViewHolder,默认巨细为2
- 三级缓存:ViewCacheExtension,开发者自界说缓存
- 四级缓存:RecycledViewPool,缓存差别范例的ViewHolder,可以在多个RecyclerView间共享
- 重要区别:
- RecyclerView缓存粒度更细,缓存条理更多
- RecyclerView支持多个RecyclerView共享缓存池
- RecyclerView缓存ViewHolder而不但是View
- RecyclerView支持自界说缓存战略
七、开源项目实战
7.1 利用Epoxy实现复杂列表
Airbnb的Epoxy是一个用于构建复杂RecyclerView的库,它利用了类似于React的组件化头脑:
- // 定义Model
- @EpoxyModelClass
- abstract class HeaderModel : EpoxyModelWithHolder<HeaderHolder>() {
- @EpoxyAttribute lateinit var title: String
-
- override fun getDefaultLayout() = R.layout.item_header
-
- override fun bind(holder: HeaderHolder) {
- holder.titleView.text = title
- }
- }
- // 定义Controller
- class MyEpoxyController : TypedEpoxyController<List<Item>>() {
- override fun buildModels(data: List<Item>) {
- // 添加头部
- header {
- id("header")
- title("我的列表")
- }
-
- // 添加列表项
- data.forEach { item ->
- itemModel {
- id(item.id)
- title(item.title)
- description(item.description)
- clickListener { _ -> onItemClicked(item) }
- }
- }
- }
-
- private fun onItemClicked(item: Item) {
- // 处理点击事件
- }
- }
- // 在Activity中使用
- val controller = MyEpoxyController()
- val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
- recyclerView.adapter = controller.adapter
- // 更新数据
- controller.setData(items)
复制代码 项目所在:Epoxy
7.2 利用Paging3实现无穷滚动列表
Jetpack Paging3库提供了一种更当代化的分页加载方案:
- // 定义PagingSource
- class MyPagingSource(
- private val api: MyApi
- ) : PagingSource<Int, Item>() {
-
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {
- return try {
- val page = params.key ?: 1
- val response = api.getItems(page, params.loadSize)
-
- LoadResult.Page(
- data = response.items,
- prevKey = if (page == 1) null else page - 1,
- nextKey = if (response.items.isEmpty()) null else page + 1
- )
- } catch (e: Exception) {
- LoadResult.Error(e)
- }
- }
-
- override fun getRefreshKey(state: PagingState<Int, Item>): Int? {
- return state.anchorPosition?.let { anchorPosition ->
- state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
- ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
- }
- }
- }
- // 在ViewModel中使用
- class MyViewModel : ViewModel() {
- val items = Pager(
- config = PagingConfig(
- pageSize = 20,
- enablePlaceholders = false,
- maxSize = 100
- ),
- pagingSourceFactory = { MyPagingSource(api) }
- ).flow.cachedIn(viewModelScope)
- }
- // 在Activity中使用
- class MyActivity : AppCompatActivity() {
- private val viewModel: MyViewModel by viewModels()
- private val adapter = MyPagingAdapter()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_my)
-
- val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
- recyclerView.adapter = adapter
-
- lifecycleScope.launch {
- viewModel.items.collectLatest { pagingData ->
- adapter.submitData(pagingData)
- }
- }
- }
- }
复制代码 项目所在:Paging
总结
本文具体先容了ListView和RecyclerView的优化本领,从根本概念到高级应用,体系地解说了怎样进步列表性能和用户体验。RecyclerView作为Android当代列表控件,提供了更多的机动性和性能上风,是开发高性能列表界面的首选。
在实际开发中,应根据具体需求选择符合的优化战略,如利用DiffUtil举行高效更新、实现分页加载、优化结构结构等。同时,联合Epoxy、Paging等良好的开源库,可以更轻松地构建复杂且高性能的列表界面。
下一篇文章将先容Fragment的生命周期与利用,敬请等待!
|