Explorar el Código

新增长按歌单的歌曲可以拖动排序, 点击...可以有两个菜单,一个设为歌单封面,一个删除

onecold hace 9 meses
padre
commit
d453dbdb48

+ 2 - 2
AppScope/app.json5

@@ -2,8 +2,8 @@
   "app": {
     "bundleName": "com.xgplayer.ttmusic.hm",
     "vendor": "example",
-    "versionCode": 20251016,
-    "versionName": "1.6.5",
+    "versionCode": 20251023,
+    "versionName": "1.6.6",
     "icon": "$media:app_icon",
     "label": "$string:app_name",
     "multiAppMode": {

+ 2 - 2
entry/src/main/ets/dialog/AddSongsToPlaylistDialog.ets

@@ -140,7 +140,7 @@ struct AddSongsToPlaylistDialogContent {
       this.updateListData(true) // append mode
       this.isLoading = false
       console.info('loadMoreData  completed, current data length:', this.dataSource.dataArray.length)
-    }, 300)
+    }, 50)
   }
 
   build() {
@@ -261,7 +261,7 @@ struct AddSongsToPlaylistDialogContent {
       }
       .width('100%')
       .justifyContent(FlexAlign.SpaceBetween)
-      .margin({ top: 20, bottom: 20 })
+      .margin({ top: 10, bottom: 10 })
     }
     .width('100%')
     .constraintSize({ maxWidth: 400 })

+ 6 - 6
entry/src/main/ets/dialog/PlaylistDialog.ets

@@ -38,12 +38,12 @@ struct PlaylistDialogContent {
   build() {
     Column({ space: 20 }) {
       // 标题
-      Text(this.isEditMode ? '编辑歌单' : '创建歌单')
-        .fontSize(18)
-        .fontWeight(FontWeight.Bold)
-        .fontColor($r('app.color.text_color'))
-        .visibility(this.isEditMode?Visibility.None:Visibility.Visible)
-        .margin({ top: 20 })
+      // Text(this.isEditMode ? '编辑歌单' : '创建歌单')
+      //   .fontSize(18)
+      //   .fontWeight(FontWeight.Bold)
+      //   .fontColor($r('app.color.text_color'))
+      //   .visibility(this.isEditMode?Visibility.None:Visibility.Visible)
+      //   .margin({ top: 20 })
 
       // 歌单封面选择
       Column({ space: 8 }) {

+ 187 - 21
entry/src/main/ets/pages/PlaylistDetailPage.ets

@@ -6,12 +6,13 @@ import { emitter } from '@kit.BasicServicesKit';
 import { ToastUtil, AppUtil, LogUtil, PreferencesUtil, StrUtil } from '@pura/harmony-utils';
 import { showEditPlaylistDialog } from '../dialog/PlaylistDialog';
 import { showAddSongsToPlaylistDialog } from '../dialog/AddSongsToPlaylistDialog';
-import { router } from '@kit.ArkUI';
+import { curves, router, SymbolGlyphModifier } from '@kit.ArkUI';
 import { GlobalContext } from '../common/util/GlobalContext';
 import { CommonConstants } from '../common/constants/CommonConstants';
 import { EventConstants } from '../common/constants/EventConstants';
 import { common } from '@kit.AbilityKit';
 import { ImagePickerUtil } from '../common/util/ImagePickerUtil';
+import { LazyDataSource } from '../common/util/LazyDataSource';
 
 /**
  * 歌单播放事件数据
@@ -49,6 +50,36 @@ export struct PlaylistDetailPage {
   private playlistId: string = ''
   @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
 
+  //长按拖动
+  @State dragItem: number = -1
+  @State scaleItem: number = -1
+  @State neighborItem: number = -1
+  @State neighborScale: number = -1
+  @State offsetY: number = 0
+  private dragRefOffset: number = 0
+  private ITEM_INTV: number = 78
+  //播放列表的拖动List 重新排序
+  @State sonDataSource: LazyDataSource<VideoItem> = new LazyDataSource(this.songList)
+  scaleSelect(item: number): number {
+    if (this.scaleItem === item) {
+      return 1.05
+    } else if (this.neighborItem === item) {
+      return this.neighborScale
+    } else {
+      return 1
+    }
+  }
+
+
+  itemMove(index: number, newIndex: number): void {
+    if (newIndex < 0 || newIndex >= this.songList.length) {
+      return;
+    }
+    let tmp = this.songList.splice(index, 1);
+    this.songList.splice(newIndex, 0, tmp[0]);
+    this.sonDataSource.pushArrayData(this.songList)
+  }
+
   aboutToAppear() {
 
     // 获取传入的歌单对象
@@ -72,6 +103,11 @@ export struct PlaylistDetailPage {
    */
   async loadPlaylistDetail() {
     try {
+      if (!this.playlist) {
+        ToastUtil.showToast('歌单不存在')
+        router.back()
+        return
+      }
       //先获取缓存
       const cacheSongs:string =PreferencesUtil.getStringSync(this.playlistId+'currentSongList', '')
       if(StrUtil.isNotEmpty(cacheSongs)){
@@ -79,12 +115,12 @@ export struct PlaylistDetailPage {
         this.songList = JSON.parse(cacheSongs) as VideoItem[]
       }else{
         console.log('onecold loadPlaylistDetail 歌曲数据为空')
+        this.isLoading = true
       }
-      if (!this.playlist) {
-        ToastUtil.showToast('歌单不存在')
-        router.back()
-        return
-      }
+      this.sonDataSource.pushArrayData(this.songList)
+      // 启动页面进入动画
+      this.animatePageEntry()
+
 
       // 加载歌单歌曲
       const playlistSongs = await this.playlistTable.queryPlaylistSongs(this.playlistId)
@@ -92,6 +128,7 @@ export struct PlaylistDetailPage {
       // 将 PlaylistSong 转换为 VideoItem
       this.songList = await convertPlaylistSongsToVideoItems(this.context,playlistSongs)
 
+      this.sonDataSource.pushArrayData(this.songList)
       // 歌单加载完成后,加载当前播放状态
       this.loadCurrentPlaybackStatus()
     } catch (error) {
@@ -151,7 +188,7 @@ export struct PlaylistDetailPage {
    * 页面入场动画
    */
   animatePageEntry() {
-    animateTo({
+    this.getUIContext().animateTo({
       duration: 600,
       curve: Curve.EaseOut,
       delay: 100,
@@ -749,7 +786,7 @@ export struct PlaylistDetailPage {
         // 歌曲列表 - 参考LocalMusic的MusicItem样式
         if (this.songList.length > 0) {
           List({ space: 0 }) {
-            ForEach(this.songList, (song: VideoItem, index: number) => {
+            LazyForEach(this.sonDataSource, (song: VideoItem, index: number) => {
               ListItem() {
                 Button({ type: ButtonType.Normal, stateEffect: true }) {
                   Row() {
@@ -857,9 +894,8 @@ export struct PlaylistDetailPage {
                         .fontColor($r('app.color.text_color'))
                         .opacity(0.6)
                         .margin({ right: 15 })
-                        .onClick(() => {
-                          this.showSongMenu(song)
-                        })
+                        .bindMenu(this.MoreMenuBuilder(song))
+
                     }
                   }
                   .width('100%')
@@ -869,6 +905,7 @@ export struct PlaylistDetailPage {
                 }
                 .backgroundColor(Color.Transparent)
                 .height(70)
+                .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.9 })
                 .width('100%')
                 .onClick(() => {
                   // 排序模式下禁用点击播放
@@ -876,15 +913,7 @@ export struct PlaylistDetailPage {
                     this.playSong(song, index)
                   }
                 })
-                .gesture(
-                  LongPressGesture()
-                    .onAction(() => {
-                      // 排序模式下不显示菜单
-                      if (!this.isSortMode) {
-                        this.showSongMenu(song)
-                      }
-                    })
-                )
+
                 .stateStyles({
                   normal: {
                     .backgroundColor(Color.Transparent)
@@ -912,10 +941,100 @@ export struct PlaylistDetailPage {
                   delay: 300 + index * 30
                 }))
               }
-            })
+              //拖动List关键代码开始
+              .scale({ x: this.scaleSelect(index), y: this.scaleSelect(index) })
+              .zIndex(this.dragItem === index ? 1 : 0)
+              .translate(this.dragItem === index ? { y: this.offsetY } : { y: 0 })
+              .gesture(
+                GestureGroup(GestureMode.Sequence,
+                  LongPressGesture({ repeat: true })
+                    .onAction((event?: GestureEvent) => {
+                      this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () => {
+                        this.scaleItem = index
+                      })
+                    })
+                    .onActionEnd(() => {
+                      this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () => {
+                        this.scaleItem = -1
+
+                      })
+
+                    }),
+                  PanGesture({ fingers: 1, direction: null, distance: 0 })
+                    .onActionStart(() => {
+                      this.dragItem = index // 记录当前拖动项索引
+                      this.dragRefOffset = 0 // 重置偏移基准
+                    })
+                    .onActionUpdate((event: GestureEvent) => {
+                      this.offsetY = event.offsetY - this.dragRefOffset;
+                      this.neighborItem = -1;
+                      let indexA = this.songList.indexOf(song);
+                      // LogUtil.info('onecold indexA = '+indexA +'  index=='+index)
+                      let curveValue: curves.ICurve = curves.initCurve(Curve.Sharp);
+                      let value: number = 0;
+
+                      if (this.offsetY < 0) {
+                        value = curveValue.interpolate(-this.offsetY / this.ITEM_INTV);
+                        this.neighborItem = indexA - 1;
+                        this.neighborScale = 1 - value / 20;
+                      } else if (this.offsetY > 0) {
+                        value = curveValue.interpolate(this.offsetY / this.ITEM_INTV);
+                        this.neighborItem = indexA + 1;
+                        this.neighborScale = 1 - value / 20;
+                      }
+
+
+                      if (this.offsetY > this.ITEM_INTV / 2 && indexA + 1 < this.songList.length) {
+                        this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
+                          this.offsetY -= this.ITEM_INTV;
+                          this.dragRefOffset += this.ITEM_INTV;
+                          this.itemMove(indexA, indexA + 1);
+                        });
+                      } else if (this.offsetY < -this.ITEM_INTV / 2 && indexA - 1 >= 0) {
+                        this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
+                          this.offsetY += this.ITEM_INTV;
+                          this.dragRefOffset -= this.ITEM_INTV;
+                          this.itemMove(indexA, indexA - 1);
+                        });
+                      }
+
+
+                    })
+                    .onActionEnd((event: GestureEvent) => {
+
+                      this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
+                        this.dragItem = -1
+                        this.neighborItem = -1
+                      })
+                      this.getUIContext().animateTo({
+                        curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
+                      }, () => {
+                        this.scaleItem = -1
+                      })
+                      this.isSortMode = true
+
+                    })
+
+                )
+                  .onCancel(() => {
+                    animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
+                      this.dragItem = -1
+                      this.neighborItem = -1
+                    })
+                    animateTo({
+                      curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
+                    }, () => {
+                      this.scaleItem = -1
+                    })
+
+                  })
+              ) //拖动List关键代码结束
+
+            }, (item: VideoItem) => item.filePath)
           }
           .width('100%')
           .layoutWeight(1)
+          .reuseId('list_item')
           .backgroundColor($r('app.color.bg_card'))
           .margin({ left: 16, right: 16 })
           .borderRadius(12)
@@ -1029,6 +1148,53 @@ export struct PlaylistDetailPage {
     .backgroundColor($r('app.color.index_background'))
   }
 
+  @Builder
+  MoreMenuBuilder(song: VideoItem) {
+    Scroll(){
+      Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
+        Menu() {
+          //设为歌单封面
+          MenuItem({
+            symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.picture')),
+            content: $r('app.string.set_cover')
+          })
+            .onClick(async () => {
+              if (StrUtil.isEmpty(song.pixelMapPath)) {
+                return
+              }
+              if(this.playlist){
+                const success = await this.playlistTable.updatePlaylist(this.playlist.id,
+                  this.playlist.name, this.playlist.description, song.pixelMapPath)
+                if (success) {
+                  this.playlist.coverPath = song.pixelMapPath
+                  ToastUtil.showToast('设为歌单封面成功')
+                  // 发送刷新事件
+                  const eventRefresh: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_REFRESH }
+                  emitter.emit(eventRefresh, {})
+                } else {
+                  ToastUtil.showToast('设为歌单封面失败')
+                }
+              }
+
+            })
+
+
+          MenuItem({
+            symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.trash')),
+            content: $r('app.string.delete')
+          })
+            .onClick(() => {
+              this.showSongMenu(song)
+            })
+        }
+        .font({ size: 15, weight: FontWeight.Normal })
+      }
+      .width(180)
+    }
+    .height('auto')
+
+  }
+
   /**
    * 显示更多菜单
    */

+ 4 - 0
entry/src/main/resources/zh_CN/element/string.json

@@ -208,6 +208,10 @@
     {
       "name": "lossless",
       "value": "无损"
+    },
+    {
+      "name": "set_cover",
+      "value": "设为歌单封面"
     }
   ]
 }