商品展示的不同模式(网格布局 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)
使用 aspectRatio
和 constrainSize
属性可以动态调整商品卡片的大小和比例。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) {
// 触摸滑动切换商品逻辑
}
});
}
}
通过以上方案和代码示例,我们可以开发出一个跨端适配的电商购物应用,在不同屏幕大小的设备上提供优化的购物体验,并支持自由窗口模式。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。