Ver código fonte

播放条和播放页的动画效果优化

onecold 4 meses atrás
pai
commit
a212713310
2 arquivos alterados com 268 adições e 113 exclusões
  1. 103 46
      entry/src/main/ets/pages/NewIndex.ets
  2. 165 67
      entry/src/main/ets/view/LocalMusic.ets

+ 103 - 46
entry/src/main/ets/pages/NewIndex.ets

@@ -83,16 +83,18 @@ struct NewIndex {
     new hdsEffect.ShaderEffectController():undefined;
   @State defalut_home_type:number = 0//首页默认类型 可支持首页 媒体库 歌单 网盘
   @State isDetailView: boolean = false; // 是否在艺术家/专辑详情视图
-  @Provide isShowPlay: boolean = false;
+  @Provide @Watch('syncMiniPlayerVisibility') isShowPlay: boolean = false;
   @Provide CONTROL_PlayStatus: number = PlayStatus.INIT;
   @Provide progressValue: number = 0;
   @State isShowPrecious: boolean = false //播控条显示上一首按钮
   @State bottomBarHeight: number = 70;
+  @State miniPlayerScaleX: number = 1;
+  @State isMiniPlayerMounted: boolean = true;
   @StorageProp('bottomSafeHeight') bottomSafeHeight: number = 0;
   @Provide cover: string | undefined = '';
   // @Provide currentSong: VideoItem | undefined = undefined;
   @StorageLink('currentSong') currentSong: VideoItem | undefined = undefined;
-  @Provide isMultiSelect: boolean = false;
+  @Provide @Watch('syncMiniPlayerVisibility') isMultiSelect: boolean = false;
   /** 页面上下文 */
   context = this.getUIContext().getHostContext() as common.UIAbilityContext
   @Provide currentSongListName:string = '' //当前歌单名称
@@ -113,7 +115,7 @@ struct NewIndex {
   /** Y 轴偏移量(预留) */
   @State offsetY: number = 0;
   /** 主内容类型(0:本地音乐,1:网络内容) */
-  @Provide mType: number = 0
+  @Provide @Watch('syncMiniPlayerVisibility') mType: number = 0
   /** 模式类型(如文件夹 媒体库 艺术家、专辑等) */
   @Provide modeType: number = 0
   /** 是否为零状态(预留) */
@@ -170,6 +172,8 @@ struct NewIndex {
     }
   })
   @State @Watch('tabSelectedIndexesChanged') tabSelectedIndexes: number[] = [0]
+  private miniPlayerAnimationTimer: number = -1;
+  private readonly miniPlayerAnimationDuration: number = 320;
   //当胶囊按钮的选择发生变化时调用此函数
   tabSelectedIndexesChanged() {
     if(this.tabSelectedIndexes[0]==1){
@@ -205,6 +209,10 @@ struct NewIndex {
   onBackPress(): boolean | void {
     console.info('onecold onBackPress isDetailView = '+ this.isDetailView);
     console.info('onecold onBackPress mType = '+  this.mType);
+    if (this.isShowPlay) {
+      this.getUIContext().getHostContext()!.eventHub.emit('dismissPlayerView');
+      return true
+    }
     if (this.mType === 4) {
       this.returnToConfiguredHome()
     } else if (this.currentPath !== this.rootPath || this.isHistory || this.isFavMusic ||
@@ -334,6 +342,7 @@ struct NewIndex {
     ScreenUtil.setScreenSize();
     this.bundleName = AppUtil.getBundleName()
     this.versionName = AppUtil.getVersionName();
+    this.syncMiniPlayerVisibility(false)
     // await ImageKnife.getInstance().initFileCache(this.context, 256, 256 * 1024 * 1024)
     this.isShowSponsorship = Utility.isOpenTime()
     let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR);
@@ -552,6 +561,7 @@ struct NewIndex {
    */
   aboutToDisappear() {
     console.info('NewIndex aboutToDisappear');
+    this.clearMiniPlayerAnimationTimer();
     this.breakpointSystem.unregister();
     emitter.off(EventConstants.EVENT_SWIPE_BACK_UPDATE);
     emitter.off(EventConstants.EVENT_USER_STATE_CHANGE);
@@ -566,11 +576,66 @@ struct NewIndex {
     }
   }
 
+  private clearMiniPlayerAnimationTimer(): void {
+    if (this.miniPlayerAnimationTimer >= 0) {
+      clearTimeout(this.miniPlayerAnimationTimer)
+      this.miniPlayerAnimationTimer = -1
+    }
+  }
+
+  private shouldShowMiniPlayer(): boolean {
+    return !(this.isMultiSelect || (this.mType > 0 && this.mType < 6) || this.isShowPlay)
+  }
+
+  private syncMiniPlayerVisibility(animated: boolean = true): void {
+    const shouldShow = this.shouldShowMiniPlayer()
+    this.clearMiniPlayerAnimationTimer()
+    if (!animated) {
+      this.isMiniPlayerMounted = shouldShow
+      this.miniPlayerScaleX = shouldShow ? 1 : 0
+      return
+    }
+    if (shouldShow) {
+      if (!this.isMiniPlayerMounted) {
+        this.isMiniPlayerMounted = true
+        this.miniPlayerScaleX = 0
+      }
+      this.getUIContext()?.animateTo({
+        duration: this.miniPlayerAnimationDuration,
+        curve: Curve.Friction
+      }, () => {
+        this.miniPlayerScaleX = 1
+      })
+      return
+    }
+    if (!this.isMiniPlayerMounted) {
+      this.miniPlayerScaleX = 0
+      return
+    }
+    this.getUIContext()?.animateTo({
+      duration: this.miniPlayerAnimationDuration,
+      curve: Curve.Friction
+    }, () => {
+      this.miniPlayerScaleX = 0
+    })
+    this.miniPlayerAnimationTimer = setTimeout(() => {
+      this.miniPlayerAnimationTimer = -1
+      if (!this.shouldShowMiniPlayer()) {
+        this.isMiniPlayerMounted = false
+      }
+    }, this.miniPlayerAnimationDuration + 16)
+  }
+
   @Builder
   ContentBuild() {
     Stack() {
-      LocalMusic()
-        .visibility(this.mType === 0 ? Visibility.Visible : Visibility.None)
+      Stack() {
+        LocalMusic()
+      }
+      .width('100%')
+      .height('100%')
+      .visibility(this.mType === 0 || this.isShowPlay ? Visibility.Visible : Visibility.None)
+      .zIndex(this.isShowPlay ? 100 : 0)
       // 本地音乐内容区
       if(this.mType === 1 ){
         UserCenter()
@@ -647,39 +712,37 @@ struct NewIndex {
       .backgroundColor(Color.Transparent)
       Stack() {
         this.ContentBuild()
-        Stack() {
+        if (this.isMiniPlayerMounted) {
+          Stack() {
 
-          this.PlayController()
+            this.PlayController()
+          }
+          .width('90%')
+          .borderRadius(200)
+          .margin({ bottom:DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_2IN1||this.curDisplayIsHiCar
+            ? 30 :this.bottomSafeHeight })
+          .backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK)
+          .backgroundImage(StrUtil.isEmpty(this.cover) ?undefined:this.cover)
+          .visualEffect(deviceInfo.sdkApiVersion>=20&&this.bgController&&StrUtil.isEmpty(this.cover)?
+            new hdsEffect.HdsEffectBuilder()
+              .shaderEffect({
+                effectType: hdsEffect.EffectType.UV_BACKGROUND_FLOW_LIGHT,
+                animation: {
+                  duration: 10000,
+                  iterations: -1,
+                  autoPlay: true,
+                  onFinish: ()=> {
+                    console.info('Succeeded in finishing');
+                  }
+                },
+                controller: this.bgController,
+              })
+              .buildEffect():null)
+          .backgroundImageSize( {  width: '100%' })
+          .scale({ x: this.miniPlayerScaleX, y: 1, centerX: '50%', centerY: '50%' })
+          .opacity(0.99)
+          .clickEffect({ level: ClickEffectLevel.HEAVY })
         }
-        .width('90%')
-        .borderRadius(200)
-        .margin({ bottom:DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_2IN1||this.curDisplayIsHiCar
-          ? 30 :this.bottomSafeHeight })
-        .backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK)
-        .backgroundImage(StrUtil.isEmpty(this.cover) ?undefined:this.cover)
-        .visibility(this.isMultiSelect||(this.mType>0&&this.mType<6) ? Visibility.None : Visibility.Visible)
-        .visualEffect(deviceInfo.sdkApiVersion>=20&&this.bgController&&StrUtil.isEmpty(this.cover)?
-          new hdsEffect.HdsEffectBuilder()
-            .shaderEffect({
-              effectType: hdsEffect.EffectType.UV_BACKGROUND_FLOW_LIGHT,
-              animation: {
-                duration: 10000,
-                iterations: -1,
-                autoPlay: true,
-                onFinish: ()=> {
-                  console.info('Succeeded in finishing');
-                }
-              },
-              controller: this.bgController,
-            })
-            .buildEffect():null)
-        .backgroundImageSize( {  width: '100%' })
-        .animation({
-          duration: 500,
-          curve: Curve.Friction  // 可选动画曲线
-        })
-        .opacity(0.99)
-        .clickEffect({ level: ClickEffectLevel.HEAVY })
       }
       .alignContent(Alignment.Bottom)
       .width('100%')
@@ -731,16 +794,10 @@ struct NewIndex {
   }
 
   setShowPlayTrue(){
-    if (this.isShowPlay === true) {
-      this.isShowPlay = false
+    if (this.isShowPlay) {
+      return
     }
-    this.getUIContext()?.animateTo({
-      duration: 500,
-      curve: Curve.Friction
-    }, () => {
-      this.isShowPlay = true;
-    });
-    this.getUIContext().getHostContext()!.eventHub.emit('showPlayerView');
+    this.getUIContext().getHostContext()!.eventHub.emit('openPlayerViewFromMiniBar');
   }
 
   @Builder
@@ -806,7 +863,7 @@ struct NewIndex {
           type: ShadowType.BLUR,
           color: 'on_primary'
         })
-        .geometryTransition('cover', { follow: true }) // 绑定标识符
+        // .geometryTransition('cover', { follow: true }) // 绑定标识符
       if(!this.curDisplayIsHiCar){
         Column() {
           Text(this.currentSong?.name)

+ 165 - 67
entry/src/main/ets/view/LocalMusic.ets

@@ -115,6 +115,7 @@ import '../common/network/RemoteCacheRegistry';
 import { ServerLogUtil } from '../common/util/ServerLogUtil';
 import { lyricService, SongData } from '../common/service/LyricService';
 import { shouldResolveRemoteLyricOnPlay, tryResolveRemotePlaybackLyric } from '../common/util/RemotePlaybackLyricUtil';
+import { resolvePlayerDismissTarget } from '../common/util/PlayerDismissHelper';
 import {
   cloneVideoItem,
   preloadNextSongIfNeeded,
@@ -772,6 +773,9 @@ export struct LocalMusic {
   @State isShowCoverScaleGuide: boolean = false
   @StorageLink('songList') songList: Array<VideoItem> = [];
   @State translateY: number = 0;
+  @State playDismissScale: number = 1;
+  @State playDismissOpacity: number = 1;
+  @State playDismissTranslateX: number = 0;
   @State isShowSheet: boolean = false;
   @State isShowSheetView: boolean = false;
   @State isShowLyricCopySheet: boolean = false;
@@ -779,6 +783,10 @@ export struct LocalMusic {
   @State lyricSelectedIndexes: number[] = [];
   @State isShowTimeCloseView: boolean = false;
   private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Vertical });
+  private playDismissTimer: number = -1;
+  private readonly playDismissDuration: number = 420;
+  private playOpenFromMiniBarPending: boolean = false;
+  private playOpenTimer: number = -1;
   @StorageLink('currIndex') curIndex: number = 0;
   @Consume isShowDrawer: boolean;
   @Consume offsetX: number;
@@ -1273,8 +1281,22 @@ export struct LocalMusic {
 
     });
     this.getUIContext().getHostContext()!.eventHub.on('showPlayerView', () => {
+      if (!this.isShowPlay) {
+        this.setShowPlayTrue()
+      }
       this.showPlayerView();
     });
+    this.getUIContext().getHostContext()!.eventHub.on('openPlayerViewFromMiniBar', () => {
+      if (ArrayUtil.isEmpty(this.songList)) {
+        ToastUtil.showToast('当前播放列表为空,请先导入音乐。');
+        return
+      }
+      this.setShowPlayTrue(true)
+      this.showPlayerView();
+    });
+    this.getUIContext().getHostContext()!.eventHub.on('dismissPlayerView', () => {
+      this.setShowPlayFalse()
+    });
     this.getUIContext().getHostContext()!.eventHub.on('openPlayList', () => {
       this.openPlayList();
     });
@@ -2270,6 +2292,8 @@ export struct LocalMusic {
     this.getUIContext().getHostContext()!.eventHub.off('playPrevious');
     this.getUIContext().getHostContext()!.eventHub.off('playNext');
     this.getUIContext().getHostContext()!.eventHub.off('showPlayerView');
+    this.getUIContext().getHostContext()!.eventHub.off('openPlayerViewFromMiniBar');
+    this.getUIContext().getHostContext()!.eventHub.off('dismissPlayerView');
     this.getUIContext().getHostContext()!.eventHub.off('openPlayList');
     this.eventHub.off('onStateChange');
   }
@@ -3752,7 +3776,9 @@ export struct LocalMusic {
 
   build() {
     Stack() {
-      this.ContentBuild()
+      if (this.mType === 0) {
+        this.ContentBuild()
+      }
       Stack() {
         Column()
           .bindSheet($$this.isShowSheet, this.PlayListSheet(), {
@@ -3768,12 +3794,14 @@ export struct LocalMusic {
             }
           })
       }
-      .bindContentCover($$this.isShowPlay, this.MusicPlayBuilder(), {
-        modalTransition:ModalTransition.DEFAULT,
-        onWillDisappear: () => {
-          this.setShowPlayFalse()
-        },
-      })
+      if (this.isShowPlay) {
+        Stack() {
+          this.MusicPlayBuilder()
+        }
+        .width('100%')
+        .height('100%')
+        .zIndex(99)
+      }
 
 
     }
@@ -8969,32 +8997,124 @@ export struct LocalMusic {
     this.isShowCoverScaleGuide = false
     PreferencesUtil.put(PREF_PLAY_COVER_SCALE_GUIDE_SHOWN, true)
   }
+  private resolveMiniBarDismissTarget() {
+    const viewportWidthVp = this.windowWidth > 0 ? px2vp(this.windowWidth) : 360
+    const viewportHeightVp = this.windowHeight > 0 ? px2vp(this.windowHeight) : 800
+    const useFixedBottomMargin = DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_2IN1
+      || this.curDisplayIsHiCar
+    return resolvePlayerDismissTarget(viewportWidthVp, viewportHeightVp, this.bottomSafeHeight, useFixedBottomMargin)
+  }
+
+  private clearPlayDismissTimer(): void {
+    if (this.playDismissTimer >= 0) {
+      clearTimeout(this.playDismissTimer)
+      this.playDismissTimer = -1
+    }
+  }
+
+  private clearPlayOpenTimer(): void {
+    if (this.playOpenTimer >= 0) {
+      clearTimeout(this.playOpenTimer)
+      this.playOpenTimer = -1
+    }
+  }
 
+  private resetMusicPlayDismissState(): void {
+    this.clearPlayDismissTimer()
+    this.clearPlayOpenTimer()
+    this.playOpenFromMiniBarPending = false
+    this.translateY = 0
+    this.playDismissTranslateX = 0
+    this.playDismissScale = 1
+    this.playDismissOpacity = 1
+    this.scaleValueImage = 1
+    this.scaleValueText = 1
+    this.playDragUiOpacity = 1
+  }
 
+  private preparePlayerViewOpenFromMiniBar(): void {
+    const target = this.resolveMiniBarDismissTarget()
+    this.playOpenFromMiniBarPending = true
+    this.translateY = target.translateY
+    this.playDismissTranslateX = target.translateX
+    this.playDismissScale = target.scale
+    this.playDismissOpacity = 0
+    this.playDragUiOpacity = 0
+    this.scaleValueImage = Math.max(0.72, target.scale)
+    this.scaleValueText = Math.max(0.88, target.scale)
+  }
 
-  setShowPlayTrue(){
+  private startPlayerViewOpenAnimationIfNeeded(): void {
+    if (!this.playOpenFromMiniBarPending) {
+      return
+    }
+    this.playOpenFromMiniBarPending = false
     this.getUIContext()?.animateTo({
-      duration: 500,
-      curve: Curve.Friction
+      duration: this.playDismissDuration,
+      curve: Curve.Sharp
     }, () => {
-      this.isShowPlay = true;
-      this.showCoverScaleGuideIfNeeded()
-    });
+      this.translateY = 0
+      this.playDismissTranslateX = 0
+      this.playDismissScale = 1
+      this.playDismissOpacity = 1
+      this.playDragUiOpacity = 1
+      this.scaleValueImage = 1
+      this.scaleValueText = 1
+    })
+  }
 
+  setShowPlayTrue(fromMiniBar: boolean = false){
+    this.clearPlayDismissTimer()
+    this.clearPlayOpenTimer()
+    if (this.isShowPlay) {
+      return
+    }
+    if (fromMiniBar) {
+      this.preparePlayerViewOpenFromMiniBar()
+    } else {
+      this.resetMusicPlayDismissState()
+    }
+    this.isShowPlay = true;
+    this.showCoverScaleGuideIfNeeded()
+    if (fromMiniBar) {
+      this.playOpenTimer = setTimeout(() => {
+        this.playOpenTimer = -1
+        this.startPlayerViewOpenAnimationIfNeeded()
+      }, 16)
+    }
   }
 
-  setShowPlayFalse(){
+  private closePlayerViewWithMiniBarAnimation(): void {
+    if (!this.isShowPlay || this.playDismissOpacity < 1) {
+      return
+    }
+    const target = this.resolveMiniBarDismissTarget()
+
+    this.clearPlayDismissTimer()
     this.getUIContext()?.animateTo({
-      duration: 500,
-      curve: Curve.Friction
+      duration: this.playDismissDuration,
+      curve: Curve.Sharp
     }, () => {
-      this.isShowPlay = false;
-    });
-    this.isShowCoverScaleGuide = false
+      this.translateY = target.translateY
+      this.playDismissTranslateX = target.translateX
+      this.playDismissScale = target.scale
+      this.playDismissOpacity = 0
+      this.playDragUiOpacity = 0
+      this.scaleValueImage = Math.max(0.72, target.scale)
+      this.scaleValueText = Math.max(0.88, target.scale)
+    })
+    this.playDismissTimer = setTimeout(() => {
+      this.playDismissTimer = -1
+      this.isShowPlay = false
+      this.isShowCoverScaleGuide = false
+    }, this.playDismissDuration + 16)
+  }
+
+  setShowPlayFalse(){
+    this.closePlayerViewWithMiniBarAnimation()
   }
   //打开播放页
   showPlayerView() {
-    this.showCoverScaleGuideIfNeeded()
     console.info('onecold  showPlayerView  this.songList'+ JSON.stringify(this.songList))
     if (ArrayUtil.isNotEmpty(this.songList)) {
       // this.setShowPlayTrue()
@@ -12159,10 +12279,6 @@ export struct LocalMusic {
     Column() {
       this.IjkMusicPlayerView()
     }
-    .transition(TransitionEffect.asymmetric(
-      TransitionEffect.opacity(1),
-      TransitionEffect.OPACITY
-    ))
     .visualEffect(deviceInfo.sdkApiVersion>=20&&this.bgController?
       new hdsEffect.HdsEffectBuilder()
         .shaderEffect({
@@ -12181,11 +12297,15 @@ export struct LocalMusic {
 
     .height('100%')
     .width('100%')
+    .onAppear(() => {
+      this.startPlayerViewOpenAnimationIfNeeded()
+    })
     .onDisAppear(() => {
-      this.translateY = 0;
-      this.playDragUiOpacity = 1
+      this.resetMusicPlayDismissState()
     })
-    .translate({ y: this.translateY })
+    .translate({ x: this.playDismissTranslateX, y: this.translateY })
+    .scale({ x: this.playDismissScale, y: this.playDismissScale })
+    .opacity(this.playDismissOpacity)
     .gesture(
       PanGesture(this.panOption)
         .onActionUpdate((event?: GestureEvent) => {
@@ -12249,17 +12369,7 @@ export struct LocalMusic {
             const shouldClose = this.translateY > minDistance || velocity > minVelocity;
 
             if (shouldClose) {
-              // 添加关闭动画
-              this.getUIContext().animateTo({
-                duration: 600,
-                curve: Curve.Friction
-              }, () => {
-                this.isShowPlay = false;
-                this.scaleValueImage = 1
-                this.scaleValueText = 1
-                this.playDragUiOpacity = 1
-                this.translateY = 0;
-              })
+              this.closePlayerViewWithMiniBarAnimation()
             } else {
               // 回弹动画
               this.getUIContext().animateTo({
@@ -12490,7 +12600,7 @@ export struct LocalMusic {
             this.PlayTitle()
           }
           .opacity(this.playDragUiOpacity)
-          .position({ x: 0, y: this.isCoverOpacity() ? 10 : 30 })
+          .position({ x: 0, y: this.isCoverOpacity() ? 10 : 40 })
           .width('100%')
           .margin({ top: 0 })
           .height(PlayConstants.HEIGHT)
@@ -14222,12 +14332,7 @@ export struct LocalMusic {
         .visibility(this.isPuraWP() ? Visibility.None : Visibility.Visible)
         .borderRadius(this.isCoverRectangle ? 20 : '100%')
         .clip(true)
-        .rotate({
-          x: 0,
-          y: 0,
-          z: 1,
-          angle: this.rotateAngle
-        })
+
         .animation({
           duration: 1000,  // 与 setInterval 同步
           curve: Curve.Linear,
@@ -14250,10 +14355,10 @@ export struct LocalMusic {
             PreferencesUtil.put(SettingPage.RECTANGLE_COVER_SCALE, this.rectangleCoverScale)
           }))
     }
-    .scale({ x: this.scaleValueImage, y: this.scaleValueImage,
-      // 设置左下角为缩放中心点
-      centerX: '0%',
-      centerY: '50%' })
+    // .scale({ x: this.scaleValueImage, y: this.scaleValueImage,
+    //   // 设置左下角为缩放中心点
+    //   centerX: '0%',
+    //   centerY: '50%' })
   }
 
   private getRectangleCoverWidth(): string | number {
@@ -14468,29 +14573,22 @@ export struct LocalMusic {
 
     Row() {
       Row() {
-        Image($r('app.media.ic_back_down'))
-          .width($r('app.float.title_image_size'))// .aspectRatio(CommonConstants.ASPECT_RATIO)
-          .hitTestBehavior(HitTestMode.Transparent)
-
+        PointLightDefaultButton({
+          isSysBol: true,
+          pointColor: Color.White,
+          imageResource: $r('sys.symbol.chevron_down'),
+          isPx: false,
+          builderHeight: 26,
+          builderWidth: 26,
+        })
+          .onClick(() => {
+            this.setShowPlayFalse()
+          })
       }
       .width('100%')
-      .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.5 })
       .visibility(this.isHide ? Visibility.Hidden : Visibility.Visible)
-      .onClick(() => {
-        this.setShowPlayFalse()
-      })
 
 
-      Column() {
-        AVCastPicker({
-          normalColor: Color.White,
-          onStateChange: this.onStateVastChange
-        })
-          .width(24)
-          .height(24)
-      }
-      .visibility(this.isHide ? Visibility.Hidden : Visibility.None)
-
     }
     .width(PlayConstants.ROW_WIDTH)