首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Android ListView notifyDataSetChanged 完成回調監聽實作

     編輯:程式設計綜合問答

問題現象

呼叫 adapter.notifyDataSetChanged() 後,需要在 ListView 完成重繪/刷新後執行後續操作(如滾動到特定位置、顯示動畫、啟用按鈕等),但該方法是非同步的,無直接回調。

解決方案:自定義 Adapter 覆寫 notifyDataSetChanged


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 訊息請求重繪
  • 重繪在 UI 執行緒的下一個訊息循環執行
  • 我們在 super.notifyDataSetChanged() 後再發送一個 sendEmptyMessageDelayed(..., 0)
  • 這個訊息會排在重繪訊息**之後**執行,確保刷新完成

替代方案一:ViewTreeObserver.OnGlobalLayoutListener


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 + ItemAnimator(現代推薦)


// 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);
        // 執行後續操作
    }
});

替代方案三:post(Runnable) 簡單延遲執行


adapter.notifyDataSetChanged();

// 在 UI 執行緒佇列末尾執行,通常在重繪之後
listView.post(() -> {
    // ListView 應已完成重繪
    listView.smoothScrollToPosition(position);
});

方案對比

方案優點缺點適用場景
自定義 Adapter 覆寫封裝性好、可復用、精確控制需自定義 Adapter 類別專案中多處需求、庫開發
ViewTreeObserver不需修改 Adapter、通用可能觸發多次、需手動移除單一頁面臨時需求
RecyclerView 現代 API效能好、動畫豐富、架構清晰需遷移至 RecyclerView新專案、長期維護專案
listView.post()最簡單、一行代碼非保證、視裝置效能而定非關鍵路徑、簡單延遲

注意事項

  • notifyDataSetChanged() 會觸發完整重繪,大量資料建議用 notifyItemInserted/Removed/Changed 精確更新
  • 若在 onRefreshComplete 中再次修改資料並呼叫 notifyDataSetChanged(),會造成遞迴,需加旗標防護
  • Handler 應使用 Looper.getMainLooper() 確保在主執行緒
  • API 16+ 用 removeOnGlobalLayoutListener,舊版用 removeGlobalOnLayoutListener

總結

最穩健方案是**自定義 Adapter 覆寫 notifyDataSetChanged() 發送 Handler 訊息回調**。簡單場景可用 listView.post()。現代專案強烈建議遷移至 RecyclerView 獲得更完善的動畫、回調與效能體系。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved