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

Android ListView Item 內 Button 狀態滾動丟失:數據源同步與 ViewHolder 模式徹底解決

     編輯:程式設計綜合問答

現象

ListView 項目含 Button A/B,點擊 Button A 隱藏它。滾動後,其他項目的 Button A 也莫名隱藏,或隱藏狀態錯亂。

根本原因:視圖複用機制

  • ListView 回收 convertView,同一視圖對象被重用於不同位置
  • 若 Button 狀態 (可見/隱藏) 僅存儲在視圖層 (setVisibility),複用時狀態隨視圖移動
  • 必須將狀態存儲在數據模型中,getView 時根據模型恢復視圖狀態

標準解決方案:布爾數組/模型字段同步狀態


// 1. 數據模型增加狀態字段
class TaskItem {
    String title;
    boolean isButtonAVisible = true; // 默認可見
}

// 2. Adapter 綁定時恢復狀態
public class TaskAdapter extends BaseAdapter {
    private List items;

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.item_task, parent, false);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        TaskItem item = items.get(position);
        holder.tvTitle.setText(item.title);
        
        // 關鍵:根據模型設置 Button 狀態
        holder.btnA.setVisibility(item.isButtonAVisible ? View.VISIBLE : View.GONE);
        
        // 關鍵:點擊更新模型,刷新單項
        holder.btnA.setOnClickListener(v -> {
            item.isButtonAVisible = false;
            notifyItemChanged(position); // BaseAdapter 用 notifyDataSetChanged()
        });
        
        return convertView;
    }
}

進階:使用 SparseBooleanArray 優化記憶體

若不想修改模型類,用獨立映射存儲狀態:


private SparseBooleanArray buttonAVisibleMap = new SparseBooleanArray();
// 初始化默認 true
for (int i = 0; i < items.size(); i++) buttonAVisibleMap.put(i, true);

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // ...
    boolean visible = buttonAVisibleMap.get(position, true);
    holder.btnA.setVisibility(visible ? View.VISIBLE : View.GONE);
    
    holder.btnA.setOnClickListener(v -> {
        buttonAVisibleMap.put(position, false);
        notifyDataSetChanged(); // 或局部刷新
    });
}

現代方案:RecyclerView + ListAdapter (自動處理)


class TaskAdapter extends ListAdapter 
    extends ListAdapter(DIFF_CALLBACK) {

    static final DiffUtil.ItemCallback DIFF_CALLBACK = 
        new DiffUtil.ItemCallback() {
            @Override boolean areItemsTheSame(TaskItem old, TaskItem n) {
                return old.id == n.id;
            }
            @Override boolean areContentsTheSame(TaskItem old, TaskItem n) {
                return old.equals(n); // 包含 isButtonAVisible 比較
            }
        };

    class VH extends RecyclerView.ViewHolder {
        Button btnA;
        VH(View v) { super(v); btnA = v.findViewById(R.id.btnA); }
        void bind(TaskItem item) {
            btnA.setVisibility(item.isButtonAVisible ? View.VISIBLE : View.GONE);
            btnA.setOnClickListener(v -> {
                // 創建新對象觸發 DiffUtil 更新
                TaskItem updated = new TaskItem(item.id, item.title, false);
                // 假設列表在 ViewModel 中,通過 LiveData 更新
                viewModel.updateItem(updated); 
            });
        }
    }
}

核心原則總結

  1. 視圖無狀態:所有 UI 狀態必須源自數據模型
  2. getView/onBindViewHolder 只負責「讀模型 → 寫視圖」
  3. 交互只負責「改模型 → 通知刷新」
  4. 使用 notifyItemChanged(position) / DiffUtil 實現局部刷新,避免全量 notifyDataSetChanged() 導致閃爍
  5. 新專案直接用 RecyclerView + ListAdapter,DiffUtil 自動計算最小更新
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved