onBindViewHolder 全量觸發卡頓:佈局測量導致的錯誤回收機制呼叫 notifyDataSetChanged() 或 notifyItemRangeInserted() 後,**所有 Item(含螢幕外)都觸發 onBindViewHolder** 導致大量運算、嚴重卡頓。
RecyclerView 尺寸不確定當 RecyclerView 本身**高度/寬度為 wrap_content、或受 layout_weight、PercentLayout、ConstraintLayout 鏈約束影響**,導致其在佈局階段**無法確定確切尺寸**,RecyclerView 會進入「測量模式」:先佈局所有 Item 以計算總高度,再決定捲動範圍。此過程會建立並綁定所有 ViewHolder,即使它們不在可視區域。
// 在 onBindViewHolder 加日誌
@Override
public void onBindViewHolder(VH holder, int position) {
Log.d("RV", "bind pos=" + position + " visible=" + isViewVisible(holder.itemView));
}
若日誌顯示位置遠超 getChildCount() 範圍,即為全量綁定。
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" // 或具體 dp,如 500dp
... />
若在 ScrollView / NestedScrollView 內,**絕不可用** wrap_content,需改用 NestedScrollingChild 機制或 RecyclerView 內建捲動。
recyclerView.post(() -> {
int height = getResources().getDisplayMetrics().heightPixels - toolbarHeight - tabHeight;
recyclerView.getLayoutParams().height = height;
recyclerView.requestLayout();
});
layout_weight 直接作用於 RecyclerView: 改用 ConstraintLayout 鏈(app:layout_constraintVertical_chainStyle="spread")分配空間。setHasFixedSize(true):
recyclerView.setHasFixedSize(true); // 告知 RV Item 尺寸不隨內容變化,可跳過請求佈局
前提:Item 佈局高度固定(wrap_content 但內容固定、或 match_parent、或具體 dp)。
DiffUtil + ListAdapter 替代 notifyDataSetChanged: 精準計算差異,僅觸發必要的 onBindViewHolder。RecyclerView 繼承 ViewGroup,其 onMeasure 中會檢查 hasFixedSize 標誌與 LayoutManager 的 canScrollHorizontally/Vertically。若尺寸不定且可捲動,LinearLayoutManager 會在 onLayoutChildren 階段**預佈局所有 Item** 以獲得總長度,導致全量 onBindViewHolder。固定尺寸後,RecyclerView 知道可視範圍,僅請求佈局可視 Item + 緩衝區。
RecyclerView 直接放在 ScrollView + wrap_contentRecyclerView 設 layout_weight 且無固定基準尺寸wrap_content 且內容動態極大(如長文本、圖片未固定尺寸)match_parent / 固定 dp / setHasFixedSize(true) / ConstraintLayout 鏈總結: onBindViewHolder 全量觸發 99% 是 RecyclerView 尺寸不確定導致預佈局。**給 RV 確定尺寸、啟用 setHasFixedSize(true)、改用 DiffUtil**,卡頓即刻消失。