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

Android ListView 嵌套 ListView 反模式:正確實現多型列表的方案

     編輯:程式設計綜合問答

問題背景

需求:單一畫面顯示多組資料,每組包含標題與多行明細,使用者嘗試用「大 ListView 嵌套小 ListView」實現,導致資料錯亂、位置偏移、回收機制失效。

為什麼不建議 ListView 嵌套 ListView?

  • 滑動衝突:內外雙層滑動事件分發極難處理
  • View 回收失效:內層 ListView 無法正確複用 `convertView`,導致資料錯位
  • 效能災難:每個外層項目建立一個內層 ListView,記憶體與渲染開銷指數級增長
  • 焦點與選取混亂:鍵盤/觸控焦點在兩層間跳轉

正確方案:單一 ListView + 多型 Item (ViewType)

Android `BaseAdapter` 提供 `getViewTypeCount()` 與 `getItemViewType(position)` 機制,支援同一列表內不同佈局:

1. 定義資料模型

// 基礎介面
interface ListItem {
    int getType();
}

// 區塊標題
class SectionHeader implements ListItem {
    String title;
    public int getType() { return 0; }
}

// 明細項目
class DetailItem implements ListItem {
    String name, price;
    public int getType() { return 1; }
}

// 使用 List<ListItem> 作為統一資料源
List<ListItem> items = new ArrayList<>();
items.add(new SectionHeader("湘菜"));
items.add(new DetailItem("小炒肉", "28元"));
items.add(new DetailItem("紅燒肉", "48元"));
items.add(new SectionHeader("川菜"));
items.add(new DetailItem("麻婆豆腐", "18元"));

2. 實作多型 Adapter

public class MultiTypeAdapter extends BaseAdapter {
    private final List<ListItem> data;
    private static final int TYPE_HEADER = 0;
    private static final int TYPE_DETAIL = 1;

    public MultiTypeAdapter(List<ListItem> data) { this.data = data; }

    @Override public int getViewTypeCount() { return 2; }

    @Override public int getItemViewType(int position) {
        return data.get(position).getType();
    }

    @Override public int getCount() { return data.size(); }
    @Override public Object getItem(int p) { return data.get(p); }
    @Override public long getItemId(int p) { return p; }

    @Override public View getView(int position, View convertView, ViewGroup parent) {
        int type = getItemViewType(position);
        ViewHolder holder;
        if (convertView == null) {
            holder = new ViewHolder();
            if (type == TYPE_HEADER) {
                convertView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_header, parent, false);
                holder.tvTitle = convertView.findViewById(R.id.tv_header);
            } else {
                convertView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_detail, parent, false);
                holder.tvName = convertView.findViewById(R.id.tv_name);
                holder.tvPrice = convertView.findViewById(R.id.tv_price);
            }
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        ListItem item = data.get(position);
        if (type == TYPE_HEADER) {
            holder.tvTitle.setText(((SectionHeader)item).title);
        } else {
            DetailItem d = (DetailItem) item;
            holder.tvName.setText(d.name);
            holder.tvPrice.setText(d.price);
        }
        return convertView;
    }

    static class ViewHolder {
        TextView tvTitle, tvName, tvPrice;
    }
}

3. 佈局檔案

<!-- item_header.xml -->
<TextView android:id="@+id/tv_header" style="@style/SectionHeader" />

<!-- item_detail.xml -->
<LinearLayout android:orientation="horizontal">
    <TextView android:id="@+id/tv_name" layout_weight="1"/>
    <TextView android:id="@+id/tv_price" layout_width="wrap_content"/>
</LinearLayout>

進階:RecyclerView 取代 ListView(現代最佳實務)

同樣原理適用於 `RecyclerView.Adapter`,且效能更佳:

public class MultiTypeRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    // ViewHolder 分類
    static class HeaderVH extends RecyclerView.ViewHolder { TextView tv; ... }
    static class DetailVH extends RecyclerView.ViewHolder { TextView name, price; ... }

    @Override public int getItemViewType(int pos) { return data.get(pos).getType(); }

    @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_HEADER)
            return new HeaderVH(inflate(R.layout.item_header, parent));
        else
            return new DetailVH(inflate(R.layout.item_detail, parent));
    }

    @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int pos) {
        ListItem item = data.get(pos);
        if (holder instanceof HeaderVH) ((HeaderVH)holder).bind((SectionHeader)item);
        else ((DetailVH)holder).bind((DetailItem)item);
    }
}

總結對比

方案複雜度效能維護性推薦度
ListView 嵌套 ListView低(表象)極差極差❌ 禁止
單一 ListView + ViewType中良好良好✅ 可用
RecyclerView + 多型 ViewHolder中高最佳最佳🌟 推薦
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved