Android 首页性能优化:如何用列表实现滚动按需加载替代NestedScrollView
在应用主页面中,我使用RecyclerView创建了10个条目数量有限的列表模块,页面顶部设置有图片轮播控件与2个普通ImageView。每个列表模块都封装在独立的Fragment中,我在MainActivity中初始化所有Fragment后,将其添加到NestedScrollView内部的FrameLayout容器中。
我观察到同类应用会在用户向下滚动时才创建对应视图,推测这类应用大概率使用了ListView的回收复用机制。但我的应用当前会一次性创建所有视图,我担心这会延长首页的加载时长,对应用性能造成负面影响。
我不清楚如何实现包含多类型视图的列表结构(适配我当前的页面布局),下方是我当前MainActivity的布局代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:animateLayoutChanges="true" android:id="@+id/submaincontainer"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="50dp" android:background="#f8f8f8" app:popupTheme="@style/AppTheme.PopupOverlay" android:layoutDirection="rtl"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <RelativeLayout android:id="@+id/searchlay" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:background="@drawable/simplebacklessroundtool" android:layout_margin="4dp"> <ImageView android:id="@+id/search" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/search" android:layout_centerVertical="true" android:layout_marginStart="10dp" /> <TextView android:layout_toEndOf="@id/search" android:id="@+id/b1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="جستجو در " android:layout_centerVertical="true" android:layout_marginEnd="-20dp" android:layout_marginStart="10dp" /> <ImageView android:layout_toLeftOf="@id/b1" android:layout_centerVertical="true" android:src="@drawable/sinbadred" android:layout_width="100dp" android:layout_height="wrap_content" android:padding="4dp" android:layout_marginTop="2dp" /> </RelativeLayout> <ProgressBar android:id="@+id/toolbarprog" android:layout_width="wrap_content" android:layout_height="wrap_content" android:indeterminateDrawable="@drawable/progress" android:layout_alignParentEnd="true" android:visibility="gone" /> </LinearLayout> </androidx.appcompat.widget.Toolbar> <androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/navigator" android:layout_below="@+id/toolbar"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <androidx.cardview.widget.CardView android:id="@+id/slidercontainer" android:layout_width="match_parent" android:layout_height="200dp" android:layout_marginBottom="10dp" android:layout_marginEnd="10dp" android:layout_marginStart="10dp" app:cardCornerRadius="6dp"> <com.denzcoskun.imageslider.ImageSlider android:id="@+id/image_slider" android:layout_width="wrap_content" android:layout_height="match_parent" app:iss_auto_cycle="true" app:iss_delay="0" app:iss_period="2500" /> </androidx.cardview.widget.CardView> <FrameLayout android:id="@+id/containerhorizsubgroup" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" /> <FrameLayout android:id="@+id/containerdiscount" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <FrameLayout android:id="@+id/containeruserseensubg" android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@+id/containernews" android:layout_width="match_parent" android:layout_height="wrap_content" /> <ImageView android:id="@+id/poster0" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginStart="15dp" android:layout_marginEnd="15dp" /> <FrameLayout android:id="@+id/userseencontainer" android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@+id/offercontainer" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="15dp" /> <FrameLayout android:id="@+id/containercustomsubgroup" android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@+id/mostvisitedcontainer" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="15dp" /> <FrameLayout android:id="@+id/userseenbygroupcontainer" android:layout_width="match_parent" android:layout_height="wrap_content" /> <ImageView android:id="@+id/poster1" android:layout_marginTop="20dp" android:layout_marginBottom="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="15dp" android:layout_marginEnd="15dp" /> <FrameLayout android:id="@+id/mostSoldcontainer" android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@+id/recentcontainer" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="15dp" /> </LinearLayout> </androidx.core.widget.NestedScrollView> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/navigator" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="#f8f8f8" android:layoutDirection="rtl" app:itemIconTint="@drawable/tab_color_selector" app:itemTextColor="@drawable/tab_color_selector" app:labelVisibilityMode="labeled" app:menu="@menu/bottomnavmenu" /> </RelativeLayout>
我将包含RecyclerView的Fragment加载到上述各个FrameLayout中。请问如何才能用列表(如ListView)实现该首页结构,让视图不再一次性创建,而是滚动到对应位置时才进行创建?我附了应用主页面截图作为参考。
核心问题根源
- 你当前使用
NestedScrollView作为滚动容器,该组件会强制测量并实例化所有子View,无论是否处于屏幕可见区域,这就是所有视图一次性加载、首页启动慢的根本原因,在内部嵌套Fragment或RecyclerView无法解决该问题。 - 同类应用的懒加载效果,本质是采用了支持回收复用的滚动容器,ListView的回收机制现已过时,优先使用RecyclerView实现该类需求。
实现方案
根据你的改动需求,可选择以下两种方案:
方案一:低改动量,保留原有Fragment逻辑
无需修改现有Fragment的内部代码,仅重构外层滚动容器即可:
- 重构首页布局,移除原有
NestedScrollView,替换为垂直方向的RecyclerView,原有Toolbar、底部导航栏保持不变:
<!-- 仅展示需要修改的部分,其余布局和原有一致 --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/homeRecyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/navigator" android:layout_below="@+id/toolbar" android:overScrollMode="never" />
- 定义自定义Adapter继承
FragmentStateAdapter,按页面从上到下的顺序映射每个位置对应的Fragment:
class HomeAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) { // 按页面显示顺序排列对应的Fragment类 private val fragmentOrder = listOf( SliderFragment::class.java, ModuleHorizSubgroupFragment::class.java, ModuleDiscountFragment::class.java, // 中间省略其余模块的Fragment PosterFragment::class.java, ModuleUserSeenFragment::class.java, // 以此类推,匹配原有页面的布局顺序 ) override fun getItemCount(): Int = fragmentOrder.size override fun createFragment(position: Int): Fragment { // 可根据position给Fragment传参,比如横幅的图片地址 return when (position) { /* 海报位置的示例 */ 4 -> PosterFragment().apply { arguments = bundleOf("img_url" to "海报图片地址") } else -> fragmentOrder[position].newInstance() } } }
- 给外层RecyclerView设置
LinearLayoutManager(垂直方向)和上述Adapter即可。
该方案的特性:RecyclerView只会在item即将进入屏幕时创建对应的Fragment,滑出屏幕的Fragment会被自动回收,首屏仅加载初始可见的2-3个模块,启动速度会有明显提升。
方案二:性能最优,移除冗余Fragment
单个页面内的功能模块不需要用Fragment包裹,直接将原有Fragment的布局抽为独立的item布局,可减少Fragment实例化的额外开销,性能更高:
- 同样将外层滚动容器替换为RecyclerView。
- 定义多ViewType的Adapter,为顶部轮播、普通横幅、内容模块分别定义对应的ViewType和item布局,按页面顺序维护item数据列表。
- 在Adapter的
onCreateViewHolder中按ViewType加载对应布局,onBindViewHolder中绑定对应数据。
注意事项
- 若模块内部有横向滚动的列表(如横向商品流),需给横向RecyclerView设置
android:nestedScrollingEnabled="false",避免和外层RecyclerView产生滑动冲突。 - 所有item布局需设置有效高度(固定高度或wrap_content可正常测量),避免外层RecyclerView测量异常。
- 不要给外层RecyclerView设置
setHasFixedSize(true),除非所有item的高度完全一致。
内容的提问来源于stack exchange,提问作者saber javadi

