呼叫 adapter.notifyDataSetChanged() 後,需要在 ListView 完成重繪/刷新後執行後續操作(如滾動到特定位置、顯示動畫、啟用按鈕等),但該方法是非同步的,無直接回調。
public class MyListAdapter extends BaseAdapter {
private static final int MSG_REFRESH_COMPLETE = 1001;
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_REFRESH_COMPLETE) {
if (onDataSetChangedListener != null) {
onDataSetChangedListener.onRefreshComplete();
}
}
}
};
public interface OnDataSetChangedListener {
void onRefreshComplete();
}
private OnDataSetChangedListener onDataSetChangedListener;
public void setOnDataSetChangedListener(OnDataSetChangedListener listener) {
this.onDataSetChangedListener = listener;
}
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
// 發送延遲訊息,讓 UI 執行緒處理完重繪後回調
mHandler.sendEmptyMessageDelayed(MSG_REFRESH_COMPLETE, 0);
}
}
MyListAdapter adapter = new MyListAdapter(dataList);
adapter.setOnDataSetChangedListener(() -> {
// ListView 刷新完成後的操作
listView.smoothScrollToPosition(targetPosition);
// 或
button.setEnabled(true);
// 或
startAnimation();
});
listView.setAdapter(adapter);
// 更新資料
dataList.add(newItem);
adapter.notifyDataSetChanged(); // 會觸發 onRefreshComplete 回調
notifyDataSetChanged() 內部會發送 Handler 訊息請求重繪super.notifyDataSetChanged() 後再發送一個 sendEmptyMessageDelayed(..., 0)
adapter.notifyDataSetChanged();
listView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 移除監聽器避免重複觸發
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
listView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
listView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
// 佈局完成,可安全操作
listView.setSelection(targetPosition);
}
});
// RecyclerView 內建動畫與回調
recyclerView.setItemAnimator(new DefaultItemAnimator());
adapter.notifyDataSetChanged(); // 或 notifyItemInserted/Removed/Changed
// 使用 LayoutManager 監聽滾動/佈局完成
recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
// 佈局完成
recyclerView.removeOnLayoutChangeListener(this);
// 執行後續操作
}
});
adapter.notifyDataSetChanged();
// 在 UI 執行緒佇列末尾執行,通常在重繪之後
listView.post(() -> {
// ListView 應已完成重繪
listView.smoothScrollToPosition(position);
});
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 自定義 Adapter 覆寫 | 封裝性好、可復用、精確控制 | 需自定義 Adapter 類別 | 專案中多處需求、庫開發 |
| ViewTreeObserver | 不需修改 Adapter、通用 | 可能觸發多次、需手動移除 | 單一頁面臨時需求 |
| RecyclerView 現代 API | 效能好、動畫豐富、架構清晰 | 需遷移至 RecyclerView | 新專案、長期維護專案 |
| listView.post() | 最簡單、一行代碼 | 非保證、視裝置效能而定 | 非關鍵路徑、簡單延遲 |
notifyDataSetChanged() 會觸發完整重繪,大量資料建議用 notifyItemInserted/Removed/Changed 精確更新onRefreshComplete 中再次修改資料並呼叫 notifyDataSetChanged(),會造成遞迴,需加旗標防護Looper.getMainLooper() 確保在主執行緒removeOnGlobalLayoutListener,舊版用 removeGlobalOnLayoutListener最穩健方案是**自定義 Adapter 覆寫 notifyDataSetChanged() 發送 Handler 訊息回調**。簡單場景可用 listView.post()。現代專案強烈建議遷移至 RecyclerView 獲得更完善的動畫、回調與效能體系。