HarmonyOS 應(yīng)用開發(fā)-自定義Swiper卡片預(yù)覽效果實現(xiàn)

介紹

本方案做的是采用Swiper組件實現(xiàn)容器視圖居中完全展示尿背,兩邊等長露出棚唆,并且跟手滑動效果邑闺。

效果圖預(yù)覽

實現(xiàn)思路

本解決方案通過維護所有卡片偏移的數(shù)組季俩,實時更新卡片的偏移量,以實現(xiàn)swiper子組件內(nèi)圖片居中展示雀瓢,兩邊等長露出。

  1. 左右露出效果靜態(tài)實現(xiàn)玉掸。

Swiper組件基礎(chǔ)視圖效果如下刃麸。

如果所有子組件卡片大小一樣,子組件內(nèi)卡片居中展示即可實現(xiàn)效果司浪。但是當(dāng)子組件的卡片大小不一樣時泊业,無法通過簡單的設(shè)置居中布局實現(xiàn)左右的等長露出把沼。
此時需要計算當(dāng)前狀態(tài)下的卡片的偏移量。

  /**
   * 計算指定卡片的最大偏移量吁伺。
   * @param index {number} target card's index.
   * @returns offset value.
   */
  getMaxOffset(index: number): number {
    /*
     * 這里的偏移量指相對容器左側(cè)的值饮睬。
     * 計算公式為:屏幕寬度 - Swiper兩側(cè)突出的偏移量 - 卡片自身的寬度。
     * 此值即為卡片可偏移的最大值篮奄,也就是卡片右對齊的狀態(tài)值捆愁。
     * 如果居中,則將最大偏移量 / 2窟却。
     */
    return this.displayWidth - this.cardsList[index].width - 2 * this.swiperMargin;
  }
        
  /**
   * 計算卡片偏移量昼丑,并維護偏移量列表。
   * @param targetIndex { number } swiper target card's index.
   */
  calculateOffset(target: number) {
    let left = target - 1;
    let right = target + 1;

    // 計算上一張卡片的偏移值
    if (this.isIndexValid(left)) {
      this.cardsOffset[left] = this.getMaxOffset(left);
    }
    // 計算當(dāng)前卡片的偏移值
    if (this.isIndexValid(target)) {
      this.cardsOffset[target] = this.getMaxOffset(target) / 2;
    }
    // 下一張片的偏移值
    if (this.isIndexValid(right)) {
      this.cardsOffset[right] = 0;
    }
  }
  1. 滑動跟手實現(xiàn)

滑動swiper組件動態(tài)位置更新原理和上一步靜態(tài)位置獲取原理一樣夸赫,只不過在滑動過程通過相應(yīng)的回調(diào)函數(shù)實時位置更新菩帝。
在以下這三個swiper回調(diào)接口中,分別實現(xiàn)卡片跟手茬腿、離手呼奢、導(dǎo)航點切換時的卡片偏移量更新

接口名 基本功能
onGestureSwipe 在頁面跟手滑動過程中切平,逐幀觸發(fā)該回調(diào)握础。
onAnimationStart 切換動畫開始時觸發(fā)該回調(diào)。
onChange 子組件索引變化時觸發(fā)該事件揭绑。

具體api接口信息查看:Swiper事件弓候。

  • 在onGestureSwiper回調(diào)中,根據(jù)手指滑動的距離實時維護卡片的偏移量他匪。
.onGestureSwipe((index, event) => {
  let currentOffset = event.currentOffset;
  // 獲取當(dāng)前卡片(居中)的原始偏移量
  let maxOffset = this.getMaxOffset(index) / 2;
  // 實時維護卡片的偏移量列表菇存,做到跟手效果
  if (currentOffset < 0) {
    // 向左偏移
    /*
     * 此處計算原理為:按照比例設(shè)置卡片的偏移量。
     * 當(dāng)前卡片居中邦蜜,向左滑動后將在左邊依鸥,此時卡片偏移量即為 maxOffset * 2(因為向右對齊)。
     * 所以手指能夠滑動的最大距離(this.displayWidth)所帶來的偏移量即為 maxOffset悼沈。
     * 易得公式:卡片實時偏移量 = (手指滑動長度 / 屏幕寬度) * 卡片最大可偏移量 + 當(dāng)前偏移量贱迟。
     * 之后的計算原理相同,將不再贅述絮供。
     */
    this.cardsOffset[index] = (-currentOffset / this.displayWidth) * maxOffset + maxOffset;
    if (this.isIndexValid(index + 1)) {
      // 下一個卡片的偏移量
      let maxOffset = this.getMaxOffset(index + 1) / 2;
      this.cardsOffset[index + 1] = (-currentOffset / this.displayWidth) * maxOffset;
    }
    if (this.isIndexValid(index - 1)) {
      // 上一個卡片的偏移量
      let maxOffset = this.getMaxOffset(index - 1) / 2;
      this.cardsOffset[index - 1] = (currentOffset / this.displayWidth) * maxOffset + 2 * maxOffset;
    }
  } else if (currentOffset > 0) {
    // 向右滑動
    this.cardsOffset[index] = maxOffset - (currentOffset / this.displayWidth) * maxOffset;
    if (this.isIndexValid(index + 1)) {
      let maxOffset = this.getMaxOffset(index + 1) / 2;
      this.cardsOffset[index + 1] = (currentOffset / this.displayWidth) * maxOffset;
    }
    if (this.isIndexValid(index - 1)) {
      let maxOffset = this.getMaxOffset(index -1) / 2;
      this.cardsOffset[index - 1] = 2 * maxOffset - (currentOffset / this.displayWidth) * maxOffset;
    }
  }
})
  • 在onAnimationStart回調(diào)中衣吠,計算手指離開屏幕時卡片的偏移量,避免產(chǎn)生突變的偏移量壤靶。
.onAnimationStart((_, targetIndex) => {
  this.calculateOffset(targetIndex);
})

這里的 calculateOffset 函數(shù)即步驟1中維護卡片偏移量的函數(shù)缚俏。

  • 在onChange回調(diào)中提前計算Swiper滑動后卡片的位置。
.onChange((index) => {
  logger.info(TAG, `Target index: ${index}`);
  this.calculateOffset(index);
})

計算方式同上一步。

  1. 圖片預(yù)覽效果實現(xiàn)

圖片預(yù)覽動效是通過共享元素轉(zhuǎn)場結(jié)合全屏模態(tài)實現(xiàn)的忧换。
通過geometryTransition屬性綁定兩個需要“一鏡到底”的組件(本案例中的圖片)恬惯,結(jié)合模態(tài)窗口轉(zhuǎn)場即可。

// 以下代碼僅展示關(guān)鍵部分亚茬,詳請查看源碼
Row() {
  Image(this.cardInfo.src)
    .objectFit(ImageFit.Cover)
    .borderRadius($r('app.integer.photo_radius'))
      // TODO 知識點:geometryTransition通過id參數(shù)綁定兩個組件轉(zhuǎn)場關(guān)系酪耳,實現(xiàn)一鏡到底動畫
    .geometryTransition(this.cardIndex.toString(), { follow: true })
    .transition(TransitionEffect.OPACITY.animation({ duration: Constants.DURATION, curve: Curve.Friction }))
}
...
.bindContentCover(
  this.isPhotoShow,
  this.photoShowBuilder(this.cardInfo.src, this.cardIndex.toString()),
  { backgroundColor: $r('app.color.photo_preview_build_background'), modalTransition: ModalTransition.ALPHA }
)
...
// 全屏模態(tài)組件
@Builder photoShowBuilder(img: Resource, id: string) {
  Column() {
    Image(img)
      .borderRadius($r('app.integer.photo_radius'))
      .geometryTransition(id, { follow: true })
      .width($r('app.string.photo_preview_width'))
      .transition(TransitionEffect.opacity(Constants.OPACITY))
  }
  ...
  .onClick(() => {
    animateTo({
      duration: Constants.DURATION,
      curve: Curve.Friction
    }, () => {
      this.isPhotoShow = !this.isPhotoShow;
    })
  })
}

高性能知識點

  • 本示例使用了LazyForEach進行數(shù)據(jù)懶加載以降低內(nèi)存占用。
  • Swiper組件的onGestureSwipe事件是個高頻回調(diào)刹缝,注意在里面不要調(diào)用冗余操作和耗時操作碗暗。

工程結(jié)構(gòu)&模塊類型

cardswiperanimation            // har包
 ├─components
 │  ├─mainpage
 │  │  └─ CardSwiper.ets       // 卡片滑動組件入口
 │  ├─model
 │  │  └─ CardModel.ets        // 定義卡片類型
 │  ├─viewmodel
 │     └─ CardViewModel.ets    // 定義卡片組件
 ├─utils
 │  ├─ Constants.ets           // 常量數(shù)據(jù)

寫在最后

如果你覺得這篇內(nèi)容對你還蠻有幫助,我想邀請你幫我三個小忙

  • 點贊赞草,轉(zhuǎn)發(fā)讹堤,有你們的 『點贊和評論』,才是我創(chuàng)造的動力厨疙。
  • 關(guān)注小編洲守,同時可以期待后續(xù)文章ing??,不定期分享原創(chuàng)知識沾凄。
  • 想要獲取更多完整鴻蒙最新學(xué)習(xí)知識點梗醇,請移步前往小編:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市撒蟀,隨后出現(xiàn)的幾起案子叙谨,更是在濱河造成了極大的恐慌,老刑警劉巖保屯,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件手负,死亡現(xiàn)場離奇詭異,居然都是意外死亡姑尺,警方通過查閱死者的電腦和手機竟终,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來切蟋,“玉大人统捶,你說我怎么就攤上這事”猓” “怎么了喘鸟?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長驻右。 經(jīng)常有香客問我什黑,道長,這世上最難降的妖魔是什么堪夭? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任兑凿,我火速辦了婚禮凯力,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘礼华。我一直安慰自己,他們只是感情好拗秘,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布圣絮。 她就那樣靜靜地躺著,像睡著了一般雕旨。 火紅的嫁衣襯著肌膚如雪扮匠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天凡涩,我揣著相機與錄音棒搜,去河邊找鬼。 笑死活箕,一個胖子當(dāng)著我的面吹牛力麸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播育韩,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼克蚂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了筋讨?” 一聲冷哼從身側(cè)響起埃叭,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悉罕,沒想到半個月后赤屋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡壁袄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年类早,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片然想。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡莺奔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出变泄,到底是詐尸還是另有隱情令哟,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布妨蛹,位于F島的核電站屏富,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蛙卤。R本人自食惡果不足惜狠半,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一噩死、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧神年,春花似錦已维、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至飘千,卻和暖如春堂鲜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背护奈。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工缔莲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人霉旗。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓痴奏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奖慌。 傳聞我的和親對象是個殘疾皇子抛虫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容