Android横向嵌套纵向RecyclerView实现3x3网格加载数据库图片
目标效果参考:
实现方案
整体采用外层横向RecyclerView+内层纵向RecyclerView的嵌套结构:外层横向RecyclerView承载可左右滑动的卡片项,每个卡片项用CardView作为根容器包裹3x3网格,CardView内部嵌套纵向RecyclerView渲染3行内容,每行固定放3张从数据库读取的图片,最终拼成3x3网格效果。
1. 添加基础依赖
在模块级build.gradle的dependencies块中引入所需控件依赖:
implementation "androidx.recyclerview:recyclerview:1.3.2" implementation "androidx.cardview:cardview:1.0.0" implementation "com.github.bumptech.glide:glide:4.16.0" // 用于异步加载图片,避免主线程卡顿 annotationProcessor "com.github.bumptech.glide:compiler:4.16.0"
2. 编写XML布局文件
共需要3份布局文件,分别对应主页面、外层卡片项、内层行项。
2.1 主页面布局(activity_main.xml)
用于放置外层横向RecyclerView:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp" android:gravity="center"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_horizontal_outer" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" /> </LinearLayout>
2.2 外层卡片项布局(item_card_grid.xml)
每个横向滚动的子项为一个CardView,内部放置纵向RecyclerView承载3行内容:
<?xml version="1.0" encoding="utf-8"?> <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="320dp" android:layout_height="320dp" android:layout_marginHorizontal="8dp" app:cardCornerRadius="12dp" app:cardElevation="6dp"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_vertical_inner" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="8dp" android:orientation="vertical" /> </androidx.cardview.widget.CardView>
2.3 内层行项布局(item_grid_row.xml)
纵向RecyclerView的每个子项为一行,平均放置3张图片:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:orientation="horizontal" android:layout_marginVertical="4dp"> <ImageView android:id="@+id/iv_col1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_marginHorizontal="4dp" android:scaleType="centerCrop"/> <ImageView android:id="@+id/iv_col2" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_marginHorizontal="4dp" android:scaleType="centerCrop"/> <ImageView android:id="@+id/iv_col3" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_marginHorizontal="4dp" android:scaleType="centerCrop"/> </LinearLayout>
3. 编写适配器逻辑
需要两个适配器分别控制外层卡片、内层行的渲染逻辑。
3.1 内层行适配器(InnerRowAdapter.java)
负责将每一行对应的3张图片绑定到ImageView,固定渲染3行凑成3行结构:
public class InnerRowAdapter extends RecyclerView.Adapter<InnerRowAdapter.RowViewHolder> { private List<Bitmap> gridImages; // 单个卡片对应的9张图片数据 public InnerRowAdapter(List<Bitmap> gridImages) { this.gridImages = gridImages; } @NonNull @Override public RowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_grid_row, parent, false); return new RowViewHolder(view); } @Override public void onBindViewHolder(@NonNull RowViewHolder holder, int rowPosition) { // 按行号计算对应图片的索引位置 Bitmap img1 = gridImages.get(rowPosition * 3); Bitmap img2 = gridImages.get(rowPosition * 3 + 1); Bitmap img3 = gridImages.get(rowPosition * 3 + 2); holder.ivCol1.setImageBitmap(img1); holder.ivCol2.setImageBitmap(img2); holder.ivCol3.setImageBitmap(img3); // 如果数据库存储的是图片路径/Uri,替换为Glide异步加载写法 // Glide.with(holder.itemView.getContext()).load(imgPath1).into(holder.ivCol1); } @Override public int getItemCount() { // 固定返回3,对应3行结构 return 3; } public static class RowViewHolder extends RecyclerView.ViewHolder { ImageView ivCol1, ivCol2, ivCol3; public RowViewHolder(@NonNull View itemView) { super(itemView); ivCol1 = itemView.findViewById(R.id.iv_col1); ivCol2 = itemView.findViewById(R.id.iv_col2); ivCol3 = itemView.findViewById(R.id.iv_col3); } } }
3.2 外层卡片适配器(OuterCardAdapter.java)
负责初始化每个CardView内的纵向RecyclerView,绑定对应的数据:
public class OuterCardAdapter extends RecyclerView.Adapter<OuterCardAdapter.CardViewHolder> { private List<List<Bitmap>> allCardData; // 所有卡片数据,每个子列表对应一个卡片的9张图 public OuterCardAdapter(List<List<Bitmap>> allCardData) { this.allCardData = allCardData; } @NonNull @Override public CardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_card_grid, parent, false); return new CardViewHolder(view); } @Override public void onBindViewHolder(@NonNull CardViewHolder holder, int position) { List<Bitmap> singleCardImages = allCardData.get(position); // 初始化内层纵向布局管理器,关闭嵌套滚动避免滑动冲突 LinearLayoutManager innerManager = new LinearLayoutManager(holder.itemView.getContext()); innerManager.setOrientation(LinearLayoutManager.VERTICAL); holder.rvInner.setLayoutManager(innerManager); holder.rvInner.setNestedScrollingEnabled(false); // 绑定内层行适配器 InnerRowAdapter innerAdapter = new InnerRowAdapter(singleCardImages); holder.rvInner.setAdapter(innerAdapter); } @Override public int getItemCount() { return allCardData == null ? 0 : allCardData.size(); } public static class CardViewHolder extends RecyclerView.ViewHolder { RecyclerView rvInner; public CardViewHolder(@NonNull View itemView) { super(itemView); rvInner = itemView.findViewById(R.id.rv_vertical_inner); } } }
4. 主页面业务逻辑
在Activity中完成数据库读取、RecyclerView初始化、适配器绑定操作:
public class MainActivity extends AppCompatActivity { private RecyclerView rvOuterHorizontal; private List<List<Bitmap>> allCardList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); rvOuterHorizontal = findViewById(R.id.rv_horizontal_outer); // 从数据库读取9张图片,替换为自己的SQLite/Room查询逻辑 List<Bitmap> singleGridImages = loadImagesFromDatabase(); // 单卡片场景只加一组数据即可,多卡片横向滑动场景可追加多组数据 allCardList.add(singleGridImages); // 初始化外层横向布局管理器 LinearLayoutManager outerManager = new LinearLayoutManager(this); outerManager.setOrientation(LinearLayoutManager.HORIZONTAL); rvOuterHorizontal.setLayoutManager(outerManager); rvOuterHorizontal.setNestedScrollingEnabled(false); // 绑定外层适配器 OuterCardAdapter outerAdapter = new OuterCardAdapter(allCardList); rvOuterHorizontal.setAdapter(outerAdapter); } // 数据库查询示例方法,返回9张图片数据 private List<Bitmap> loadImagesFromDatabase() { List<Bitmap> result = new ArrayList<>(); // 编写实际数据库查询逻辑,保证返回列表长度为9,避免数组越界 return result; } }
注意事项
- 内外层RecyclerView必须调用
setNestedScrollingEnabled(false),否则会出现横向、纵向滑动冲突、滑动卡顿问题。 - CardView的宽高可以根据屏幕宽度动态计算,比如设置为屏幕宽度减去左右边距,实现卡片撑满屏幕的效果。
- 不要在主线程做Bitmap解码操作,大尺寸图片需要提前压缩,避免出现OOM或UI卡顿。
- 如果数据库存储的是图片路径而非Bitmap,直接替换为Glide加载对应路径即可,不需要手动解码Bitmap。
内容的提问来源于stack exchange,提问作者user19186320
相关产品推荐
相关产品推荐


