(AllAppsContainerView)RecyclerView+GridLayoutManager+GridLayoutManager.SpanSizeLookup实现自定义网格大小的网格布局

Launcher3的AllAppsContainerView类似实现:

RecyclerView + GridLayoutManager + SpanSizeLookup 实现自定义网格布局

核心组件功能

  1. RecyclerView

    • 高效展示大量数据的容器,支持滚动和视图回收复用
  2. GridLayoutManager

    • 提供网格布局能力,可设置列数(spanCount)和方向
  3. GridLayoutManager.SpanSizeLookup

    • 核心控制类,用于动态指定每个位置的Item应占据的列数

实现步骤与示例代码

1. 布局文件(activity_main.xml)
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:padding="16dp"/>
2. 数据模型(ItemModel.java)
public class ItemModel {
    private int type;       // 视图类型
    private String title;   // 显示文本
    
    public ItemModel(int type, String title) {
        this.type = type;
        this.title = title;
    }
    
    // Getters and Setters
    public int getType() { return type; }
    public String getTitle() { return title; }
}
3. 适配器(CustomGridAdapter.java)
public class CustomGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int TYPE_HEADER = 0;     // 标题类型
    private static final int TYPE_NORMAL = 1;     // 普通项类型
    private static final int TYPE_WIDE = 2;       // 宽项类型
    
    private List<ItemModel> itemList;
    
    public CustomGridAdapter(List<ItemModel> itemList) {
        this.itemList = itemList;
    }
    
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View view;
        
        if (viewType == TYPE_HEADER) {
            view = inflater.inflate(R.layout.item_header, parent, false);
            return new HeaderViewHolder(view);
        } else if (viewType == TYPE_WIDE) {
            view = inflater.inflate(R.layout.item_wide, parent, false);
            return new WideViewHolder(view);
        } else {
            view = inflater.inflate(R.layout.item_normal, parent, false);
            return new NormalViewHolder(view);
        }
    }
    
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        ItemModel item = itemList.get(position);
        
        if (holder instanceof HeaderViewHolder) {
            ((HeaderViewHolder) holder).textView.setText(item.getTitle());
        } else if (holder instanceof WideViewHolder) {
            ((WideViewHolder) holder).textView.setText(item.getTitle());
        } else {
            ((NormalViewHolder) holder).textView.setText(item.getTitle());
        }
    }
    
    @Override
    public int getItemCount() {
        return itemList.size();
    }
    
    @Override
    public int getItemViewType(int position) {
        return itemList.get(position).getType();
    }
    
    // 定义ViewHolder类
    static class HeaderViewHolder extends RecyclerView.ViewHolder {
        TextView textView;
        HeaderViewHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.header_text);
        }
    }
    
    static class WideViewHolder extends RecyclerView.ViewHolder {
        TextView textView;
        WideViewHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.wide_text);
        }
    }
    
    static class NormalViewHolder extends RecyclerView.ViewHolder {
        TextView textView;
        NormalViewHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.normal_text);
        }
    }
}
4. 自定义SpanSizeLookup(CustomSpanSizeLookup.java)
public class CustomSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
    private CustomGridAdapter adapter;
    private int totalSpans;
    
    public CustomSpanSizeLookup(CustomGridAdapter adapter, int totalSpans) {
        this.adapter = adapter;
        this.totalSpans = totalSpans;
        setSpanIndexCacheEnabled(true); // 启用缓存提高性能
    }
    
    @Override
    public int getSpanSize(int position) {
        int viewType = adapter.getItemViewType(position);
        
        switch (viewType) {
            case CustomGridAdapter.TYPE_HEADER:
                return totalSpans; // 标题占据整行
            case CustomGridAdapter.TYPE_WIDE:
                return totalSpans / 2; // 宽项占据半行(假设总列数为4)
            case CustomGridAdapter.TYPE_NORMAL:
            default:
                return 1; // 普通项占据1列
        }
    }
}
5. Activity中配置RecyclerView(MainActivity.java)
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        recyclerView = findViewById(R.id.recyclerView);
        
        // 准备数据
        List<ItemModel> itemList = prepareItems();
        
        // 创建适配器
        CustomGridAdapter adapter = new CustomGridAdapter(itemList);
        
        // 设置GridLayoutManager,总列数为4
        int spanCount = 4;
        GridLayoutManager layoutManager = new GridLayoutManager(this, spanCount);
        
        // 设置SpanSizeLookup
        layoutManager.setSpanSizeLookup(new CustomSpanSizeLookup(adapter, spanCount));
        
        // 配置RecyclerView
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(adapter);
    }
    
    private List<ItemModel> prepareItems() {
        List<ItemModel> items = new ArrayList<>();
        
        // 添加标题
        items.add(new ItemModel(CustomGridAdapter.TYPE_HEADER, "标题"));
        
        // 添加普通项
        for (int i = 1; i <= 8; i++) {
            items.add(new ItemModel(CustomGridAdapter.TYPE_NORMAL, "普通项 " + i));
        }
        
        // 添加宽项
        items.add(new ItemModel(CustomGridAdapter.TYPE_WIDE, "宽项"));
        
        // 添加更多普通项
        for (int i = 9; i <= 12; i++) {
            items.add(new ItemModel(CustomGridAdapter.TYPE_NORMAL, "普通项 " + i));
        }
        
        return items;
    }
}

关键概念解析

  1. SpanSizeLookup的作用

    • getSpanSize(int position)方法返回值决定了Item在网格中占据的列数
    • 返回值范围为1到spanCount(总列数)
    • 例如:总列数为4时,返回2表示该Item占据2列宽度
  2. 性能优化

    • setSpanIndexCacheEnabled(true)启用缓存,避免重复计算相同位置的spanSize
    • 适合固定布局模式的列表,提升滚动流畅度
  3. 常见应用场景

    • 标题行占据整行宽度
    • 广告卡片占据半行或多行宽度
    • 实现瀑布流布局(结合不同高度的Item)

效果示意图

总列数 = 4

+-----------------------------------+
|              标题                 |  (TYPE_HEADER, span=4)
+--------+--------+--------+--------+
| 普通项1 | 普通项2 | 普通项3 | 普通项4 |  (TYPE_NORMAL, span=1)
+--------+--------+--------+--------+
| 普通项5 | 普通项6 | 普通项7 | 普通项8 |
+--------+--------+--------+--------+
|              宽项                 |  (TYPE_WIDE, span=2)
+--------+--------+--------+--------+
| 普通项9 | 普通项10| 普通项11| 普通项12|
+--------+--------+--------+--------+

注意事项

  1. 总列数(spanCount)选择

    • 应根据设计需求和屏幕尺寸合理选择
    • 手机端通常为2-4列,平板端可更多
  2. 视图类型与布局文件匹配

    • 确保getItemViewType返回值与onCreateViewHolder中的处理一致
  3. 宽项尺寸适配

    • 宽项的布局文件宽度应设置为match_parent以占满分配的空间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值