Introduction
As a crucial feedback mechanism for user interaction, the quality of loading components directly impacts the user experience of an application. Building upon its basic implementation, ZeroOneApp's loading component offers rich advanced customization capabilities and performance optimization strategies, meeting diverse requirements in complex application scenarios. This article will explore advanced customization techniques, performance optimization practices, and integration solutions with other system features for the loading component, helping developers create smoother, more personalized loading experiences.
Official Resources
- ArkUI Custom Component Development Guide: Detailed instructions on building reusable, highly extensible custom components ArkUI Custom Component Documentation
- HarmonyOS Performance Optimization Guide: Official recommendations for application performance tuning HarmonyOS Performance Optimization Documentation
- Lottie Animation Advanced Features: Advanced usage and performance optimization techniques for Lottie animations Lottie Advanced Usage
Detailed Explanation
1. Advanced Style Customization Techniques
1.1 Complete Guide to Custom Loading Layouts
ZeroOneApp's loading component supports fully customized loading layouts through the layout
property, implemented using ArkUI's WrappedBuilder
. Here's an example of a complex custom layout:
// Custom loading layout example const CustomLoadingLayout = wrapBuilder((message: string) => { Column() { // Custom animation component Image($r('app.media.loading_icon')) .width(60) .height(60) .animation({ duration: 1000, curve: Curve.Linear, iterations: -1, playMode: PlayMode.Normal }) .rotate({ angle: 360 }) // Loading text Text(message || 'Loading data...') .fontSize(16) .fontColor($r('app.color.primary_text')) .margin({ top: 12 }) // Cancel button Button('Cancel') .width(120) .height(36) .margin({ top: 20 }) .onClick(() => { Loading.close(); }) } .padding(24) .backgroundColor(Color.White) .borderRadius(16) .shadow({ radius: 10, color: Color.Black.withAlpha(0.1), offsetX: 0, offsetY: 4 }) }); // Using custom layout Loading.open({ layout: CustomLoadingLayout, alignment: Alignment.Center, outsideCancel: true });
Key technical points for custom layouts:
- Wrap any UI structure with
wrapBuilder
to achieve complete layout customization - Combine with ArkUI's animation system to implement non-Lottie animation effects
- Add interactive elements (like cancel buttons) to enhance user control
- Beautify appearance with properties like padding, backgroundColor, and borderRadius
- Add depth with shadow properties to improve visual experience
1.2 Global vs. Local Style Priority Mechanism
ZeroOneApp's loading component supports both global default styles and local call styles, following clear priority rules:
// 1. Global style initialization (at application startup) Loading.initDefaultStyle({ alignment: Alignment.Center, pressBackCancel: true, outsideCancel: false, outBoxAttr: { backgroundColor: Color.Black.withAlpha(0.5) } }); // 2. Override specific styles when calling locally Loading.open({ // Only override properties that need modification, others use global configuration outsideCancel: true, alignment: Alignment.Bottom });
Priority order (from highest to lowest):
-
LoadingStyle
parameters passed during local calls - Globally initialized
loadingStyle
configuration - Component internal default values
This mechanism ensures style consistency within the application while allowing flexible adjustments for specific scenarios.
2. Advanced Lottie Animation Applications
2.1 Multi-animation Resource Management and Dynamic Switching
For complex applications requiring multiple loading animations, implement dynamic management of animation resources:
// Animation resource management utility class class LottieAnimationManager { private static animations: Map<string, string> = new Map(); // Preload animation resources static preloadAnimations(context: common.UIAbilityContext) { const animationFiles = ['loading_default.json', 'loading_success.json', 'loading_error.json']; const decoder = new util.TextDecoder('utf-8', { ignoreBOM: true }); animationFiles.forEach(file => { context.resourceManager.getRawFile(file, (err, data) => { if (data?.buffer) { const jsonStr = decoder.decode(new Uint8Array(data.buffer)); this.animations.set(file, jsonStr); } }); }); } // Get animation data static getAnimationData(key: string): object | undefined { const jsonStr = this.animations.get(key); return jsonStr ? JSON.parse(jsonStr) : undefined; } } // Custom Lottie loading component @ComponentV2 struct CustomLottieLoading { @Prop animationKey: string = 'loading_default.json'; // ...other properties and methods private createAnimation() { lottie.destroy(); const animationData = LottieAnimationManager.getAnimationData(this.animationKey); if (animationData) { lottie.loadAnimation({ container: this.mainCanvasRenderingContext, renderer: 'canvas', loop: this.animationKey === 'loading_default.json', // Only default animation loops autoplay: true, animationData: animationData, context: getContext() as common.UIAbilityContext }); } } }
Advantages of this implementation:
- Preloading mechanism reduces animation display delay
- Centralized management of multiple animation resources for easier maintenance
- Supports dynamic switching between animations for different states (loading, success, error)
- Non-looping animations for status prompts enhance interaction experience
2.2 Lottie Animation Performance Optimization
For performance issues that may arise with Lottie animations, implement the following optimization strategies in common/ui/src/main/ets/components/loading/LottieLoading.ets
:
// Optimized LottieLoading component @ComponentV2 export struct OptimizedLottieLoading { // ...other properties private animationInstance: any = null; private isAnimationLoaded: boolean = false; // Limit animation frame rate private frameRate: number = 30; private lastFrameTime: number = 0; @Monitor('isStartLoading') loadAnimation() { if (!this.isStartLoading) { this.destroyAnimation(); return; } // Play directly if animation is already loaded to avoid re-creation if (this.isAnimationLoaded && this.animationInstance) { this.animationInstance.play(); return; } this.createAnimation(); } private createAnimation() { // ...original code this.animationInstance = lottie.loadAnimation({ // ...configuration rendererSettings: { preserveAspectRatio: 'xMidYMid meet', clearCanvas: true } }); this.isAnimationLoaded = true; // Custom frame rate control this.animationInstance.setSubframe(false); this.animationInstance.frameRate = this.frameRate; } private destroyAnimation() { if (this.animationInstance) { this.animationInstance.stop(); // Keep instance but stop playback to avoid frequent destruction and creation // lottie.destroy() } } build() { Canvas(this.mainCanvasRenderingContext) .width('85%') .height('50%') .onDisappear(() => { this.destroyAnimation(); }) } }
Key optimization points:
- Avoid frequent destruction and creation of animation instances by using pause/play mode
- Limit frame rate (e.g., 30fps) to reduce CPU usage
- Disable subframe rendering (setSubframe(false)) to reduce drawing complexity
- Properly set preserveAspectRatio to avoid unnecessary scaling calculations
- Stop animations promptly when components disappear to release resources
3. Advanced Interaction and State Management
3.1 Decoupling Loading State from Business Logic
Implement decoupling of loading state and business logic through state management patterns:
// Loading state management class class LoadingManager { private static loadingCount: number = 0; // Show loading with support for叠加计数 static showLoading(options: LoadingStyle = {}): void { if (this.loadingCount === 0) { Loading.open(options); } this.loadingCount++; } // Hide loading, requires matching show count static hideLoading(): void { if (this.loadingCount > 0) { this.loadingCount--; if (this.loadingCount === 0) { Loading.close(); } } } // Force hide all loading static forceHide(): void { this.loadingCount = 0; Loading.close(); } } // Usage in business logic class DataService { fetchData(): Promise<any> { LoadingManager.showLoading(); return new Promise((resolve) => { // Network request net.post<DataResp>(API.data, {}, { onSuccess: (data) => resolve(data), onFail: () => resolve(null), onFinally: () => LoadingManager.hideLoading() }); }); } }
This design solves the following problems:
- Loading state conflicts with multiple concurrent requests
- Avoid loading flicker caused by rapid consecutive calls
- Unified management of all loading states in the application
- Simplified loading control logic in business code
3.2 Integration with Routing and Page Lifecycle
Implement automatic association of loading state with page lifecycle:
// Integrate loading management in route interceptor RouterUtil.addInterceptor({ beforePush: (url: string) => { // Some pages need automatic loading display if (['DetailPage', 'ProfilePage'].includes(url)) { LoadingManager.showLoading(); } return true; }, afterPush: (url: string) => { // Automatically hide loading after page entry if (['DetailPage', 'ProfilePage'].includes(url)) { // Allow time for page initialization setTimeout(() => LoadingManager.hideLoading(), 300); } } }); // Usage in page components @Component struct DetailPage { aboutToAppear() { // Start data loading when page appears this.loadData(); } loadData() { // Loading already shown by route interceptor, no need to call again dataService.fetchDetail().then(data => { // Process data }); } }
4. Special Scenario Handling
4.1 Loading Strategies for Weak Network Environments
Optimize loading experience for weak network environments:
// Weak network detection utility class NetworkQualityMonitor { static isWeakNetwork(): boolean { const netType = network.getCurrentType(); const signal = network.getSignalStrength(); // Determine based on network type and signal strength return netType === network.NetworkType.NETWORK_MOBILE && signal < 2 || netType === network.NetworkType.NETWORK_WIFI && signal < 30; } } // Weak network loading configuration const createWeakNetworkLoading = () => { return wrapBuilder(() => { Column() { LottieLoading() Text(NetworkQualityMonitor.isWeakNetwork() ? 'Poor network condition, still trying to load...' : 'Loading...') .fontSize(14) .margin({ top: 10 }) if (NetworkQualityMonitor.isWeakNetwork()) { Button('Switch to Offline Mode') .margin({ top: 15 }) .onClick(() => { AppState.switchOfflineMode(); Loading.close(); }) } } }); }; // Use weak network adapted loading Loading.open({ layout: createWeakNetworkLoading(), pressBackCancel: true });
4.2 Loading Timeout Handling
Implement automatic cancellation mechanism for loading timeouts:
// Loading utility with timeout class TimeoutLoading { private timeoutId: number = -1; showLoading(options: LoadingStyle = {}, timeoutMs: number = 10000): Promise<boolean> { return new Promise((resolve) => { Loading.open(options); // Set timeout timer this.timeoutId = setTimeout(() => { Loading.close(); resolve(false); // Timeout }, timeoutMs); // Listen for loading close event (implemented through custom events) LoadingEvent.on('close', () => { if (this.timeoutId !== -1) { clearTimeout(this.timeoutId); resolve(true); // Normal close } }); }); } } // Usage example const timeoutLoading = new TimeoutLoading(); const loadSuccess = await timeoutLoading.showLoading({}, 8000); if (!loadSuccess) { promptAction.showToast({ message: 'Loading timed out, please try again' }); }
Practical Recommendations
1. Performance Optimization Checklist
-
Animation Optimization:
- Prefer Lottie for complex animations over custom property animations
- Control animation frame rate below 30fps
- Avoid playing multiple animations simultaneously
-
Resource Management:
- Preload commonly used animation resources
- Destroy animation instances when no longer needed
- Compress Lottie JSON file sizes
-
Interaction Design:
- All loading states must provide a cancellation method
- Long loading processes should display progress hints
- Error states should have clear retry guidance
2. Accessibility Optimization
Ensure loading components are accessible to all users:
// Accessible loading component @Builder function AccessibleLoading() { Column() { LottieLoading() Text('Loading data') .fontSize(16) .margin({ top: 12 }) } .semanticsLabel('Data loading') // Accessibility label .semanticsHint('Please wait, data is being loaded') // Accessibility hint .focusable(true) // Support focus }
3. Testing Strategy
- Unit Testing: Test the counting logic of LoadingManager
- Performance Testing: Monitor animation CPU usage with performance analysis tools
- Scenario Testing: Simulate weak network, timeout, concurrent requests, etc.
- UI Testing: Verify loading positions and styles on different sized devices
Summary and Outlook
The advanced features of ZeroOneApp's loading component provide powerful support for complex application scenarios, with key advantages including:
- Highly Customizable: From simple style adjustments to fully custom layouts, meeting diverse design needs
- Performance Optimized: Ensuring smooth experiences through animation management and resource preloading
- Scenario Adaptable: Providing solutions for special cases like weak networks and timeouts
- Architecturally Flexible: Supporting deep integration with state management and routing systems
Future exploration directions:
- Progressive loading solutions based on skeleton screens
- Intelligent loading strategies combined with application state
- Consistent loading experiences across devices
- A/B testing framework for loading states
Top comments (0)