鴻蒙OS高級技巧:打造個性化動態(tài)Swiper效果

前言

在鴻蒙OS的廣闊天地中,開發(fā)者們有機會創(chuàng)造出令人驚嘆的用戶體驗。最近,我著手設計一款具有獨特滑動效果的Swiper組件,它在滑動時能夠迅速進入視野,同時巧妙地將舊的cell隱藏到視線之外。本文將分享如何利用鴻蒙的Swiper組件,實現這一引人入勝的動態(tài)效果。

56cd252ccd1f4ec5b6a4cf706839dae2-2.gif

一、設計與構思

Swiper的設計理念是簡潔而富有動感。每個cell在滑動時不僅會逐漸縮小至原始大小的70%,還會被前一個cell覆蓋,創(chuàng)造出一種流暢且連續(xù)的視覺效果。這種效果的實現,依賴于精確的動畫控制和布局調整。

二、代碼設計與實現思路

實現這一效果,我們需要對Swiper組件進行深度定制。這包括對cell的尺寸、位置和層級進行動態(tài)調整,以及利用貝塞爾曲線來實現平滑的動畫效果。

三、控件采用與代碼說明

3.1 Swiper組件定制

Swiper組件提供了豐富的API,允許我們對其行為進行精細控制。以下是一些關鍵的配置項和它們的作用:

  • itemSpace: 控制cell之間的間距。
  • indicator: 是否顯示指示器。
  • displayCount: 設置同時展示的cell數量。
  • onAreaChange: 當Swiper區(qū)域大小變化時的回調。
  • customContentTransition: 自定義內容轉換動畫。

Swiper組件基礎配置代碼:

Swiper()
  .itemSpace(12)
  .indicator(false)
  .displayCount(this.DISPLAY_COUNT)
  .padding({left:10, right:10})
  .onAreaChange((oldValue, newValue) => {
    // 處理區(qū)域變化邏輯
  })
  .customContentTransition({
    transition: (proxy) => {
      // 自定義轉換邏輯
    }
  });
3.2 Item組件設置

每個Item需要根據其在Swiper中的位置進行尺寸、位置和層級的調整。這涉及到初始化相關變量,并在aboutToAppear生命周期方法中進行設置。

初始化寬高,初始化組件數據:

  @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[] = []

Item尺寸和位置設置代碼:

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

在 customContentTransition的transition 屬性中設置屬性:


//scaleList 需要進行線性變化
//translateList 位移需要進行 數據偏移處理和貝塞爾曲線處理
//zIndexList 需要進行位置層級設置

this.scaleList[proxy.index] = 線性函數
this.translateList[proxy.index] = - proxy.position * proxy.mainAxisLength + 貝塞爾曲線函數
this.zIndexList[proxy.index] = proxy.position
3.3 自定義動畫效果

為了實現平滑的動畫效果,我們定義了三次貝塞爾曲線函數和線性函數。這些函數將用于計算cell在滑動過程中的尺寸、位置和層級變化。

三次貝塞爾曲線函數:

function cubicBezier8(t, a1, b1, a2, b2) {
  // 計算三次貝塞爾曲線的值
  
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 chazhi(startPosition, endPosition, startValue, endValue, position) {
  // 計算線性插值的結果
 

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

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

return result;

}
3.4 計算函數實現

我們編寫了計算函數來確定cell在Swiper中的最終表現。這包括根據位置計算尺寸、位置和層級。

計算尺寸和位置的函數:

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

  const normalizedPosition = position / 4;

  // 計算貝塞爾曲線的緩動值
  const easedPosition = cubicBezier(normalizedPosition, 0.3, 0.1, 1,  0.05);

  // 根據緩動值計算最終的變化值
  const value = minValue + (width - minValue) * easedPosition;
  return value;
}

function calculateValueScale(position) {

if (position >= 2.5) {
  // 當position大于2時,值固定為0.8
  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;

}

四、全部代碼整合

將上述所有代碼片段整合到一個組件中,確保Swiper和每個Item都能夠根據用戶的滑動操作動態(tài)調整。
代碼如下:


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
          }
        })
  }

五、總結

通過本文的深入解析,我們不僅實現了一個具有個性化動態(tài)效果的Swiper組件,還學習了如何利用鴻蒙OS的強大API來定制動畫和布局。希望這篇文章能夠激發(fā)更多開發(fā)者的創(chuàng)造力,共同探索鴻蒙OS的無限可能。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容