DEV Community

zhonghua
zhonghua

Posted on • Edited on

Advanced Techniques in HarmonyOS: Creating a Personalized Dynamic Swiper Effect

Advanced Techniques in HarmonyOS: Creating a Personalized Dynamic Swiper Effect

Foreword
In the vast realm of HarmonyOS, developers have the opportunity to create astonishing user experiences. Recently, I set out to design a Swiper component with a unique sliding effect that quickly enters the field of vision while skillfully hiding the old cell out of sight. This article will share how to use HarmonyOS's Swiper component to achieve this captivating dynamic effect.

Image description

I. Design and Concept
The design philosophy of the Swiper is simplicity combined with dynamism. Each cell not only gradually scales down to 70% of its original size during the slide but is also covered by the preceding cell, creating a smooth and continuous visual effect. The realization of this effect relies on precise animation control and layout adjustment.

II. Code Design and Implementation Approach
To achieve this effect, we need to deeply customize the Swiper component. This includes dynamically adjusting the size, position, and layer of the cells, as well as using Bezier curves to achieve smooth animation effects.

III. Component Adoption and Code Explanation

3.1 Customizing the Swiper Component
The Swiper component provides a rich set of APIs that allow us to finely control its behavior. Here are some key configuration items and their functions:

  • itemSpace: Controls the spacing between cells.
  • indicator: Whether to display the indicator.
  • displayCount: Sets the number of cells to be displayed simultaneously.
  • onAreaChange: Callback when the size of the Swiper area changes.
  • customContentTransition: Custom content transition animation.

Basic configuration code for the Swiper component:

Swiper()
  .itemSpace(12)
  .indicator(false)
  .displayCount(this.DISPLAY_COUNT)
  .padding({left:10, right:10})
  .onAreaChange((oldValue, newValue) => {
    // Handle area change logic
  })
  .customContentTransition({
    transition: (proxy) => {
      // Custom transition logic
    }
  });
Enter fullscreen mode Exit fullscreen mode

3.2 Item Component Settings
Each Item needs to adjust its size, position, and layer according to its position in the Swiper. This involves initializing relevant variables and setting them in the aboutToAppear lifecycle method.

Initialize width and height, initialize component data:

  @State cw: number = 0;
  @State ch: number = 0;

  aboutToAppear(): void {
    initSwipe(...)
  }

  initSwipe(num:number){
    this.translateList = []
    for (let i = 0; i < num; i++) {
      this.scaleList.push(0.8)
      this.translateList.push(0.0)
      this.zIndexList.push(0)
    }
  }
  private MIN_SCALE: number = 0.70
  private DISPLAY_COUNT: number = 4
  private DISPLAY_WIDTH: number = 200
  @State scaleList: number[] = []
  @State translateList: number[] = []
  @State zIndexList: number[] = []
Enter fullscreen mode Exit fullscreen mode

Item size and position setting code:

LifeStyleItem({lifeStyleResponse: item})
  .scale({ x: this.scaleList[index], y: this.scaleList[index] })
  .translate({ x: this.translateList[index] })
  .zIndex(this.zIndexList[index]);
Enter fullscreen mode Exit fullscreen mode

Set properties in the transition property of customContentTransition:


//scaleList needs to be linearly changed
//translateList displacement needs to be processed with data offset and Bezier curve
//zIndexList needs to be set for position layer

this.scaleList[proxy.index] = linear function
this.translateList[proxy.index] = - proxy.position * proxy.mainAxisLength + Bezier curve function
this.zIndexList[proxy.index] = proxy.position
Enter fullscreen mode Exit fullscreen mode

3.3 Custom Animation Effects
To achieve smooth animation effects, we have defined cubic Bezier curve functions and linear functions. These functions will be used to calculate the size, position, and layer changes of cells during the slide.

Cubic Bezier curve function:

function cubicBezier8(t, a1, b1, a2, b2) {
  // Calculate the value of the cubic Bezier curve

const k1 = 3 * a1;
const k2 = 3 * (a2 - b1) - k1;
const k3 = 1 - k1 - k2;
return k3 * Math.pow(t, 3) + k2 * Math.pow(t, 2) + k1 * t;

}
Enter fullscreen mode Exit fullscreen mode

Linear function:

function chazhi(startPosition, endPosition, startValue, endValue, position) {
  // Calculate the result of linear interpolation

const range = endPosition - startPosition;
const positionDifference = position - startPosition;
const fraction = positionDifference / range;

const valueRange = endValue - startValue;
const result = startValue + (valueRange * fraction);

return result;

}
Enter fullscreen mode Exit fullscreen mode

3.4 Calculation Function Implementation
We have written calculation functions to determine the final appearance of cells in the Swiper. This includes calculating size, position, and layer according to position.

Functions to calculate size and position:

function calculateValue(width: number, position: number): number {
  const minValue = 0;

  const normalizedPosition = position / 4;

  // Calculate the easing value of the Bezier curve
  const easedPosition = cubicBezier(normalizedPosition, 0.3, 0.1, 1,  0.05);

  // Calculate the final change value based on the easing value
  const value = minValue + (width - minValue) * easedPosition;
  return value;
}

function calculateValueScale(position) {

if (position >= 2.5) {
  // When position is greater than 2, the value is fixed at 0.8
  return 0.8;
} else if (position < 2.5) {
  const startPosition = 2.5;
  const endPosition = -1;
  // Define the start and end values of the return value
  const startValue = 0.8;
  const endValue = 0.7;
  return chazhi(startPosition,endPosition,startValue,endValue,position)
}

return 0.7;

}
Enter fullscreen mode Exit fullscreen mode

IV. Full Code Integration
Integrate all the above code snippets into a component to ensure that the Swiper and each Item dynamically adjust according to the user's swipe operation.
The code is as follows:


function calculateValue(width: number, position: number): number {
  const minValue = 0;
  const normalizedPosition = position / 4;
  const easedPosition = cubicBezier8(normalizedPosition, 0.3, 0.1, 1,  0.05);
  const value = minValue + (width - minValue) * easedPosition;
  return value;
}

function cubicBezier(t: number, a1: number, b1: number, a2: number, b2: number): number {
  const k1 = 3 * a1;
  const k2 = 3 * (a2 - b1) - k1;
  const k3 = 1 - k1 - k2;
  return k3 * Math.pow(t, 3) + k2 * Math.pow(t, 2) + k1 * t;
}

function calculateValueScale(position: number): number {
  if (position >= 2.5) {
    return 0.8;
  } else if (position < 2.5) {
    const startPosition = 2.5;
    const endPosition = -1;
    const startValue = 0.8;
    const endValue = 0.7;
    return chazhi(startPosition,endPosition,startValue,endValue,position)
  }
  return 0.7;
}

function chazhi(startPosition:number,endPosition:number,startValue:number,endValue:number,position:number):number{

  const range = endPosition - startPosition;
  const positionDifference = position - startPosition;
  const fraction = positionDifference / range;

  const valueRange = endValue - startValue;
  const result = startValue + (valueRange * fraction);

  return result;
}

@Component
struct Banner {
  @State cw: number = 0;
  @State ch: number = 0;
  aboutToAppear(): void {
  initSwipe()
  }

  initSwipe(num:number){
    this.translateList = []
    for (let i = 0; i < num; i++) {
      this.scaleList.push(0.8)
      this.translateList.push(0.0)
      this.zIndexList.push(0)
    }
  }
  private MIN_SCALE: number = 0.70
  private DISPLAY_COUNT: number = 4
  private DISPLAY_WIDTH: number = 200
  @State scaleList: number[] = []
  @State translateList: number[] = []
  @State zIndexList: number[] = []


  build(){
  Swiper() {
          ForEach(this.lifeStyleList, (item: LifeStyleResponse|null,index) => {
            LifeStyleItem({lifeStyleResponse:item})
              .scale({ x: this.scaleList[index], y: this.scaleList[index] })
              .translate({ x: this.translateList[index] })
              .zIndex(this.zIndexList[index])
          }
          )
        }
        .itemSpace(12)
        .indicator(false)
        .displayCount(this.DISPLAY_COUNT)
        .padding({left:10,right:10})
        .onAreaChange((oldValue,newValue)=>{
          this.cw = new Number(newValue.width).valueOf()
          this.ch = new Number(newValue.height).valueOf()
        })
        .customContentTransition({
          transition :(proxy: SwiperContentTransitionProxy)=>{
            this.scaleList[proxy.index] = calculateValueScale(proxy.position)
            this.translateList[proxy.index] = - proxy.position * proxy.mainAxisLength + calculateValue8(this.cw,proxy.position)
            this.zIndexList[proxy.index] = proxy.position
          }
        })
  }
Enter fullscreen mode Exit fullscreen mode

V. Summary
Through the in-depth analysis of this article, we have not only implemented a Swiper component with a personalized dynamic effect but also learned how to use the powerful APIs of HarmonyOS to customize animations and layouts. It is hoped that this article will inspire the creativity of more developers to jointly explore the infinite possibilities of HarmonyOS.

Top comments (0)