商品展示的不同模式(网格布局 vs 列表布局)

在电商应用中,商品展示模式主要有网格布局和列表布局两种。网格布局以矩阵形式排列商品,能在有限空间内展示较多商品,适合快速浏览和比较商品。列表布局则是将商品依次纵向排列,每个商品信息展示更详细,注重商品详情的呈现。

小屏单列 vs 大屏多列的适配策略

小屏设备(如手机)屏幕空间有限,为保证商品信息清晰展示和操作便捷,商品列表通常采用单列布局。用户可以通过上下滑动轻松浏览商品。而大屏设备(如平板、PC)有更大的显示区域,采用多列布局能充分利用空间,提高信息展示效率。例如,在平板上可以展示双列商品,在 PC 上可以展示三列甚至更多列商品。

栅格布局 + 自适应布局 让商品卡片自动排列

栅格布局提供了一种灵活的网格系统,结合自适应布局可以实现商品卡片的自动排列。通过设置栅格列数和列间距,以及商品卡片的宽度占比,能够根据屏幕大小自动调整商品卡片的排列方式。例如,在小屏上,商品卡片宽度占比为 100%,实现单列布局;在大屏上,商品卡片宽度占比为 33.3%(三列布局)或 50%(双列布局)。

以下是示例代码:

import { BreakpointSystem, BreakPointType } from '../common/breakpointsystem';

@Entry
@Component
struct ProductDisplay {
    @State currentBreakpoint: string ='sm';
    @State products: Array<{ id: number, name: string, image: Resource }> = [
        { id: 1, name: '商品 1', image: $r('app.media.product1') },
        { id: 2, name: '商品 2', image: $r('app.media.product2') },
        // 更多商品...
    ];
    private breakpointSystem: BreakpointSystem = new BreakpointSystem();

    aboutToAppear() {
        this.breakpointSystem.register();
        this.breakpointSystem.onBreakpointChange((breakpoint: string) => {
            this.currentBreakpoint = breakpoint;
        });
    }

    aboutToDisappear() {
        this.breakpointSystem.unregister();
    }

    build() {
        GridRow({ breakpoints: { value: ['600vp', '840vp'], reference: BreakpointsReference.WindowSize } }) {
            ForEach(this.products, (product) => {
                GridCol({ span: { sm: 12, md: 6, lg: 4 } }) {
                    Column() {
                        Image(product.image).width('100%').aspectRatio(1).objectFit(ImageFit.Contain);
                        Text(product.name).fontSize(16).textAlign(TextAlign.Center);
                    }
                      .padding(10)
                      .backgroundColor('#FFFFFF')
                      .borderRadius(10)
                      .shadow({ color: '#00000020', offset: { x: 0, y: 2 }, blurRadius: 4 });
                }
            });
        }
    }
}

构建 HarmonyOS Next 购物界面

在大屏设备上,首页展示 3 栏模式(侧边导航栏 + 商品分类 + 商品详情)

大屏设备拥有充足的显示空间,采用三栏模式可以提供更丰富的信息展示和便捷的操作体验。侧边导航栏用于快速导航到不同的功能模块,商品分类栏展示各类商品分类,商品详情栏则显示当前选中商品的详细信息。

以下是示例代码:

@Entry
@Component
struct BigScreenShoppingPage {
    @State selectedCategory: string = '全部商品';
    @State productCategories: Array<string> = ['全部商品', '电子产品', '服装', '家居用品'];
    @State products: Array<{ id: number, name: string, image: Resource, category: string }> = [
        { id: 1, name: '商品 1', image: $r('app.media.product1'), category: '电子产品' },
        { id: 2, name: '商品 2', image: $r('app.media.product2'), category: '服装' },
        // 更多商品...
    ];
    @State selectedProductId: number | null = null;

    build() {
        SideBarContainer(SideBarContainerType.Embed) {
            // 侧边导航栏
            Column() {
                ForEach(['首页', '购物车', '个人中心'], (item) => {
                    Text(item).fontSize(18).onClick(() => {
                        // 导航逻辑
                    });
                });
            }
              .width('20%')
              .backgroundColor('#F1F3F5');

            Column() {
                // 商品分类栏
                GridRow() {
                    ForEach(this.productCategories, (category) => {
                        GridCol({ span: 3 }) {
                            Text(category).fontSize(16).onClick(() => {
                                this.selectedCategory = category;
                            });
                        }
                    });
                }

                // 商品列表栏
                GridRow() {
                    ForEach(this.products.filter(product => this.selectedCategory === '全部商品' || product.category === this.selectedCategory), (product) => {
                        GridCol({ span: 4 }) {
                            Column() {
                                Image(product.image).width('100%').aspectRatio(1).objectFit(ImageFit.Contain).onClick(() => {
                                    this.selectedProductId = product.id;
                                });
                                Text(product.name).fontSize(14).textAlign(TextAlign.Center);
                            }
                              .padding(10)
                              .backgroundColor('#FFFFFF')
                              .borderRadius(10)
                              .shadow({ color: '#00000020', offset: { x: 0, y: 2 }, blurRadius: 4 });
                        }
                    });
                }

                // 商品详情栏
                if (this.selectedProductId!== null) {
                    const selectedProduct = this.products.find(product => product.id === this.selectedProductId);
                    if (selectedProduct) {
                        Column() {
                            Image(selectedProduct.image).width('100%').aspectRatio(1).objectFit(ImageFit.Contain);
                            Text(selectedProduct.name).fontSize(20).fontWeight(500);
                            // 更多商品详情信息...
                        }
                          .width('30%')
                          .padding(20)
                          .backgroundColor('#FFFFFF')
                          .borderRadius(10)
                          .shadow({ color: '#00000020', offset: { x: 0, y: 2 }, blurRadius: 4 });
                    }
                }
            }
              .width('80%');
        }
          .sideBarWidth('20%')
          .showSideBar(true);
    }
}

在小屏设备上,Tab 切换控制页面,商品列表以单列形式呈现

小屏设备屏幕空间有限,使用 Tab 切换控制页面可以简洁地展示不同功能模块。商品列表以单列形式呈现,方便用户单手操作和浏览。

以下是示例代码:

@Entry
@Component
struct SmallScreenShoppingPage {
    @State currentTab: number = 0;
    @State products: Array<{ id: number, name: string, image: Resource }> = [
        { id: 1, name: '商品 1', image: $r('app.media.product1') },
        { id: 2, name: '商品 2', image: $r('app.media.product2') },
        // 更多商品...
    ];

    build() {
        Column() {
            Tabs({ barPosition: BarPosition.End }) {
                TabContent() {
                    // 商品列表页
                    List() {
                        ForEach(this.products, (product) => {
                            ListItem() {
                                Column() {
                                    Image(product.image).width(100).height(100).objectFit(ImageFit.Contain);
                                    Text(product.name).fontSize(16);
                                }
                            }
                        });
                    }
                }
                .tabBar(
                    Column() {
                        Image($r('app.media.product_list_icon')).width(24).height(24);
                        Text('商品列表').fontSize(12);
                    }
                      .justifyContent(FlexAlign.Center).height('100%').width('100%')
                );
                TabContent() {
                    // 购物车页
                    // 购物车内容...
                }
                .tabBar(
                    Column() {
                        Image($r('app.media.shopping_cart_icon')).width(24).height(24);
                        Text('购物车').fontSize(12);
                    }
                      .justifyContent(FlexAlign.Center).height('100%').width('100%')
                );
                TabContent() {
                    // 个人中心页
                    // 个人中心内容...
                }
                .tabBar(
                    Column() {
                        Image($r('app.media.personal_center_icon')).width(24).height(24);
                        Text('个人中心').fontSize(12);
                    }
                      .justifyContent(FlexAlign.Center).height('100%').width('100%')
                );
            }
              .barMode(BarMode.Fixed)
              .barWidth('100%')
              .barHeight(56)
              .onChange((index: number) => {
                    this.currentTab = index;
                });
        }
    }
}

使用 Swiper 组件优化 Banner 轮播,在不同屏幕下调整图片显示个数

Swiper 组件可以实现 Banner 轮播效果,通过设置 displayCount 属性可以在不同屏幕下调整图片显示个数。在小屏设备上显示 1 张图片,在大屏设备上显示 2 - 3 张图片。

以下是示例代码:

@Entry
@Component
struct BannerSwiper {
    @State currentBreakpoint: string ='sm';
    @State banners: Array<Resource> = [
        $r('app.media.banner1'),
        $r('app.media.banner2'),
        $r('app.media.banner3')
    ];
    private breakpointSystem: BreakpointSystem = new BreakpointSystem();

    aboutToAppear() {
        this.breakpointSystem.register();
        this.breakpointSystem.onBreakpointChange((breakpoint: string) => {
            this.currentBreakpoint = breakpoint;
        });
    }

    aboutToDisappear() {
        this.breakpointSystem.unregister();
    }

    build() {
        Swiper() {
            ForEach(this.banners, (banner) => {
                Image(banner).width('100%').height(200).objectFit(ImageFit.Cover);
            });
        }
          .autoPlay(true)
          .indicator(true)
          .displayCount(new BreakPointType({ sm: 1, md: 2, lg: 3 }).getValue(this.currentBreakpoint)!);
    }
}

购物体验优化与跨设备联动

动态调整商品卡片大小(使用 aspectRatio + constrainSize)

使用 aspectRatioconstrainSize 属性可以动态调整商品卡片的大小和比例。aspectRatio 用于固定商品卡片的宽高比,constrainSize 用于限制商品卡片的最大和最小尺寸。

以下是示例代码:

@Component
struct ProductCard {
    @Prop product: { id: number, name: string, image: Resource };

    build() {
        Column() {
            Image(this.product.image).width('100%').aspectRatio(1).constrainSize({ minWidth: 150, maxWidth: 250 }).objectFit(ImageFit.Contain);
            Text(this.product.name).fontSize(16).textAlign(TextAlign.Center);
        }
          .padding(10)
          .backgroundColor('#FFFFFF')
          .borderRadius(10)
          .shadow({ color: '#00000020', offset: { x: 0, y: 2 }, blurRadius: 4 });
    }
}

结合自由窗口模式,支持窗口大小变化时自动切换 UI

通过监听窗口大小变化(断点监听),结合自适应和响应式布局,实现窗口大小变化时自动切换 UI。例如,当窗口从大屏模式变为小屏模式时,自动将三栏布局切换为 Tab 切换的单列布局。

优化触摸/鼠标交互体验(鼠标悬停显示商品详情,触摸滑动切换商品)

对于大屏设备,支持鼠标悬停显示商品详情可以提供更丰富的信息展示。对于小屏设备,触摸滑动切换商品可以提高操作便捷性。

以下是示例代码:

@Component
struct InteractiveProductCard {
    @Prop product: { id: number, name: string, image: Resource };
    @State isHover: boolean = false;

    build() {
        Column() {
            Image(this.product.image).width('100%').aspectRatio(1).objectFit(ImageFit.Contain).onHover((isHover) => {
                this.isHover = isHover;
            });
            Text(this.product.name).fontSize(16).textAlign(TextAlign.Center);
            if (this.isHover) {
                Text('商品详情信息...').fontSize(14).opacity(0.8);
            }
        }
          .padding(10)
          .backgroundColor('#FFFFFF')
          .borderRadius(10)
          .shadow({ color: '#00000020', offset: { x: 0, y: 2 }, blurRadius: 4 })
          .onTouch((event) => {
                if (event.type === TouchType.Swipe) {
                    // 触摸滑动切换商品逻辑
                }
            });
    }
}

通过以上方案和代码示例,我们可以开发出一个跨端适配的电商购物应用,在不同屏幕大小的设备上提供优化的购物体验,并支持自由窗口模式。


SameX
1 声望2 粉丝