DEV Community

SameX
SameX

Posted on

Development of an E-commerce Shopping Application with Cross-terminal 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: grid layout and 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 one by one, 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. Large-screen devices (such as tablets and PCs), on the other hand, have a larger display area. Adopting a multi-column layout can make full use of the space and improve the efficiency of information display. For example, a dual-column product display 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, the column spacing, and the width proportion of the product cards, the arrangement of the product cards can be automatically adjusted according to the screen size. For example, on a small screen, the width proportion of the product cards is 100% to achieve a single-column layout; on a large screen, the width proportion of the 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 });
                }
            });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Building a Shopping Interface in HarmonyOS Next

On Large-screen Devices, the Home Page Displays a Three-column Mode (Side Navigation Bar + Product Categories + Product Details)

Large-screen devices have sufficient display space. Adopting a three-column mode can provide a richer information display and a convenient operation experience. The side navigation bar is used for quickly navigating to different functional modules, the product category bar displays various product categories, and the product details bar 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) {
            // Side Navigation Bar
            Column() {
                ForEach(['Home', 'Shopping Cart', 'Personal Center'], (item) => {
                    Text(item).fontSize(18).onClick(() => {
                        // Navigation logic
                    });
                });
            }
              .width('20%')
              .backgroundColor('#F1F3F5');

            Column() {
                // Product Category Bar
                GridRow() {
                    ForEach(this.productCategories, (category) => {
                        GridCol({ span: 3 }) {
                            Text(category).fontSize(16).onClick(() => {
                                this.selectedCategory = category;
                            });
                        }
                    });
                }

                // Product List Bar
                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 Bar
                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);
    }
}
Enter fullscreen mode Exit fullscreen mode

On Small-screen Devices, Tabs Are Used to Switch and Control Pages, and the Product List Is Presented in a Single-column Form

Small-screen devices have limited screen space. Using tabs to switch and control pages can concisely display different functional modules. The product list is presented in a single-column form, which is 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
                    // Contents 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
                    // Contents 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;
                });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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. One image is displayed on small-screen devices, and 2-3 images are displayed on large-screen devices.

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)!);
    }
}
Enter fullscreen mode Exit fullscreen mode

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 the product cards, and the constrainSize is used to limit the maximum and minimum sizes of the 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 });
    }
}
Enter fullscreen mode Exit fullscreen mode

Combining the Free Window Mode to Support Automatic UI Switching When the Window Size Changes

By listening to the changes in the window size (breakpoint listening) and combining the adaptive and responsive layouts, the automatic UI switching when the window size changes can be achieved. 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, Swiping to Switch Products on Touch)

For large-screen devices, supporting the display of product details on mouse hover can provide a richer information display. For small-screen devices, swiping to switch products on touch 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 swiping to switch products on touch
                }
            });
    }
}
Enter fullscreen mode Exit fullscreen mode

Through the above solutions and code examples, we can develop an e-commerce shopping application with cross-terminal adaptation, providing an optimized shopping experience on devices with different screen sizes and supporting the free window mode.

Top comments (0)