Development of an E-commerce Shopping Application with Cross-device Adaptation in HarmonyOS Next
Different Product Display Modes (Grid Layout vs. List Layout)
In an e-commerce application, there are mainly two product display modes: the grid layout and the list layout. The grid layout arranges products in a matrix form, which can display more products in a limited space and is suitable for quickly browsing and comparing products. The list layout arranges products vertically in sequence, presenting more detailed information for each product and focusing on the display of product details.
Adaptation Strategies for Single Column on Small Screens vs. Multiple Columns on Large Screens
Small-screen devices (such as mobile phones) have limited screen space. To ensure the clear display of product information and convenient operation, the product list usually adopts a single-column layout. Users can easily browse products by scrolling up and down. On the other hand, large-screen devices (such as tablets and PCs) have a larger display area, and using a multi-column layout can make full use of the space and improve the efficiency of information display. For example, a dual-column display of products can be used on tablets, and a three-column or even more column display can be used on PCs.
Grid Layout + Adaptive Layout for Automatic Arrangement of Product Cards
The grid layout provides a flexible grid system. Combined with the adaptive layout, the automatic arrangement of product cards can be achieved. By setting the number of grid columns, column spacing, and the width proportion of product cards, the arrangement of product cards can be automatically adjusted according to the screen size. For example, on small screens, the width proportion of product cards is 100%, achieving a single-column layout; on large screens, the width proportion of product cards is 33.3% (for a three-column layout) or 50% (for a dual-column layout).
The following is the sample code:
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: 'Product 1', image: $r('app.media.product1') },
{ id: 2, name: 'Product 2', image: $r('app.media.product2') },
// More products...
];
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 });
}
});
}
}
}
Building a Shopping Interface in HarmonyOS Next
On Large-screen Devices, the Home Page Displays a 3-column Mode (Sidebar Navigation + Product Categories + Product Details)
Large-screen devices have sufficient display space, and using a three-column mode can provide a richer display of information and a convenient operation experience. The sidebar navigation is used for quickly navigating to different functional modules, the product category column displays various product categories, and the product details column shows the detailed information of the currently selected product.
The following is the sample code:
@Entry
@Component
struct BigScreenShoppingPage {
@State selectedCategory: string = 'All Products';
@State productCategories: Array<string> = ['All Products', 'Electronics', 'Clothing', 'Home Supplies'];
@State products: Array<{ id: number, name: string, image: Resource, category: string }> = [
{ id: 1, name: 'Product 1', image: $r('app.media.product1'), category: 'Electronics' },
{ id: 2, name: 'Product 2', image: $r('app.media.product2'), category: 'Clothing' },
// More products...
];
@State selectedProductId: number | null = null;
build() {
SideBarContainer(SideBarContainerType.Embed) {
// Sidebar Navigation
Column() {
ForEach(['Home', 'Shopping Cart', 'Personal Center'], (item) => {
Text(item).fontSize(18).onClick(() => {
// Navigation logic
});
});
}
.width('20%')
.backgroundColor('#F1F3F5');
Column() {
// Product Category Column
GridRow() {
ForEach(this.productCategories, (category) => {
GridCol({ span: 3 }) {
Text(category).fontSize(16).onClick(() => {
this.selectedCategory = category;
});
}
});
}
// Product List Column
GridRow() {
ForEach(this.products.filter(product => this.selectedCategory === 'All Products' || 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 });
}
});
}
// Product Details Column
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);
// More product details information...
}
.width('30%')
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.shadow({ color: '#00000020', offset: { x: 0, y: 2 }, blurRadius: 4 });
}
}
}
.width('80%');
}
.sideBarWidth('20%')
.showSideBar(true);
}
}
On Small-screen Devices, Tabs are Used to Switch Control Pages, and the Product List is Presented in a Single-column Form
Small-screen devices have limited screen space. Using Tabs to switch control pages can concisely display different functional modules. The product list is presented in a single-column form, making it convenient for users to operate and browse with one hand.
The following is the sample code:
@Entry
@Component
struct SmallScreenShoppingPage {
@State currentTab: number = 0;
@State products: Array<{ id: number, name: string, image: Resource }> = [
{ id: 1, name: 'Product 1', image: $r('app.media.product1') },
{ id: 2, name: 'Product 2', image: $r('app.media.product2') },
// More products...
];
build() {
Column() {
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
// Product List Page
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('Product List').fontSize(12);
}
.justifyContent(FlexAlign.Center).height('100%').width('100%')
);
TabContent() {
// Shopping Cart Page
// Content of the shopping cart...
}
.tabBar(
Column() {
Image($r('app.media.shopping_cart_icon')).width(24).height(24);
Text('Shopping Cart').fontSize(12);
}
.justifyContent(FlexAlign.Center).height('100%').width('100%')
);
TabContent() {
// Personal Center Page
// Content of the personal center...
}
.tabBar(
Column() {
Image($r('app.media.personal_center_icon')).width(24).height(24);
Text('Personal Center').fontSize(12);
}
.justifyContent(FlexAlign.Center).height('100%').width('100%')
);
}
.barMode(BarMode.Fixed)
.barWidth('100%')
.barHeight(56)
.onChange((index: number) => {
this.currentTab = index;
});
}
}
}
Using the Swiper Component to Optimize the Banner Carousel and Adjust the Number of Displayed Images on Different Screens
The Swiper component can achieve the Banner carousel effect. By setting the displayCount
property, the number of displayed images can be adjusted on different screens. On small-screen devices, 1 image is displayed, and on large-screen devices, 2 - 3 images are displayed.
The following is the sample code:
@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)!);
}
}
Optimization of the Shopping Experience and Cross-device Interaction
Dynamically Adjusting the Size of Product Cards (Using aspectRatio + constrainSize)
The aspectRatio
and constrainSize
properties can be used to dynamically adjust the size and proportion of product cards. The aspectRatio
is used to fix the width-to-height ratio of product cards, and the constrainSize
is used to limit the maximum and minimum sizes of product cards.
The following is the sample code:
@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 });
}
}
Combining the Free Window Mode to Support Automatic UI Switching When the Window Size Changes
By listening to changes in the window size (breakpoint listening) and combining the adaptive and responsive layouts, automatic UI switching is achieved when the window size changes. For example, when the window changes from the large-screen mode to the small-screen mode, the three-column layout is automatically switched to the single-column layout with Tab switching.
Optimizing the Touch/Mouse Interaction Experience (Displaying Product Details on Mouse Hover, Switching Products by Touch Swiping)
For large-screen devices, supporting the display of product details on mouse hover can provide a richer display of information. For small-screen devices, switching products by touch swiping can improve the convenience of operation.
The following is the sample code:
@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('Product details information...').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) {
// Logic for switching products by touch swiping
}
});
}
}
Through the above solutions and code examples, we can develop an e-commerce shopping application with cross-device adaptation, providing an optimized shopping experience on devices of different screen sizes and supporting the free window mode.