소스 검색

添加一个点击的回弹效果,表增加一个列

onecold 1 년 전
부모
커밋
5cdffeaff4

+ 5 - 2
entry/src/main/ets/common/util/MediaTable.ets

@@ -225,6 +225,7 @@ export default class MediaTable {
       obj.lyricContent  = resultSet.getString(resultSet.getColumnIndex('lyricContent'));
       obj.md5Str  = resultSet.getString(resultSet.getColumnIndex('md5Str'));
       obj.extra_json  = resultSet.getString(resultSet.getColumnIndex('extra_json'));
+      obj.pyStr  = resultSet.getString(resultSet.getColumnIndex('pyStr'));
       const valueBucket: relationalStore.ValuesBucket = obj
       // Step 4: 执行更新
       const updatePredicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
@@ -541,7 +542,7 @@ export default class MediaTable {
 
     item.md5Str = safeGet('md5Str');
     item.extra_json = safeGet('extra_json');
-
+    item.pyStr = safeGet('pyStr');
     return item;
   }
 
@@ -609,7 +610,9 @@ function generateBucket(item: VideoItem): relationalStore.ValuesBucket {
   if(item.extra_json){
     obj.extra_json = item.extra_json;
   }
-
+  if(item.pyStr){
+    obj.pyStr = item.pyStr;
+  }
 
   return obj;
 }

+ 4 - 2
entry/src/main/ets/common/util/RdbUtils.ets

@@ -60,12 +60,13 @@ export default class RdbUtils {
       '        lyricContent TEXT,\n' +
       '        md5Str TEXT,\n' +
       '        extra_json TEXT,\n' +
+      '        pyStr TEXT,\n' +
       '        mimeType TEXT' +
       ')',
     columns: ['id', 'name', 'filePath','mtype', 'videoSize', 'cTime','size', 'artist', 'album',
       'fileName','parentPath','isFav','pixelMapPath','pixelMapToString',
       'duration',   'sampleRate','playCount',  'lastPlayedStr', 'trackCount',
-      'lyricContent','md5Str','extra_json','mimeType']
+      'lyricContent','md5Str','extra_json','pyStr','mimeType']
   };
 
   constructor(tableName: string, sqlCreateTable: string, columns: Array<string>) {
@@ -137,7 +138,8 @@ export default class RdbUtils {
             'lyricContent': 'TEXT',
             'md5Str': 'TEXT',
             'extra_json': 'TEXT',
-            'mimeType': 'TEXT'
+            'mimeType': 'TEXT',
+            'pyStr': 'TEXT',
           };
           
           // 逐个添加列,不依赖于检查结果

+ 78 - 5
entry/src/main/ets/common/util/Utility.ets

@@ -22,6 +22,7 @@ import { window } from '@kit.ArkUI';
 import { bundleManager } from '@kit.AbilityKit'
 import { VipPage } from '../../pages/VipPage';
 import NetAxiosUtil from './NetAxiosUtil';
+import { pinyin4js } from '@ohos/pinyin4js';
 
 
 // import userFileManager from '@ohos.filemanagement.userFileManager';
@@ -677,9 +678,22 @@ export class Utility {
             const metadata = await avMetadataExtractor.fetchMetadata();
             if(StrUtil.isNotEmpty(metadata.title)){
               musicName = metadata.title
+            }else{
+              //自动解析像这样的获取不到元数据的 周杰伦-七里香.mp3文件
+              console.log(`onecold musicName为空:${file.name}`);
+              const musicData = parseMusicFileName(file.name);
+              if (musicData.isValid)  {
+                // console.log(`onecold 艺术家:${musicData.artist}`);   // 输出:周杰伦
+                // console.log(`onecold 歌曲名:${musicData.title}`);   // 输出:七里香
+                musicName = musicData.title
+                if(artist==''||artist==undefined)
+                  artist = musicData.artist
+              } else {
+                musicName = file.name
+                // console.log("onecold 文件名格式不符合要求");
+              }
             }
-            if(musicName==undefined)
-              musicName = file.name
+
 
             if(StrUtil.isNotEmpty(metadata.artist)){
               artist = metadata.artist
@@ -717,11 +731,11 @@ export class Utility {
 
               // console.info('onecold release success. name= '+musicName);
               if(pixelMap!==undefined&&pixelMap!==null){
-                console.info('onecold pixelMap is not empty= '+musicName);
+                // console.info('onecold pixelMap is not empty= '+musicName);
                 imagePath = await ImageUtil.savePixelMap(pixelMap,context.filesDir,name)
                 imagePath = fileUri.getUriFromPath(imagePath)
               }else{
-                console.info('onecold pixelMap is  empty= '+musicName);
+                // console.info('onecold pixelMap is  empty= '+musicName);
                 imagePath = ''
                 // if(StrUtil.isNotEmpty(artist))
                 //   imagePath =  await NetAxiosUtil.getLyricCover(musicName,artist)
@@ -742,6 +756,7 @@ export class Utility {
           console.warn('AVMetadataExtractor capability is not supported.');
         }
 
+
         if(musicName==undefined)
           musicName = file.name
 
@@ -753,7 +768,8 @@ export class Utility {
         item.sampleRate = sampleRate;
         item.isFav = 0;
         item.playCount = 0;
-
+        item.pyStr = pinyin4js.getShortPinyin(musicName)
+        console.info('onecold pyStr = '+pinyin4js.getShortPinyin(musicName));
       })
     } catch (error) {
       console.error('uriGetAssetsFromFile failed with err: ' + JSON.stringify(error));
@@ -1258,3 +1274,60 @@ function convertSecondsToTime(secondsStr: string): string {
     return `${formatNumber(minutes)}:${formatNumber(secs)}`;
   }
 }
+
+
+// 定义解析结果的数据结构
+class MusicInfo {
+  artist: string = "";  // 艺术家名称
+  title: string = "";  // 歌曲名称
+  isValid: boolean = false;  // 格式是否有效
+}
+/**
+ * 解析音乐文件名
+ * @param fileName - 待解析的文件名(需包含扩展名)
+ * @returns 包含解析结果的MusicInfo对象
+ */
+/**
+ * 智能解析音乐文件名(支持多种分隔符和前缀序号)
+ * @param fileName - 待解析的完整文件名
+ * @returns 结构化音乐信息
+ */
+function parseMusicFileName(fileName: string): MusicInfo {
+  const result = new MusicInfo();
+
+  // 1. 预处理:移除首尾空格(保留中间空格)
+  const cleanName = fileName.trim();
+
+  // 2. 提取文件扩展名(以最后一个点分隔)
+  const lastDotIndex = cleanName.lastIndexOf('.');
+  if (lastDotIndex < 0) return result; // 无扩展名
+
+  const baseName = cleanName.substring(0,  lastDotIndex).trim();
+  const extension = cleanName.substring(lastDotIndex  + 1);
+
+  // 3. 支持多种分隔符(中英文短横线)
+  const separators = ['-', '-', '—']; // 半角/全角短横线
+  let dashIndex = -1;
+
+  // 查找最后一个有效分隔符位置
+  for (const sep of separators) {
+    const index = baseName.lastIndexOf(sep);
+    if (index > dashIndex) dashIndex = index;
+  }
+
+  // 4. 核心解析逻辑
+  if (dashIndex > 0 && dashIndex < baseName.length  - 1) {
+    let artistPart = baseName.substring(0,  dashIndex).trim();
+    result.title  = baseName.substring(dashIndex  + 1).trim();
+
+    // 5. 处理前缀序号(如"04 - ")
+    const numPrefixRegex = /^\d+\s*[--—]\s*/; // 匹配数字+分隔符组合
+    artistPart = artistPart.replace(numPrefixRegex,  '').trim();
+
+    // 6. 最终有效性验证
+    result.artist  = artistPart;
+    result.isValid  = (result.artist.length  > 0 && result.title.length  > 0);
+  }
+
+  return result;
+}

+ 2 - 1
entry/src/main/ets/pages/NewIndex.ets

@@ -309,7 +309,7 @@ struct NewIndex{
               Image(item.img as Resource)
                 .height(22)
                 .alignSelf(ItemAlign.Center)
-                .fillColor($r('app.color.img_color'))
+                .fillColor(this.themeColor)
                 .margin({ left: 2 })
             }
 
@@ -449,6 +449,7 @@ struct NewIndex{
             }
           })
         }
+        .clickEffect({level:ClickEffectLevel.HEAVY})
         .transition(TransitionEffect.move(TransitionEdge.BOTTOM).animation({ duration: 600, curve: Curve.Ease, delay: 60*index }))
         .width('90%')
         .height(55)

+ 29 - 1
entry/src/main/ets/pages/SettingPage.ets

@@ -537,6 +537,7 @@ export struct SettingPage {
             }
             .backgroundColor(Color.Transparent)
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
             .onClick(() => {
               this.showColorPicker = false;
               this.isThemeSheet = true;
@@ -584,6 +585,7 @@ export struct SettingPage {
                   .align(Alignment.Center)
               }
             }
+            .clickEffect({level:ClickEffectLevel.HEAVY})
             .height(55)
             .onClick(() => {
               this.isCustomizeBgSheet = !this.isCustomizeBgSheet;
@@ -613,6 +615,7 @@ export struct SettingPage {
                 .height(30);
             }
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
 
@@ -637,6 +640,7 @@ export struct SettingPage {
                 .height(30);
             }
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
             // 显示私密音频
@@ -660,6 +664,7 @@ export struct SettingPage {
                 .height(30);
             }
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
             // 显示我的收藏
@@ -683,6 +688,7 @@ export struct SettingPage {
                 .height(30);
             }
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
             // 显示最近播放
@@ -706,7 +712,10 @@ export struct SettingPage {
                 .height(30);
             }
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
           }
+          .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
+            .animation({ duration: 500, curve: Curve.Ease,delay:200 }))
           .backgroundColor($r('app.color.settings_background_main'))
           .borderRadius(20)
           .margin({
@@ -750,6 +759,7 @@ export struct SettingPage {
                 .height(30);
             }
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
             // 记住播放进度
@@ -773,6 +783,7 @@ export struct SettingPage {
                 .height(30);
             }
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
             // 记忆播放
@@ -796,6 +807,7 @@ export struct SettingPage {
                 .height(30);
             }
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
             // 默认排序模式
@@ -827,6 +839,7 @@ export struct SettingPage {
                 })
             }
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
             // 保存播放模式
@@ -849,6 +862,7 @@ export struct SettingPage {
                 .height(30);
             }
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
               .visibility(Visibility.None)
@@ -870,7 +884,8 @@ export struct SettingPage {
                 .width(50)
                 .height(30);
             }
-            .height(55)
+            .height(55).clickEffect({level:ClickEffectLevel.HEAVY})
+
             .visibility(Visibility.None)
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
@@ -894,7 +909,10 @@ export struct SettingPage {
                 .height(30);
             }
             .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
           }
+          .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
+            .animation({ duration: 500, curve: Curve.Ease ,delay:400}))
           .backgroundColor($r('app.color.settings_background_main'))
           .borderRadius(20)
           .margin({
@@ -937,6 +955,7 @@ export struct SettingPage {
 
               }
             }
+            .clickEffect({level:ClickEffectLevel.HEAVY})
             .backgroundColor(Color.Transparent)
             .onClick(() => {
               this.apiDialogType = 'lyric'
@@ -964,6 +983,7 @@ export struct SettingPage {
 
               }
             }
+            .clickEffect({level:ClickEffectLevel.HEAVY})
             .backgroundColor(Color.Transparent)
             .onClick(() => {
               this.apiDialogType = 'cover'
@@ -973,6 +993,8 @@ export struct SettingPage {
             .height(55)
 
           }
+          .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
+            .animation({ duration: 600, curve: Curve.Ease ,delay:600}))
           .backgroundColor($r('app.color.settings_background_main'))
           .borderRadius(20)
           .margin({
@@ -983,6 +1005,8 @@ export struct SettingPage {
           })
           .padding(0)
 
+
+          //设置教程
           Button({ type: ButtonType.Capsule, stateEffect: true }) {
             Column() {
               Row() {
@@ -1008,6 +1032,7 @@ export struct SettingPage {
 
             }
           }
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .onClick(() => {
             const qqUrl =
               `mqqapi://card/show_pslcard?src_type=internal&version=1&uin=${CommonConstants.QQ_GROUP}&card_type=group&source=external`;
@@ -1110,6 +1135,7 @@ export struct SettingPage {
         .objectFit(ImageFit.Contain)
         .borderRadius(20)
         .clip(true)
+        .clickEffect({level:ClickEffectLevel.HEAVY})
         .blur(this.blurValue)
         .brightness(this.bgBrightness + 0.8)
         .margin({ left: 30, right: 30, bottom: 20 })
@@ -1123,6 +1149,7 @@ export struct SettingPage {
             .fontSize(12)
             .height(48)
             .width(100)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
             .backgroundColor(this.themeColor)
             .stateEffect(true)
             .onClick(async () => {
@@ -1153,6 +1180,7 @@ export struct SettingPage {
           Toggle({ type: ToggleType.Switch, isOn: this.isCustomizeBg })
             .selectedColor(this.themeColor)
             .switchPointColor(Color.White)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
             .margin({ right: 18 })
             .onChange((checked: boolean) => {
               this.isCustomizeBg = checked;

+ 13 - 6
entry/src/main/ets/pages/VipPage.ets

@@ -264,6 +264,7 @@ export  struct  VipPage{
           }
           .width('100%')
           .height(45)
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .onClick(()=>{
             ToastUtil.showToast('复制邮件地址成功!')
             Utility.copyText(CommonConstants.CONTACT_MAIL)
@@ -321,6 +322,7 @@ export  struct  VipPage{
               .height(22)
 
           }
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .visibility(Utility.isNoble()?Visibility.None:Visibility.Visible)
           .onClick(() => {
             this.payPrice = VipPage.TYPE_MONTH
@@ -372,6 +374,7 @@ export  struct  VipPage{
               .height(22)
 
           }
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .width('100%')
           .height(50)
           .onClick(() => {
@@ -430,6 +433,7 @@ export  struct  VipPage{
               .height(22)
 
           }
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .onClick(() => {
             this.payType =0
           })
@@ -469,6 +473,7 @@ export  struct  VipPage{
 
 
           }
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .onClick(() => {
             this.payType =1
           })
@@ -503,6 +508,7 @@ export  struct  VipPage{
               .align(Alignment.Center)
 
           }
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .width('100%')
           .height(45)
           .bindSheet($$this.isShowDutySheetView, this.DutySheet(), {
@@ -529,7 +535,7 @@ export  struct  VipPage{
             .layoutWeight(1)
             .height(50)
             .backgroundColor(this.themeColor)
-            .stateEffect(true)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
             .margin({left:10,right:25})
             .onClick(()=>{
               this.doPay()
@@ -795,10 +801,11 @@ export  struct  VipPage{
       Row(){
         Button('不同意')
           .fontColor(Color.White)
-          .backgroundColor($r('app.color.title_bar_bg'))
+          // .backgroundColor(this.themeColor)
           .height(50)
           .layoutWeight(1)
-          .stateEffect(true)
+          .linearGradient( { direction: GradientDirection.Right, colors:[['#ff37a0fc',0.0],['#67e667',0.5],['#f5856e',1.0]]})
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .margin({left:25,right:10})
           .onClick(()=>{
             this.isAgree = false
@@ -807,11 +814,11 @@ export  struct  VipPage{
           })
         Button('同意')
           .fontColor(Color.White)
-            //.linearGradient( { direction: GradientDirection.Right, colors:[['#ff37a0fc',0.0],['#67e667',0.5],['#f5856e',1.0]]})
+          .linearGradient( { direction: GradientDirection.Right, colors:[['#ff37a0fc',0.0],['#67e667',0.5],['#f5856e',1.0]]})
           .layoutWeight(1)
           .height(50)
-          .backgroundColor($r('app.color.title_bar_bg'))
-          .stateEffect(true)
+          // .backgroundColor(this.themeColor)
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .margin({left:10,right:25})
           .onClick(()=>{
             this.isAgree = true

+ 139 - 0
entry/src/main/ets/view/IndexerView.ets

@@ -0,0 +1,139 @@
+/**
+ * A custom indexer view with motion animation.
+ */
+@Component
+export struct IndexerView {
+    /**
+     * The index data of the indexer view.
+     */
+    @Prop indexArray: Array<string>
+    /**
+     * The size of the char text.
+     */
+    @Prop textSize: number
+    /**
+     * The value to observe the selected index.
+     */
+    @Prop selectedIndex: number
+    @State isPressed: boolean = false
+    @State charWidth: number = 0
+    @State charHeight: number = 0
+    private viewHeight: number = 0
+    /* ------------------ heightLight style  ------------------*/
+    /**
+     * The color of current selected index.
+     */
+    selectedColor: ResourceColor = "#000000"
+    /**
+     * The color of unselected index.
+     */
+    normalColor: ResourceColor = "#ff737373"
+    /* ------------------ float selected view style  ------------------*/
+    /**
+     * Display the float selected view or not.
+     */
+    isShowFloatSelectedView: boolean = true
+    /**
+     * The size of float selected view.
+     */
+    floatSelectedViewSize: number = 32
+    /**
+     * The color of float selected view text.
+     */
+    floatSelectedTextColor: ResourceColor = "#ffffff"
+    /**
+     * The color of float selected view background.
+     */
+    floatSelectedViewColor: ResourceColor = "#000000"
+    /* ------------------ animation style  ------------------*/
+    /**
+     * The duration of animation for this view.
+     */
+    animationDuration: number = 300
+    /**
+     * The translation size of animation.
+     */
+    animationTranslation: number = 32
+    /**
+     * The observe to watch the index changed by user.
+     */
+    onIndexChanged: ((index: number) => void) | null = null
+
+    build() {
+        Stack() {
+            // anchor
+            Column() {
+                ForEach(this.indexArray, (char: string, index: number) => {
+                    Text(char)
+                        .fontSize(this.textSize)
+                        .fontColor(this.selectedIndex == index ? this.selectedColor : this.normalColor)
+                        .fontWeight(this.selectedIndex == index ? FontWeight.Bold : FontWeight.Normal)
+                        .textAlign(TextAlign.Center)
+                        .width("100%")
+                        .height(this.charHeight)
+                        .translate({
+                            x: this.selectedIndex == index && this.isPressed ? -this.animationTranslation :
+                                this.selectedIndex == index + 1 && this.isPressed || this.selectedIndex == index - 1 && this.isPressed ? -this.animationTranslation * 0.75 :
+                                    this.selectedIndex == index + 2 && this.isPressed || this.selectedIndex == index - 2 && this.isPressed ? -this.animationTranslation * 0.5 :
+                                        this.selectedIndex == index + 3 && this.isPressed || this.selectedIndex == index - 3 && this.isPressed ? -this.animationTranslation * 0.25 : 0
+                        })
+                        .animation({
+                            duration: this.animationDuration
+                        })
+                })
+            }
+            .width("100%")
+            .height("100%")
+
+            // float selected view
+            if (this.isShowFloatSelectedView) {
+                Text(this.indexArray[this.selectedIndex])
+                    .fontColor(this.floatSelectedTextColor)
+                    .textAlign(TextAlign.Center)
+                    .fontSize(this.floatSelectedViewSize * 0.6)
+                    .width(this.floatSelectedViewSize)
+                    .height(this.floatSelectedViewSize)
+                    .border({ radius: this.floatSelectedViewSize })
+                    .visibility(this.isPressed && this.selectedIndex >= 0 && this.selectedIndex <= this.indexArray.length - 1 ? Visibility.Visible : Visibility.Hidden)
+                    .backgroundColor(this.floatSelectedViewColor)
+                    .position({
+                        x: -this.animationTranslation - this.floatSelectedViewSize * 1.2,
+                        y: this.selectedIndex * this.charHeight - (this.floatSelectedViewSize - this.charHeight) * 0.5
+                    })
+                    .animation({
+                        duration: this.animationDuration
+                    })
+            }
+        }
+        .align(Alignment.TopStart)
+        .onAreaChange((_, newSize) => {
+            this.charWidth = newSize.width as number
+            this.viewHeight = newSize.height as number
+            this.charHeight = this.viewHeight / this.indexArray.length
+        })
+        .onTouch((event) => {
+            let motionEvent = event.touches[0]
+            switch (motionEvent.type) {
+                case TouchType.Down:
+                case TouchType.Move:
+                    this.isPressed = true
+                    let currentY = motionEvent.y
+                    if (currentY <= 0) {
+                        this.selectedIndex = 0
+                    } else if (currentY > this.viewHeight) {
+                        this.selectedIndex = this.indexArray.length - 1
+                    } else {
+                        this.selectedIndex = Math.round(currentY / this.charHeight)
+                    }
+                    this.onIndexChanged?.(this.selectedIndex)
+                    break
+                case TouchType.Up:
+                case TouchType.Cancel:
+                    this.isPressed = false
+                    break
+                default:
+                    break
+            }
+        })
+    }
+}

+ 76 - 18
entry/src/main/ets/view/LocalMusic.ets

@@ -85,9 +85,11 @@ import { LrcParser } from '@sgaolei/lrc_parser';
 import app, { AppResponse } from '@system.app';
 import { Log } from '@tencent/wechat_open_sdk';
 import { TextNodeController } from './PipLyricTextBuilder';
+import { IndexerView } from './IndexerView';
 
 const TAG = 'LocalMusic';
 
+const DEFAULT_INDEX = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
 
 //排序类型
 function getTypeOrder(type: number) {
@@ -185,6 +187,7 @@ export struct LocalMusic {
   @State blurValue: number = 0 //背景模糊
   @State bgBrightness: number = 0 //背景亮度
   private listScroller: ListScroller = new ListScroller()
+  private playListScroller: Scroller = new Scroller()
   private listArea: Area = {
     width: 0,
     height: 0,
@@ -1862,16 +1865,42 @@ export struct LocalMusic {
           .height(CommonConstants.FULL_PERCENT)
           .width(CommonConstants.FULL_PERCENT)
 
+          Stack() {
+            Row() {
+              Blank()
+                .layoutWeight(1)
+              IndexerView(
+                {
+                  indexArray: DEFAULT_INDEX,
+                  selectedIndex: this.selectedIndex,
+                  textSize: 12,
+                  selectedColor: "#ff419ee5",
+                  normalColor: "#ffa0a0a0",
+                  floatSelectedTextColor: "#ffffff",
+                  floatSelectedViewColor: "#ff419ee5",
+                  onIndexChanged: (index) => {
+                    this.listScroller.scrollToIndex(index)
+                  }
+                })
+                .width(24)
+                .height("50%")
+            }.visibility(Visibility.None)
 
-          Column() {
-            this.tabTitle()
-            this.listViewTitle()
-            if(this.isGridMusic){
-              this.getGridView()
-            }else {
-              this.getListView()
+            Column() {
+              this.tabTitle()
+              this.listViewTitle()
+              if(this.isGridMusic){
+                this.getGridView()
+              }else {
+                this.getListView()
+              }
             }
+            .layoutWeight(1)
+            .height('100%')
+
+
           }
+
           .layoutWeight(1)
           .height('100%')
 
@@ -1882,6 +1911,7 @@ export struct LocalMusic {
               .height(60)
               .margin({ top: 10, bottom: 10, right: 6 })
               .backgroundColor(this.themeColor)
+              .clickEffect({level:ClickEffectLevel.HEAVY})
               .onClick(() => {
                 if (this.isAllSelected) {
                   // 全不选
@@ -1897,6 +1927,7 @@ export struct LocalMusic {
               .height(60)
               .id('music_cut')
               .margin({ top: 10, bottom: 10, right: 6 })
+              .clickEffect({level:ClickEffectLevel.HEAVY})
               .backgroundColor(this.themeColor)
               .onClick(() => {
                 this.showCutDialogForMultipleFiles(false, 'music_cut')
@@ -1909,6 +1940,7 @@ export struct LocalMusic {
               .visibility(this.isHasDir ? Visibility.Visible : Visibility.None)
               .margin({ top: 10, bottom: 10, right: 6 })
               .backgroundColor(this.themeColor)
+              .clickEffect({level:ClickEffectLevel.HEAVY})
               .onClick(() => {
 
                 this.showCutDialogForMultipleFiles(true, 'music_cut_current')
@@ -1919,6 +1951,7 @@ export struct LocalMusic {
               .id('music_copy_current')// .visibility(this.isHasDir?Visibility.None:Visibility.None)
               .margin({ top: 10, bottom: 10, right: 6 })
               .backgroundColor(this.themeColor)
+              .clickEffect({level:ClickEffectLevel.HEAVY})
               .onClick(() => {
                 this.showCopyDialogForMultipleFiles(false, 'music_copy_current')
               })
@@ -1929,6 +1962,7 @@ export struct LocalMusic {
               .margin({ top: 10, bottom: 10, right: 6 })
               .visibility(this.isFavMusic?Visibility.None:Visibility.Visible)
               .backgroundColor(this.themeColor)
+              .clickEffect({level:ClickEffectLevel.HEAVY})
               .onClick(() => {
                 this.showWarnIsDelete()
               })
@@ -1937,6 +1971,7 @@ export struct LocalMusic {
               .height(60)
               .margin({ top: 10, bottom: 10, right: 6 })
               .backgroundColor(this.themeColor)
+              .clickEffect({level:ClickEffectLevel.HEAVY})
               .onClick(() => {
                 this.selectedFiles = [];
                 this.isAllSelected = false
@@ -2626,6 +2661,7 @@ export struct LocalMusic {
               curve: 'ease-in-out' // 可选动画曲线
             })
         }
+        .clickEffect({level:ClickEffectLevel.LIGHT})
         .visibility(this.isSearchMode ? Visibility.None : Visibility.Visible)
         .animation({
           duration: 666,
@@ -2687,7 +2723,7 @@ export struct LocalMusic {
             curve: 'ease-in-out' // 可选动画曲线
           })// .opacity(this.opacityItem)
           .fillColor(this.themeColor)
-          .clickEffect({ level: ClickEffectLevel.MIDDLE })
+          .clickEffect({level:ClickEffectLevel.LIGHT})
           .onClick(() => {
             this.isSearchMode = true
           })
@@ -2700,7 +2736,7 @@ export struct LocalMusic {
             duration: 666,
             curve: 'ease-in-out' // 可选动画曲线
           })
-          .clickEffect({ level: ClickEffectLevel.MIDDLE })
+          .clickEffect({level:ClickEffectLevel.LIGHT})
           .onClick(() => {
             if(this.isFavMusic){
               this.getFavList(true)
@@ -2719,7 +2755,7 @@ export struct LocalMusic {
             curve: 'ease-in-out' // 可选动画曲线
           })
           .fillColor(this.themeColor)
-          .clickEffect({ level: ClickEffectLevel.MIDDLE })
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .onClick(() => {
             this.showRankDialog()
           })
@@ -2932,7 +2968,7 @@ export struct LocalMusic {
 
           }
         }
-
+        .clickEffect({level:ClickEffectLevel.LIGHT})
         .scale({ x: this.scaleItem === index ? 1.02 : 1, y: this.scaleItem === index ? 1.02 : 1 })
         .zIndex(this.dragItem === index ? 1 : 0)
         .translate(this.dragItem === index ? { x: this.offsetX, y: this.offsetY } : { x: 0, y: 0 })
@@ -3046,7 +3082,7 @@ export struct LocalMusic {
   @State tempLyricContent:string = ''
   @Builder
   private MusicItemGrid(item: VideoItem, index: number) {
-    Button({ type: ButtonType.Normal, stateEffect: true }) {
+    Button({ type: ButtonType.Normal, stateEffect: false }) {
       Stack() {
         Column() {
           Image(StrUtil.isEmpty(item.pixelMapPath) ?Utility.getMusisBg2(index) : item.pixelMapPath)
@@ -3261,6 +3297,7 @@ export struct LocalMusic {
   }
 
   private listMaxScrollOffsetY: number = 0
+  @State selectedIndex: number = -1
   @Builder
   getListView() {
 
@@ -3318,6 +3355,7 @@ export struct LocalMusic {
           .animation({ curve: Curve.Sharp, duration: 300 })
 
         }
+        .clickEffect({level:ClickEffectLevel.LIGHT})
         //.transition(TransitionEffect.move(TransitionEdge.BOTTOM).animation({ duration: 800, curve: Curve.Ease, delay: 100*index }))
         .swipeAction((
           (this.modeType==0||this.modeType==1 ||((this.modeType==3||this.modeType==2)&&this.isCanBack))
@@ -3328,6 +3366,7 @@ export struct LocalMusic {
           this.doPlay(item, index)
 
         })
+
         //拖动List关键代码开始
         .scale({ x: this.scaleSelect(index), y: this.scaleSelect(index) })
         .zIndex(this.dragItem === index ? 1 : 0)
@@ -3440,6 +3479,9 @@ export struct LocalMusic {
     .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
       .animation({ duration: 500, curve: Curve.Ease }))
     .layoutWeight(1)
+    .onScrollIndex((start) => {
+      this.selectedIndex = start
+    })
     .visibility(this.isGridMusic?Visibility.None:Visibility.Visible)
     .lanes(
       new BreakpointType<number>({
@@ -3693,6 +3735,7 @@ export struct LocalMusic {
           Image($r('app.media.hm_previous'))
             .height(24)
             .width(24)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
             .margin({ left: 8, right: 16 })
             .fillColor(this.themeColor)
             .displayPriority(2)
@@ -3720,6 +3763,7 @@ export struct LocalMusic {
               .height(32)
               .width(32)
               .displayPriority(3)
+              .clickEffect({level:ClickEffectLevel.HEAVY})
               .fillColor(this.themeColor)
               .onClick(() => {
                 if (ArrayUtil.isNotEmpty(this.songList)) {
@@ -3739,6 +3783,7 @@ export struct LocalMusic {
               left: 16
             })
             .fillColor(this.themeColor)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
             .displayPriority(2)
             .onClick(() => {
               if (ArrayUtil.isNotEmpty(this.songList)) {
@@ -3753,6 +3798,7 @@ export struct LocalMusic {
             .height(24)
             .width(24)
             .displayPriority(1)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
             .fillColor(this.themeColor)
             .bindSheet($$this.isShowSheet, this.PlayListSheet(), {
               height: '95%',
@@ -3770,6 +3816,8 @@ export struct LocalMusic {
               if (ArrayUtil.isNotEmpty(this.songList)) {
                 this.isShowSheet = !this.isShowSheet;
                 this.isFrontWhite = false
+                LogUtil.info('onecold scrollToIndex = '+this.curIndex)
+                this.playListScroller.scrollToIndex(this.curIndex,true, ScrollAlign.CENTER)
               } else {
                 ToastUtil.showToast('当前播放列表为空,请先导入音乐。')
               }
@@ -3796,7 +3844,7 @@ export struct LocalMusic {
         // .visibility(this.isPhoneLan()?Visibility.None:this.isCustomizeBg?Visibility.Hidden:Visibility.Visible)
         .visibility(this.isCustomizeBg?Visibility.Hidden:Visibility.Visible)
     }
-
+    .clickEffect({level:ClickEffectLevel.HEAVY})
   }
 
   @State isFrontWhite: boolean = false
@@ -4597,7 +4645,7 @@ export struct LocalMusic {
 
   @Builder
   PlayList() {
-    List() {
+    List({ scroller: this.playListScroller }) {
       LazyForEach(this.sonDataSource, (item: VideoItem, index: number) => {
         ListItem() {
           this.MusicItemSon(item, index)
@@ -4607,7 +4655,7 @@ export struct LocalMusic {
           duration: 666,
           curve: 'ease-in-out' // 可选动画曲线
         })
-
+        .clickEffect({level:ClickEffectLevel.MIDDLE})
         //拖动List关键代码开始
         .scale({ x: this.scaleSelect(index), y: this.scaleSelect(index) })
         .zIndex(this.dragItem === index ? 1 : 0)
@@ -5586,7 +5634,9 @@ export struct LocalMusic {
           .width(39)
           .alt(this.imageLabel)
           .borderRadius(8)
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .margin({ left: this.currentLyricAlignMode === 0 ? 25 : -15 })
+
       }
       .width('15%')
 
@@ -5625,6 +5675,7 @@ export struct LocalMusic {
           .width(25)
           .aspectRatio(CommonConstants.ASPECT_RATIO)
           .margin({ right: 30 })
+          .clickEffect({level:ClickEffectLevel.HEAVY})
           .onClick(() => {
 
             this.isLyricSetting = !this.isLyricSetting;
@@ -5658,7 +5709,7 @@ export struct LocalMusic {
       }
       .height(this.isPuraWP() ? 15 : 58)
       .visibility(this.currentBreakpoint !== BreakpointTypeEnum.SM ?
-      Visibility.None : Visibility.Visible)
+        Visibility.None: Visibility.Visible)
       .margin({ left: 6, top: this.isPuraWP() ? 5 : 38 })
       .width("100%")
 
@@ -5685,7 +5736,7 @@ export struct LocalMusic {
         })
           .width("100%")
           .layoutWeight(1)
-          .margin({ left: this.currentLyricAlignMode === 0 ? 0 : 50, top: 2, bottom: 10 })
+          .margin({ left: this.currentLyricAlignMode === 0 ? 0 : 50, top: 2, bottom: this.isPhoneLan() ? 40 :10 })
 
       }
       .position({
@@ -5712,6 +5763,7 @@ export struct LocalMusic {
             //更多功能
             Image($r('app.media.menu'))
               .width(24)
+              .clickEffect({level:ClickEffectLevel.MIDDLE})
               .bindSheet($$this.isShowMoreView, this.PlayMoreSheet(), {
                 height: this.isCoverOpacity() ? '95%' : '93%',
                 preferType: this.currentBreakpoint!==BreakpointTypeEnum.SM?SheetType.CENTER:SheetType.BOTTOM,
@@ -5729,6 +5781,7 @@ export struct LocalMusic {
           Button({type:ButtonType.Circle,stateEffect:true}){
             Image($r('app.media.ic_previous'))
               .width(33)
+              .clickEffect({level:ClickEffectLevel.HEAVY})
               .aspectRatio(CommonConstants.ASPECT_RATIO)
               .onClick(async () => {
                 this.playPrevious()
@@ -5744,6 +5797,7 @@ export struct LocalMusic {
             Image(this.CONTROL_PlayStatus === PlayStatus.PLAY ?
             $r('app.media.ic_public_play') : $r('app.media.ic_public_pause'))
               .width(this.isPhoneLan()?48:60)
+              .clickEffect({level:ClickEffectLevel.HEAVY})
               .aspectRatio(CommonConstants.ASPECT_RATIO)
               .onClick(async () => {
                 this.playOrPause()
@@ -5762,6 +5816,7 @@ export struct LocalMusic {
           Button({ type: ButtonType.Capsule, stateEffect: true }) {
             Image($r('app.media.ic_next'))
               .width(33)
+              .clickEffect({level:ClickEffectLevel.HEAVY})
               .aspectRatio(CommonConstants.ASPECT_RATIO)
               .onClick(() => {
 
@@ -5777,6 +5832,7 @@ export struct LocalMusic {
             Image(Utility.getIsFav(this.favList,this.currentSong)?
             $r('app.media.add_fac_light'):$r('app.media.add_fac'))
               .width(24)
+              .clickEffect({level:ClickEffectLevel.MIDDLE})
               .aspectRatio(CommonConstants.ASPECT_RATIO)
               .onClick(async () => {
                 if(this.currentSong){
@@ -5902,6 +5958,7 @@ export struct LocalMusic {
       .onClick(() => {
         this.isShowSheetView = !this.isShowSheetView;
         this.isFrontWhite = true
+        // this.listScroller.scrollToIndex(this.curIndex)
       })
 
     }
@@ -5978,6 +6035,7 @@ export struct LocalMusic {
           .aspectRatio(1)
           .visibility(this.isPuraWP()?Visibility.None:Visibility.Visible)
           .borderRadius(this.isCoverRectangle ? 20 : '100%')
+          .clickEffect({level:ClickEffectLevel.MIDDLE})
           .align(Alignment.Center)// .opacity(this.isCoverOpacity()||this.isCoverRectangle?0:1)
           .clip(true)
           .rotate({
@@ -6008,6 +6066,7 @@ export struct LocalMusic {
             .width('48%')
             .textAlign(TextAlign.Center)
             .fontColor(Color.White)
+            .visibility(this.isPhoneLan()?Visibility.None:Visibility.Visible)
           Text(this.artist)
             .fontSize(this.isPuraWP()?18:(this.isCoverOpacity() ? 12 : 15))
             .fontColor(Color.White)
@@ -6016,7 +6075,6 @@ export struct LocalMusic {
         }
         .justifyContent(FlexAlign.Start)
         .zIndex(1)
-        // .margin({bottom:180})
         .visibility(this.isCoverOpacity() ? Visibility.Visible : Visibility.None)
 
       }

+ 2 - 0
entry/src/main/ets/viewmodel/VideoItem.ets

@@ -47,6 +47,7 @@ export class VideoItem  {
 
   md5Str?:string
   extra_json?:string
+  pyStr?:string//中文歌曲名称拼音的首字母
 
   constructor(name: string, id: string, filePath: string, type:number,videoSize:number,cTime: string,pixelMap?: image.PixelMap,
     size?: string,pixelMapPath?: string,artist?: string,album?: string,fileName?: string, lastPlayed?:Date) {
@@ -73,5 +74,6 @@ export class VideoItem  {
     this.playCount = 0
     this.md5Str = ''
     this.extra_json = ''
+    this.pyStr = ''
   }
 }

+ 1 - 1
entry/src/main/resources/base/media/hm_play2.svg

@@ -1 +1 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1750044112792" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2169" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 0C229.227168 0 0 229.227168 0 512s229.227168 512 512 512S1024 794.772832 1024 512 794.772832 0 512 0z m183.134237 556.605165l-234.455205 209.955734c-37.541754 23.428835-84.621894-5.853733-84.621894-52.725307v-419.911468c0-46.85767 47.010618-76.154143 84.621894-52.725308l234.455205 209.955734c37.611276 23.44274 37.611276 82.02178 0 105.450615z" p-id="2170"></path></svg>
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1750766060440" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1523" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M511.996 1.28C229.933 1.28 1.276 229.937 1.276 512s228.657 510.72 510.72 510.72c282.07 0 510.72-228.657 510.72-510.72 0.004-282.067-228.65-510.72-510.72-510.72z m249.46 520.727c-25.906 15.157-329.382 192.31-344.566 201.08-18.953 10.96-38.122-2.982-38.122-21.717V298.4c0-20.641 21.186-30.664 37.342-21.583 22.07 12.371 326.692 190.424 345.345 201.534 16.733 9.969 17.006 33.703 0 43.656z" fill="#ffffff" p-id="1524"></path></svg>