You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Volley从JSON获取并排序分类与子分类数据并展示于RecyclerView

解决主分类与子分类的RecyclerView展示问题

首先,你的核心问题出在数据模型设计不合理和数据解析逻辑错误上,导致子分类无法正确关联到主分类,同时列表结构混乱。咱们一步步来解决:

1. 重构数据模型

把主分类和子分类拆成两个独立的类,让主分类类包含子分类列表,这样能清晰建立层级关系:

Category.java(主分类类)

public class Category {
    private String categoryId;
    private String categoryName;
    private String categoryImage;
    private List<Subcategory> subcategories;

    public Category() {
        subcategories = new ArrayList<>(); // 初始化子分类列表
    }

    // 带参数构造器
    public Category(String categoryId, String categoryName, String categoryImage) {
        this.categoryId = categoryId;
        this.categoryName = categoryName;
        this.categoryImage = categoryImage;
        this.subcategories = new ArrayList<>();
    }

    // Getter & Setter
    public String getCategoryId() { return categoryId; }
    public void setCategoryId(String categoryId) { this.categoryId = categoryId; }
    public String getCategoryName() { return categoryName; }
    public void setCategoryName(String categoryName) { this.categoryName = categoryName; }
    public String getCategoryImage() { return categoryImage; }
    public void setCategoryImage(String categoryImage) { this.categoryImage = categoryImage; }
    public List<Subcategory> getSubcategories() { return subcategories; }
    public void addSubcategory(Subcategory subcategory) { subcategories.add(subcategory); }
}

Subcategory.java(子分类类)

public class Subcategory {
    private String subcategoryId;
    private String subcategoryName;

    public Subcategory() {}

    public Subcategory(String subcategoryId, String subcategoryName) {
        this.subcategoryId = subcategoryId;
        this.subcategoryName = subcategoryName;
    }

    // Getter & Setter
    public String getSubcategoryId() { return subcategoryId; }
    public void setSubcategoryId(String subcategoryId) { this.subcategoryId = subcategoryId; }
    public String getSubcategoryName() { return subcategoryName; }
    public void setSubcategoryName(String subcategoryName) { this.subcategoryName = subcategoryName; }
}

2. 修正数据解析逻辑

原来的代码把子分类直接加到全局列表,还因为索引问题覆盖了主分类。现在我们要把每个子分类关联到对应的主分类,再统一管理主分类列表:

final JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.POST, url, jsonObject, 
    new Response.Listener<JSONObject>() { 
        @Override 
        public void onResponse(JSONObject response) { 
            Log.d(TAG, response.toString()); 
            try { 
                JSONArray jArray = response.getJSONArray("data");
                List<Category> categoryList = new ArrayList<>(); // 主分类列表

                for (int i = 0; i < jArray.length(); i++) { 
                    JSONObject json_data = jArray.getJSONObject(i);
                    // 创建主分类对象
                    Category category = new Category();
                    category.setCategoryId(json_data.getString("category_id"));
                    category.setCategoryName(json_data.getString("category_name"));
                    category.setCategoryImage(json_data.getString("category_image"));

                    // 解析子分类并关联到主分类
                    JSONArray jArray1 = json_data.getJSONArray("subcategory");
                    for (int j = 0; j < jArray1.length(); j++) { 
                        JSONObject json_data1 = jArray1.getJSONObject(j);
                        Subcategory subcategory = new Subcategory();
                        subcategory.setSubcategoryId(json_data1.getString("category_id"));
                        subcategory.setSubcategoryName(json_data1.getString("category_name"));
                        category.addSubcategory(subcategory); // 关联到主分类
                    }

                    categoryList.add(category); // 把主分类加入列表
                }

                // 解决数据降序问题:按分类名称升序排序(也可以按ID排序)
                Collections.sort(categoryList, new Comparator<Category>() {
                    @Override
                    public int compare(Category c1, Category c2) {
                        return c1.getCategoryName().compareToIgnoreCase(c2.getCategoryName());
                        // 按ID排序用这个:return c1.getCategoryId().compareTo(c2.getCategoryId());
                    }
                });

                // 初始化Adapter并绑定RecyclerView
                CategoryAdapter adapter = new CategoryAdapter(categoryList, getApplicationContext());
                RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
                recyclerView.setLayoutManager(mLayoutManager);
                recyclerView.setItemAnimator(new DefaultItemAnimator());
                recyclerView.setAdapter(adapter);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }, 
    new Response.ErrorListener() { 
        @Override 
        public void onErrorResponse(VolleyError error) { 
            VolleyLog.d(TAG, "Error: " + error.getMessage()); 
        } 
    });

3. 实现支持层级展示的RecyclerView Adapter

这里我们用多类型Item的方式,区分主分类和子分类的布局,也可以实现展开收起功能:

首先创建一个辅助类标记Item类型:

public class ListItem {
    public static final int TYPE_CATEGORY = 0;
    public static final int TYPE_SUBCATEGORY = 1;

    private int type;
    private Category category;
    private Subcategory subcategory;

    // 主分类Item构造
    public ListItem(int type, Category category) {
        this.type = type;
        this.category = category;
    }

    // 子分类Item构造
    public ListItem(int type, Subcategory subcategory) {
        this.type = type;
        this.subcategory = subcategory;
    }

    // Getter
    public int getType() { return type; }
    public Category getCategory() { return category; }
    public Subcategory getSubcategory() { return subcategory; }
}

然后写Adapter:

public class CategoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List<ListItem> combinedList;
    private Context context;
    // 记录主分类的展开状态(默认都展开)
    private Map<String, Boolean> expandStatusMap = new HashMap<>();

    public CategoryAdapter(List<Category> categoryList, Context context) {
        this.context = context;
        this.combinedList = new ArrayList<>();
        // 把主分类和子分类合并到一个列表
        for (Category category : categoryList) {
            combinedList.add(new ListItem(ListItem.TYPE_CATEGORY, category));
            expandStatusMap.put(category.getCategoryId(), true); // 默认展开
            // 如果展开,就添加子分类
            if (expandStatusMap.get(category.getCategoryId())) {
                for (Subcategory sub : category.getSubcategories()) {
                    combinedList.add(new ListItem(ListItem.TYPE_SUBCATEGORY, sub));
                }
            }
        }
    }

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

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(context);
        if (viewType == ListItem.TYPE_CATEGORY) {
            View view = inflater.inflate(R.layout.item_category, parent, false);
            return new CategoryViewHolder(view);
        } else {
            View view = inflater.inflate(R.layout.item_subcategory, parent, false);
            return new SubcategoryViewHolder(view);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ListItem item = combinedList.get(position);
        if (item.getType() == ListItem.TYPE_CATEGORY) {
            CategoryViewHolder categoryHolder = (CategoryViewHolder) holder;
            Category category = item.getCategory();
            categoryHolder.tvName.setText(category.getCategoryName());
            // 加载图片用Glide/Picasso
            Glide.with(context).load(category.getCategoryImage()).into(categoryHolder.ivImage);

            // 展开收起点击事件
            categoryHolder.itemView.setOnClickListener(v -> {
                boolean isExpanded = expandStatusMap.get(category.getCategoryId());
                expandStatusMap.put(category.getCategoryId(), !isExpanded);
                // 更新列表
                updateCombinedList();
                notifyDataSetChanged();
            });
        } else {
            SubcategoryViewHolder subHolder = (SubcategoryViewHolder) holder;
            Subcategory subcategory = item.getSubcategory();
            subHolder.tvSubName.setText(subcategory.getSubcategoryName());
        }
    }

    @Override
    public int getItemCount() {
        return combinedList.size();
    }

    // 更新合并列表,根据展开状态决定是否显示子分类
    private void updateCombinedList() {
        combinedList.clear();
        // 先重新获取所有主分类(这里可以优化,比如单独存主分类列表)
        List<Category> categoryList = new ArrayList<>();
        for (ListItem item : combinedList) {
            if (item.getType() == ListItem.TYPE_CATEGORY) {
                categoryList.add(item.getCategory());
            }
        }
        // 重新构建合并列表
        for (Category category : categoryList) {
            combinedList.add(new ListItem(ListItem.TYPE_CATEGORY, category));
            if (expandStatusMap.get(category.getCategoryId())) {
                for (Subcategory sub : category.getSubcategories()) {
                    combinedList.add(new ListItem(ListItem.TYPE_SUBCATEGORY, sub));
                }
            }
        }
    }

    // 主分类ViewHolder
    static class CategoryViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;
        ImageView ivImage;

        public CategoryViewHolder(View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.tv_category_name);
            ivImage = itemView.findViewById(R.id.iv_category_image);
        }
    }

    // 子分类ViewHolder
    static class SubcategoryViewHolder extends RecyclerView.ViewHolder {
        TextView tvSubName;

        public SubcategoryViewHolder(View itemView) {
            super(itemView);
            tvSubName = itemView.findViewById(R.id.tv_subcategory_name);
        }
    }
}

4. 布局文件示例

item_category.xml(主分类布局)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:orientation="horizontal"
    android:background="#f5f5f5">

    <ImageView
        android:id="@+id/iv_category_image"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:scaleType="centerCrop"/>

    <TextView
        android:id="@+id/tv_category_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_gravity="center_vertical"
        android:textSize="18sp"
        android:textStyle="bold"/>
</LinearLayout>

item_subcategory.xml(子分类布局)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:paddingStart="32dp">

    <TextView
        android:id="@+id/tv_subcategory_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"/>
</LinearLayout>

这样修改后,你就能正确展示主分类和关联的子分类,同时解决数据降序的问题,还能实现展开收起的交互效果。


内容的提问来源于stack exchange,提问作者MohammadAli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:13:57