Android嵌套滑动实战:CoordinatorLayout+ViewPager2+SmartRefreshLayout解决滑动冲突
1. 从一次真实的滑动冲突说起为什么这套组合拳值得单独写一篇做移动端开发的朋友大概率都遇到过这种场景页面顶部是一个可折叠的头部区域中间挂着标签栏下面是一个可以左右切换的列表容器每个列表又需要支持下拉刷新和上滑加载更多。听起来需求很常规但真正动手做的时候问题就来了——手指往上滑头部该收起的时候没收起手指往下拉刷新指示器没出来反而把整个页面拖动了左右切换标签的时候纵向的滚动位置又莫名其妙地串了。我最近接手的一个项目就踩了这套坑。页面结构是典型的“折叠头部 标签栏 多列表”布局最初想用最朴素的方式堆出来结果滑动冲突修了整整两天。后来把 CoordinatorLayout、AppBarLayout、TabLayout、ViewPager2 和 SmartRefreshLayout 这套组合拳理顺之后才发现问题的根源不在于某个控件本身而在于嵌套层级中每个控件对触摸事件的消费顺序没有理清楚。这篇内容就是把这套多层嵌套的滑动方案完整拆开讲一遍。适合已经有一定 Android 开发基础、正在做复杂列表页面的同学也适合被滑动冲突折磨过、想彻底搞明白事件分发逻辑的同行。我会从整体架构设计讲到每个控件的职责边界再讲到实际编码中的关键细节和踩坑记录尽量做到看完就能照着搭出来。核心关键词贯穿全文CoordinatorLayout 嵌套滑动、AppBarLayout 折叠、TabLayout 联动、ViewPager2 懒加载、SmartRefreshLayout 刷新、上滑隐藏下拉刷新、滑动冲突处理。这些不是孤立的知识点而是一条完整的链路任何一个环节没接好整个页面就会“手感不对”。2. 整体架构拆解五个控件各自扮演什么角色2.1 为什么不能只用 ScrollView 加个头部很多人第一反应是不就是一个可以滚动的页面吗外面套个 ScrollView里面放头部、标签栏和列表不就行了。这个思路在单列表场景下勉强能用但一旦涉及多标签切换就彻底崩了。原因有三第一ScrollView 和内部 RecyclerView 会产生嵌套滚动冲突外层 ScrollView 会抢走纵向滑动事件导致列表滚不动或者滚到一半卡住。第二标签切换需要 ViewPager2 来承载多个 Fragment而 ViewPager2 内部本身就是 RecyclerView再套一层 ScrollView事件分发会变成一团乱麻。第三折叠头部需要根据滚动偏移量做联动动画ScrollView 无法提供细粒度的滚动回调来驱动 AppBarLayout 的折叠。所以正确的做法是让 CoordinatorLayout 作为根容器它天生就是为协调多个子 View 之间的嵌套滑动而设计的。AppBarLayout 作为可折叠头部挂在 CoordinatorLayout 下面通过app:layout_scrollFlags声明自己的折叠行为。TabLayout 放在 AppBarLayout 内部跟随头部一起折叠或者固定在顶部。ViewPager2 作为可滚动内容区通过app:layout_behavior关联滚动行为。SmartRefreshLayout 则包裹在每个 Fragment 的列表外层负责下拉刷新。2.2 各控件职责边界一览控件核心职责关键配置CoordinatorLayout根容器协调子 View 嵌套滑动作为 XML 根节点AppBarLayout可折叠头部容器layout_scrollFlags控制折叠TabLayout标签栏与 ViewPager2 联动setupWithViewPager2ViewPager2多列表横向切换容器orientationhorizontalSmartRefreshLayout下拉刷新与上滑加载包裹 RecyclerView这张表看起来简单但每个控件的配置细节都会直接影响最终手感。比如 AppBarLayout 的scrollFlags如果设错头部要么完全不折叠要么折叠后不还原。SmartRefreshLayout 如果嵌套层级放错下拉刷新和 CoordinatorLayout 的折叠会互相打架。2.3 嵌套层级的设计原则这套方案的核心原则是纵向滑动由 CoordinatorLayout 统一调度横向滑动交给 ViewPager2刷新逻辑下沉到每个 Fragment 内部。具体来说CoordinatorLayout 作为最外层负责接收纵向滑动事件并分发给 AppBarLayout 和 ViewPager2。AppBarLayout 根据滑动偏移量决定头部折叠多少。ViewPager2 内部的每个 Fragment 各自持有一个 SmartRefreshLayout 包裹的 RecyclerView纵向滚动事件通过嵌套滑动机制向上传递给 CoordinatorLayout。这里有个容易忽略的点ViewPager2 默认是横向的它的内部 RecyclerView 在横向上消费事件纵向事件会通过NestedScrollingChild接口向上传递。所以只要保证 ViewPager2 的每个页面内部使用的是支持嵌套滑动的 RecyclerView整个链路就能打通。3. 环境搭建与依赖配置版本兼容是第一个坎3.1 依赖库选型与版本对齐这套方案涉及多个库版本不匹配是新手最容易踩的坑。我建议统一使用较新的稳定版本并且确保 Material Components 和 SmartRefreshLayout 的版本兼容。// build.gradle (Module级别) dependencies { // Material Components提供CoordinatorLayout、AppBarLayout、TabLayout implementation com.google.android.material:material:1.11.0 // ViewPager2 implementation androidx.viewpager2:viewpager2:1.0.0 // SmartRefreshLayout implementation io.github.scwang90:refresh-layout-kernel:2.1.0 implementation io.github.scwang90:refresh-header-classics:2.1.0 implementation io.github.scwang90:refresh-footer-classics:2.1.0 // RecyclerView implementation androidx.recyclerview:recyclerview:1.3.2 }注意SmartRefreshLayout 从 2.0 版本开始改了包名老版本是com.scwang.smart:refresh-layout-kernel新版本是io.github.scwang90:refresh-layout-kernel。如果你在网上搜到的教程用的是老包名直接复制会报错。3.2 主题配置中容易被忽略的细节使用 Material Components 的控件需要在主题中继承对应的 Theme。如果你的项目还在用 AppCompat 主题CoordinatorLayout 和 AppBarLayout 的某些属性可能不生效。!-- res/values/themes.xml -- style nameTheme.MyApp parentTheme.MaterialComponents.DayNight.NoActionBar !-- 状态栏透明让头部内容延伸到状态栏下方 -- item nameandroid:statusBarColorandroid:color/transparent/item item nameandroid:windowLightStatusBartrue/item /style状态栏透明这个配置很关键。如果头部需要沉浸式效果AppBarLayout 需要设置android:fitsSystemWindowstrue同时给头部容器加上顶部内边距来避开状态栏。否则内容会被状态栏遮挡折叠动画也会出现跳变。3.3 布局文件的整体骨架先把最外层的 XML 结构搭出来后面再逐个填充细节。androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:layout_widthmatch_parent android:layout_heightmatch_parent com.google.android.material.appbar.AppBarLayout android:idid/appBarLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:fitsSystemWindowstrue !-- 可折叠的头部内容区 -- ImageView android:idid/ivHeader android:layout_widthmatch_parent android:layout_height200dp android:scaleTypecenterCrop app:layout_scrollFlagsscroll|exitUntilCollapsed / !-- 标签栏固定在头部下方 -- com.google.android.material.tabs.TabLayout android:idid/tabLayout android:layout_widthmatch_parent android:layout_height48dp app:layout_scrollFlagsscroll|enterAlways / /com.google.android.material.appbar.AppBarLayout androidx.viewpager2.widget.ViewPager2 android:idid/viewPager2 android:layout_widthmatch_parent android:layout_heightmatch_parent app:layout_behaviorstring/appbar_scrolling_view_behavior / /androidx.coordinatorlayout.widget.CoordinatorLayout这段骨架里有两个关键属性需要解释。app:layout_scrollFlagsscroll|exitUntilCollapsed表示头部会随着上滑而折叠但折叠到最小高度后会停住不会完全消失。app:layout_behaviorstring/appbar_scrolling_view_behavior告诉 CoordinatorLayoutViewPager2 是那个负责滚动的兄弟 ViewAppBarLayout 要根据它的滚动来折叠。4. 折叠头部与标签栏的联动实现4.1 AppBarLayout 的 scrollFlags 到底怎么选layout_scrollFlags是 AppBarLayout 折叠行为的核心开关常用的组合有这几种scroll头部会跟随滚动事件一起滚动这是基础开关不加这个其他标志都没意义。exitUntilCollapsed头部折叠到最小高度后停住不会完全滚出屏幕。适合头部有固定内容比如标签栏的场景。enterAlways只要向下滑动头部立即展开不需要滚到顶部才展开。适合希望快速恢复头部的场景。snap折叠或展开时自动吸附到最近的状态避免停在中间半折叠的尴尬位置。我最终用的是scroll|exitUntilCollapsed给图片头部scroll|enterAlways给标签栏。这样上滑时图片先折叠标签栏跟着上移但会固定在顶部下滑时标签栏先出现图片再逐渐展开。4.2 TabLayout 与 ViewPager2 的绑定TabLayout 和 ViewPager2 的联动用官方提供的TabLayoutMediator就能搞定不需要手动监听页面切换。val titles listOf(推荐, 关注, 热榜, 附近) val fragments listOf( ListFragment.newInstance(0), ListFragment.newInstance(1), ListFragment.newInstance(2), ListFragment.newInstance(3) ) val adapter FragmentStateAdapter(this) viewPager2.adapter adapter TabLayoutMediator(tabLayout, viewPager2) { tab, position - tab.text titles[position] }.attach()这里有个细节ViewPager2 的适配器推荐用FragmentStateAdapter而不是FragmentPagerAdapter。因为多列表场景下每个 Fragment 都持有自己的 RecyclerView 和数据用 StateAdapter 可以在页面不可见时回收 Fragment减少内存占用。4.3 头部折叠时的视觉过渡处理头部折叠过程中如果只是简单地让图片往上移视觉上会很生硬。常见的优化是给图片加一个透明度渐变或者缩放效果让折叠过程更自然。appBarLayout.addOnOffsetChangedListener { appBar, verticalOffset - val totalScrollRange appBar.totalScrollRange if (totalScrollRange 0) returnaddOnOffsetChangedListener // 计算折叠比例0表示完全展开1表示完全折叠 val collapseRatio Math.abs(verticalOffset).toFloat() / totalScrollRange // 图片透明度随折叠比例变化 ivHeader.alpha 1f - collapseRatio * 0.8f // 图片缩放效果 val scale 1f collapseRatio * 0.2f ivHeader.scaleX scale ivHeader.scaleY scale }提示totalScrollRange在布局未完成时可能为 0所以一定要做除零保护否则会触发 ArithmeticException。这个监听器是整套方案里最实用的一个扩展点。除了做透明度动画还可以根据折叠比例动态改变标题栏的颜色、控制返回按钮的显示隐藏等。5. 每个 Fragment 内部的刷新与加载设计5.1 SmartRefreshLayout 的嵌套位置选择SmartRefreshLayout 放在哪里直接决定了刷新和折叠会不会打架。我的做法是把它放在每个 Fragment 的布局里包裹 RecyclerView而不是放在 ViewPager2 外面。!-- fragment_list.xml -- com.scwang.smart.refresh.layout.SmartRefreshLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:idid/refreshLayout android:layout_widthmatch_parent android:layout_heightmatch_parent com.scwang.smart.refresh.header.ClassicsHeader android:layout_widthmatch_parent android:layout_heightwrap_content / androidx.recyclerview.widget.RecyclerView android:idid/recyclerView android:layout_widthmatch_parent android:layout_heightmatch_parent android:overScrollModenever / com.scwang.smart.refresh.footer.ClassicsFooter android:layout_widthmatch_parent android:layout_heightwrap_content / /com.scwang.smart.refresh.layout.SmartRefreshLayout为什么放在 Fragment 内部而不是外层因为如果放在 ViewPager2 外面下拉刷新会拦截所有纵向滑动事件导致 AppBarLayout 收不到滚动回调头部就折叠不了。放在 Fragment 内部SmartRefreshLayout 只在列表滚到顶部时才响应下拉其他时候纵向事件正常向上传递。5.2 下拉刷新与上滑加载的代码实现class ListFragment : Fragment() { private var pageIndex 0 private val viewModel: ListViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) refreshLayout.setOnRefreshListener { pageIndex 0 viewModel.refresh(pageIndex) } refreshLayout.setOnLoadMoreListener { pageIndex viewModel.loadMore(pageIndex) } viewModel.data.observe(viewLifecycleOwner) { list - adapter.submitList(list) refreshLayout.finishRefresh() refreshLayout.finishLoadMore() } } }这里有个实战经验finishRefresh()和finishLoadMore()一定要在数据回调里调用不要在网络请求的回调里直接调。因为数据绑定到列表是有延迟的如果刷新动画先结束了但列表还没更新用户会看到列表“跳一下”。5.3 多列表状态下滚动位置的独立维护ViewPager2 默认会保留相邻页面的状态但如果你用的是FragmentStateAdapter页面被回收后滚动位置会丢失。解决办法是在 Fragment 的onDestroyView里保存滚动位置在onViewCreated里恢复。private var scrollPosition 0 override fun onDestroyView() { scrollPosition (recyclerView.layoutManager as LinearLayoutManager) .findFirstVisibleItemPosition() super.onDestroyView() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) recyclerView.layoutManager?.scrollToPosition(scrollPosition) }这个细节看起来不起眼但用户切来切去发现位置丢了体验会非常差。尤其是列表很长的时候每次切回来都回到顶部用户会直接卸载。6. 滑动冲突的排查链路与修复方案6.1 症状一上滑时头部不折叠这是最常见的症状。手指在列表上往上滑列表滚动了但头部纹丝不动。排查思路是这样的第一步检查 AppBarLayout 的layout_scrollFlags是否设置了scroll。没有这个标志AppBarLayout 根本不会响应滚动。第二步检查 ViewPager2 是否设置了app:layout_behaviorstring/appbar_scrolling_view_behavior。这个属性是连接 AppBarLayout 和滚动内容的桥梁漏了它CoordinatorLayout 不知道谁该驱动折叠。第三步检查 Fragment 内部的 RecyclerView 是否被 SmartRefreshLayout 正确包裹。如果 SmartRefreshLayout 的setEnableNestedScrolling被关掉了纵向事件就传不上去。我遇到的情况是第三步SmartRefreshLayout 默认开启了嵌套滑动但我在某个版本里手动调了setEnableNestedScrolling(false)导致事件链断了。改回 true 之后头部折叠立刻恢复正常。6.2 症状二下拉刷新和头部展开互相抢事件这个症状表现为列表已经滚到顶部了继续下拉有时候触发刷新有时候把头部拉下来了两个行为随机出现。根因在于 SmartRefreshLayout 和 CoordinatorLayout 都在监听下拉事件。SmartRefreshLayout 判断列表到顶后开始消费事件CoordinatorLayout 也想把头部拉下来。谁先消费取决于触摸事件的传递顺序。解决方案是给 SmartRefreshLayout 设置一个合理的触发条件让它在头部完全展开后才响应下拉。refreshLayout.setOnMultiListener(object : OnMultiListener { override fun onHeaderMoving(header: RefreshHeader?, isDragging: Boolean, percent: Float, offset: Int, last: Int, headerHeight: Int) { // 头部折叠状态下不触发刷新 if (appBarLayout.totalScrollRange ! 0 Math.abs(appBarLayout.y) 0) { refreshLayout.finishRefresh() } } })更彻底的做法是监听 AppBarLayout 的偏移量当头部未完全展开时禁用 SmartRefreshLayout 的下拉。appBarLayout.addOnOffsetChangedListener { appBar, verticalOffset - val isFullyExpanded verticalOffset 0 refreshLayout.setEnableRefresh(isFullyExpanded) }这样只有头部完全展开、列表滚到顶部时下拉才会触发刷新逻辑清晰不会打架。6.3 症状三左右切换标签后纵向滚动错乱这个问题的表现是在第一个标签滚到中间切到第二个标签再切回来第一个标签的滚动位置变了或者头部折叠状态不对。原因在于 ViewPager2 切换页面时AppBarLayout 的折叠状态没有跟着重置。因为 AppBarLayout 是共享的它不知道当前是哪个页面在滚动。解决办法是在页面切换时手动同步折叠状态。监听 ViewPager2 的页面切换回调根据当前页面的滚动位置调整 AppBarLayout。viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { // 切换页面时将头部恢复到展开状态 appBarLayout.setExpanded(true, true) } })如果希望保留每个页面的折叠状态就需要在每个 Fragment 里记录自己的滚动位置切换时恢复。这个方案复杂一些但体验更好。我的建议是如果头部内容不多直接重置为展开状态就够了如果头部很高用户经常需要折叠状态那就做状态保存。6.4 症状四快速滑动时头部折叠卡顿快速滑动时头部折叠不跟手或者出现跳变通常是两个原因一是折叠动画在主线程做了耗时操作二是snap标志导致的吸附行为在快速滑动时表现异常。先检查addOnOffsetChangedListener里有没有做复杂计算或者频繁的布局请求。如果有把计算逻辑简化或者用View.post延迟处理。如果用了snap标志快速滑动时可能会出现头部在中间状态反复吸附的情况。可以尝试去掉snap或者只在特定条件下启用。// 根据滑动速度动态控制snap appBarLayout.addOnOffsetChangedListener { appBar, verticalOffset - val velocity ... // 获取当前滑动速度 val layoutParams appBar.layoutParams as AppBarLayout.LayoutParams if (Math.abs(velocity) 5000) { layoutParams.scrollFlags SCROLL_FLAG_SCROLL or SCROLL_FLAG_EXIT_UNTIL_COLLAPSED } else { layoutParams.scrollFlags SCROLL_FLAG_SCROLL or SCROLL_FLAG_EXIT_UNTIL_COLLAPSED or SCROLL_FLAG_SNAP } }这个方案稍微有点重但实测在低端机上效果明显。7. 性能优化与体验打磨的实战心得7.1 图片头部的加载与内存控制头部图片通常是高分辨率的大图如果直接加载原图在折叠动画过程中会频繁触发绘制导致掉帧。我的做法是用 Glide 加载并做尺寸压缩。Glide.with(this) .load(headerUrl) .override(1080, 600) // 限制解码尺寸 .centerCrop() .into(ivHeader)override指定解码后的宽高避免加载原图占用过多内存。同时给 ImageView 设置android:scaleTypecenterCrop保证图片填满区域不变形。另外如果头部图片不需要在折叠时做缩放动画可以关掉硬件加速的过度绘制。在 XML 里给 ImageView 加android:layerTypehardware让 GPU 缓存图层减少重复绘制。7.2 RecyclerView 的嵌套滑动优化RecyclerView 默认支持嵌套滑动但在多层嵌套场景下可以做一些优化来提升流畅度。第一设置setHasFixedSize(true)如果列表项高度固定这个设置能避免每次数据变化都重新测量。第二使用setItemViewCacheSize增加缓存数量减少滑动时的 View 创建。recyclerView.apply { setHasFixedSize(true) itemAnimator null // 关闭默认动画减少滑动时的计算 setItemViewCacheSize(10) recycledViewPool.setMaxRecycledViews(0, 20) }第三如果列表项布局复杂考虑用ConstraintLayout减少层级或者用ViewBinding替代findViewById减少查找开销。7.3 刷新指示器的自定义与状态保持SmartRefreshLayout 自带的 ClassicsHeader 够用但如果想和 AppBarLayout 的折叠动画配合得更好可以自定义一个 Header。自定义 Header 需要继承RefreshHeader并实现几个关键方法。核心思路是在下拉过程中根据下拉距离改变指示器的透明度和旋转角度让刷新动画和头部折叠形成视觉上的连贯。class CustomHeader(context: Context) : LinearLayout(context), RefreshHeader { private var state: RefreshState RefreshState.None override fun onMoving(isDragging: Boolean, percent: Float, offset: Int, last: Int, headerHeight: Int) { // 根据percent控制指示器透明度 indicator.alpha percent.coerceIn(0f, 1f) indicator.rotation percent * 360 } override fun onStateChanged(state: RefreshState) { this.state state when (state) { is RefreshState.Release - indicator.animate().rotation(180f) is RefreshState.Refreshing - startLoadingAnimation() is RefreshState.None - resetIndicator() else - {} } } }提示自定义 Header 时要注意onMoving会被频繁调用里面不要做耗时操作也不要频繁请求布局。7.4 状态栏与导航栏的适配细节沉浸式状态下AppBarLayout 需要处理状态栏高度。如果直接设置fitsSystemWindowstrueAppBarLayout 会自动加上状态栏高度的内边距但折叠时这个内边距不会消失可能导致头部折叠后仍然留有一条状态栏高度的空白。更精细的做法是手动计算状态栏高度给头部内容区加 padding而不是给 AppBarLayout 加。val statusBarHeight resources.getDimensionPixelSize( resources.getIdentifier(status_bar_height, dimen, android) ) ivHeader.updatePadding(top statusBarHeight)这样折叠时图片可以完全滚出状态栏区域视觉效果更干净。8. 几个容易忽略的边界场景8.1 数据为空时的占位处理列表数据为空时如果直接显示空白用户会以为页面坏了。需要在 SmartRefreshLayout 内部加一个空状态视图并且保证空状态也能触发下拉刷新。com.scwang.smart.refresh.layout.SmartRefreshLayout FrameLayout RecyclerView ... / LinearLayout android:idid/emptyView android:visibilitygone android:gravitycenter TextView android:text暂无数据下拉刷新试试 / /LinearLayout /FrameLayout /com.scwang.smart.refresh.layout.SmartRefreshLayout注意空状态视图要放在 RecyclerView 的兄弟层级不要放在 RecyclerView 内部否则下拉刷新会失效。8.2 快速切换标签时的 Fragment 生命周期ViewPager2 的FragmentStateAdapter默认只保留当前页面和相邻页面。快速切换标签时Fragment 可能被销毁重建如果数据加载逻辑放在onCreate里会重复请求。我的做法是把数据请求放在 ViewModel 里Fragment 只负责观察数据。ViewModel 的生命周期比 Fragment 长切换标签时数据不会丢失。class ListViewModel : ViewModel() { private val _data MutableLiveDataListItem() val data: LiveDataListItem _data private var isLoaded false fun loadIfNeeded() { if (isLoaded) return isLoaded true // 执行网络请求 } }8.3 折叠状态下的点击事件穿透AppBarLayout 折叠后如果头部还有可点击的按钮折叠到不可见区域后点击事件仍然可能被触发。需要在折叠监听里动态控制点击事件的启用状态。appBarLayout.addOnOffsetChangedListener { appBar, verticalOffset - val isCollapsed Math.abs(verticalOffset) appBar.totalScrollRange ivHeader.isClickable !isCollapsed }这个细节很小但如果不处理用户点击折叠后的空白区域可能会触发头部的跳转逻辑体验很突兀。9. 写在最后的一些个人体会这套 CoordinatorLayout AppBarLayout TabLayout ViewPager2 SmartRefreshLayout 的组合本质上是在解决一个“多层嵌套下滑动事件如何有序传递”的问题。每个控件都有自己的滚动逻辑把它们放在一起就像让五个各有想法的人合作完成一件事必须明确谁先谁后、谁听谁的。我踩过的坑主要集中在两个地方一是 SmartRefreshLayout 的嵌套滑动开关二是 AppBarLayout 的折叠状态同步。前者导致头部不折叠后者导致切换标签后状态错乱。这两个问题的排查过程让我意识到嵌套滑动的问题不能靠猜必须理解NestedScrollingParent和NestedScrollingChild的传递机制知道事件从手指按下到最终消费经过了哪些环节。如果让我给正在做类似页面的同行一个建议那就是先把最简结构跑通再逐个加功能。先搭 CoordinatorLayout AppBarLayout ViewPager2确认头部能正常折叠再加 TabLayout 联动最后在每个 Fragment 里加 SmartRefreshLayout。每加一个控件就测试一次滑动出问题立刻定位比全部堆完再排查要高效得多。另外不同厂商的 ROM 对嵌套滑动的处理有细微差异尤其是折叠动画的流畅度。建议在低端机和高端机上分别测试如果低端机掉帧严重优先检查图片加载和列表项布局层级这两个是性能大头。

相关新闻

Python tkinter绘制实时波形图:从坐标变换到性能优化

Python tkinter绘制实时波形图:从坐标变换到性能优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 1:09:34 阅读更多 →
Android高性能NV21转Bitmap与Bitmap透明度混合工具类实战

Android高性能NV21转Bitmap与Bitmap透明度混合工具类实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 1:09:34 阅读更多 →
GPIO模拟SPI驱动OLED:时序原理与代码实现详解

GPIO模拟SPI驱动OLED:时序原理与代码实现详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 1:09:34 阅读更多 →

最新新闻

Familjen Grotesk 字体深度解析:ink trap 设计语言与 Fontsource 可变字体实战指南

Familjen Grotesk 字体深度解析:ink trap 设计语言与 Fontsource 可变字体实战指南

前端CLI开发工具 【免费下载链接】fontsource Self-host Open Source fonts in neatly bundled NPM packages. 项目地址: https://gitcode.com/gh_mirrors/fo/fontsource 点击查看 免费下载 Familjen Grotesk 是瑞典设计工作室 Familjen STHLM 出品的当代 sans-ser…

2026/10/16 1:56:44 阅读更多 →
三步跑通抖音批量下载:douyin-downloader 让主页作品一次下完

三步跑通抖音批量下载:douyin-downloader 让主页作品一次下完

三步跑通抖音批量下载:douyin-downloader 让主页作品一次下完 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallbac…

2026/10/16 1:56:44 阅读更多 →
易语言文件操作四函数详解:判断存在、取尺寸、取时间与更名

易语言文件操作四函数详解:判断存在、取尺寸、取时间与更名

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 1:56:44 阅读更多 →
STM32+MQTT+TencentOS tiny:物联网设备上云移植指南

STM32+MQTT+TencentOS tiny:物联网设备上云移植指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 1:56:44 阅读更多 →
GoAnime 终端 TUI 实现解析:用 Charm bubbletea 打造流畅交互式追番界面

GoAnime 终端 TUI 实现解析:用 Charm bubbletea 打造流畅交互式追番界面

【免费下载链接】GoAnime Search, stream and download anime, movies and TV shows from the terminal, in English and Brazilian Portuguese. 项目地址: https://gitcode.com/gh_mirrors/go/GoAnime 点击查看 免费下载 GoAnime 是一款用 Go 编写的终端追番工具&…

2026/10/16 1:56:44 阅读更多 →
TheBigPromptLibrary 实战解析:Astrology Birth Chart GPT 自定义指令的数据采集、盘面计算与行为边界设计

TheBigPromptLibrary 实战解析:Astrology Birth Chart GPT 自定义指令的数据采集、盘面计算与行为边界设计

提示工程AI 安全治理 【免费下载链接】TheBigPromptLibrary A collection of prompts, system prompts and LLM instructions 项目地址: https://gitcode.com/gh_mirrors/th/TheBigPromptLibrary 点击查看 免费下载 导读 本文以 TheBigPromptLibrary 仓库中收录的…

2026/10/16 1:55:44 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →