HarmonyOS鸿蒙Next中list里在播放音频时如何更新item

HarmonyOS鸿蒙Next中list里在播放音频时如何更新item 使用的是abner/refresh 加载数据,ef_audio 播放音频。当某条itemlayout音频在播放时,列表上需要动态显示播放进度(progress)并且播放图标变化。于此同时,其他的在播的itemlayout 的 progress 需要归零。我是用的更新当前itemlayout上的 Databean 数据,然后刷新的方法。但是这样做,当前条目会一直闪,而且闪几秒应用就崩溃了.有点懵

import { ListView, RefreshController, RefreshDataSource } from '@abner/refresh'
import { DefaultMediaModel, DefaultMediaType, MediaPreview, MediaPreviewOptions } from '@lyb/media-preview'
import { APIs } from '../common/APIs'
import { CommonConstants } from '../common/CommonConstants'
import { DataBean, FileBean } from '../models/DataBean'
import { HttpUtils, ResType } from '../utils/HttpUtils'
import { getRecordTime } from '../utils/Utils'
import HdNav from '../views/HdNav'
import { EfAVPlayer, EfAVPlayerState } from '@yunkss/ef_audio'

@Component
struct MyData {
  @State type: number = 0
  @State showEmptyLayout: boolean = false
  @State heightMap: Map<number, number> = new Map([])
  //音频播放
  @State currentIndex: number = 0;
  efAVPlay: EfAVPlayer = new EfAVPlayer()
  controller: RefreshController = new RefreshController() //刷新控制器
  lazyDataSource?: RefreshDataSource = new RefreshDataSource()
  private page: number = 1;
  //可在初始化的时候set回调,注:options 也不一定需要全局申明,可在点击的时候创建
  options: MediaPreviewOptions<DefaultMediaModel> = new MediaPreviewOptions<DefaultMediaModel>()
    .setPageChangedListener((_item, index) => {
      this.index = index
    })
    .setBackListener(() => {
      this.index = -1
    })
  @State index: number = -1

  aboutToAppear(): void {
    const params = this.getUIContext().getRouter().getParams() as Record<string, number>; // 获取传递过来的参数对象
    if (params) {
      this.type = params.type as number
    }
    this.page = 1
    this.getdata()
    this.efAVPlay.init()
    this.efAVPlay.onStateChange((state: EfAVPlayerState) => {
      if (state === 'stopped' || state === 'completed') {
        let beans: DataBean[] = this.lazyDataSource?.getDataAll() as DataBean[]
        beans.forEach((bean: DataBean) => {
          bean.value = 0
          bean.play = 0
        })
        this.lazyDataSource?.modifyAllData()
      }
    })
    this.efAVPlay.onTimeUpdate((time: number) => {
      let bean: DataBean = this.lazyDataSource?.getData(this.currentIndex) as DataBean
      bean.play = 1
      bean.value = time
      bean.total = this.efAVPlay.duration
      this.lazyDataSource?.changeData(this.currentIndex,bean)
    })
  }

  getdata() {
    const params: object =
      Object({ 'page': this.page, 'limit': CommonConstants.PAGESIZE, 'cate_id': this.type })
    HttpUtils.post(APIs.myData, params, '').then((res: ResType) => {
      if (this.page == 1) {
        this.controller.finishRefresh()
      } else {
        this.controller.finishLoadMore()
      }
      let beans: DataBean[] = res.data
      if (beans.length > 0) {
        this.showEmptyLayout = false
        if (this.page == 1) {
          this.lazyDataSource?.initData(beans)
        } else {
          this.lazyDataSource?.pushDataArray(beans)
        }
        this.page += 1
      } else {
        if (this.page == 1) {
          this.showEmptyLayout = true
        } else {
          this.controller.finishLoadMore(true)
        }
      }
    }).catch((err: Error) => {
      if (this.page == 1) {
        this.showEmptyLayout = true
      } else {
        this.controller.finishLoadMore(false)
      }
    })
  }

  build() {
    Column() {
      HdNav({
        title: this.getTitle()
      })

      ListView({
        lazyDataSource: this.lazyDataSource,
        itemLayout: (item, index) => this.itemLayout(item as DataBean, index),
        controller: this.controller,
        emptyLayout: this.emptyLayout,
        showEmptyLayout: this.showEmptyLayout,
        isRefreshSticky: false,
        onRefresh: () => {
          //下拉刷新 模拟耗时
          this.page = 1;
          this.getdata();
        },
        onLoadMore: () => {
          // 上拉加载 模拟耗时
          this.getdata();
        }
      }).margin({ top: -32 })
    }
    .height('100%')
    .width('100%').backgroundColor($r('app.color.color_EDEEF5'))
  }

  getTitle(): string {
    if (this.type == 1) {
      return '我的音频'
    } else if (this.type == 2) {
      return '我的图片'
    } else if (this.type == 3) {
      return '我的文本'
    } else if (this.type == 4) {
      return '我的视频'
    } else {
      return '我的数据'
    }
  }

  @Builder
  itemLayout(item: DataBean, position: number): void {
    Column() {
      Row() {
        // 左侧时间轴
        Column() { // 前景圆点和短线
          Divider()
            .width(2)
            .height(14)
            .visibility(position == 0 ? Visibility.Hidden : Visibility.Visible)
            .backgroundColor($r('app.color.color_D9DAE3'))

          Circle()
            .width(8)
            .height(8)
            .fill(Color.White)
            .stroke($r('app.color.color_9A1E2E'))
            .strokeWidth(2)

          Divider()
            .width(2)
            .backgroundColor($r('app.color.color_D9DAE3'))
            .height(this.heightMap.get(position)) // 让分割线填充剩余空间
        }
        .alignItems(HorizontalAlign.Center)
        .margin({ left: 10 })
        .width(20) // 固定时间轴宽度

        // 右侧内容
        Column() {
          Text(item.upload_time)
            .fontSize(13)
            .width('100%')
            .padding({
              bottom: 10
            })
            .textAlign(TextAlign.Start)
            .alignSelf(ItemAlign.Center)
            .fontColor($r('app.color.color_000000'))

          Column() {
            Text(item.task_title)
              .fontSize(13)
              .fontColor($r('app.color.color_000000'))
              .width('100%')
              .textAlign(TextAlign.Start)
              .padding({
                top: 10,
                bottom: 10
              })

            if (item.cate == 1) {
              // 音频播放器
              Row() {
                Image(item.play == 1 ? $r('app.media.icon_record_data_play') : $r('app.media.icon_record_data_pause'))
                  .width(22)
                  .height(22)
                  .onClick(() => {
                    if (item.result[0].path) {
                      this.efAVPlay.stop()
                      this.currentIndex = position
                      this.efAVPlay.setUrl(item.result[0].path)
                    }
                  })

                Progress({ value: item.value, total: item.total })
                  .layoutWeight(1)
                  .margin({ left: 8, right: 8 })

                Text(getRecordTime(item.result[0].duration))
                  .fontSize(10)
                  .fontColor($r('app.color.color_9B9B9B'))
              }
              .height(36)
              .margin({ top: 10 })
              .padding({ left: 14, right: 14 })
              .backgroundColor($r('app.color.color_EDEEF5'))
              .borderRadius(100)
            }

            //图片或者视频
            if (item.cate == 2 || item.cate == 4) {
              Row() {
                ForEach(item.result.slice(0, Math.min(3, item.result.length)), (fileBean: FileBean, index: number) => {
                  Stack() {
                    Image(fileBean.cate == 2 ? fileBean.path : fileBean.snapshot)
                      .width('31%')// (100% - 2 * 3.5%) / 3 ≈ 31%
                      .aspectRatio(1)// 保持宽高比 1:1
                      .borderRadius(8)
                      .objectFit(ImageFit.Cover)
                      .onClick(() => {
                        let resources: DefaultMediaModel[] = getMedia(item.result)
                        this.options
                          .setInitIndex(index)
                          .setMedias(resources)
                        MediaPreview.open(this.getUIContext(), this.options)
                      })

                    if (fileBean.cate == 4) {
                      Image($r('app.media.icon_data_play')).width(24).height(24)
                        .objectFit(ImageFit.Fill)
                    }

                    if (index === 2 && item.result.length > 3) {
                      Column() {
                        Text(`+${item.result.length - 3}`)
                          .fontSize(16)
                          .fontWeight(FontWeight.Bold)
                          .fontColor(Color.White)
                      }
                      .width('31%')
                      .aspectRatio(1)
                      .backgroundColor('rgba(0, 0, 0, 0.5)')
                      .borderRadius(8)
                      .justifyContent(FlexAlign.Center)
                    }
                  }.margin({ right: index < 2 ? '3.5%' : 0 })
                })
              }
              .width('100%')
            }

            // 文件列表
            if (item.cate == 3) {
              ForEach(item.result, (fileBean: FileBean) => {
                Row() {
                  Image($r('app.media.icon_default_text'))
                    .width(16)
                    .height(16)
                  Text(fileBean.file_name)
                    .fontSize(12)
                    .fontColor($r('app.color.color_333333'))
                    .margin({ left: 8 })
                }
                .margin({ top: 10 })
                .height(36)
                .width('100%')
                .padding({ left: 14, right: 14 })
                .backgroundColor($r('app.color.color_EDEEF5'))
                .borderRadius(100)
              })
            }

            // 参数信息
            if (item.result.length == 1) {
              Text('音频参数:' + item.result[0].data_param)
                .fontSize(11)
                .width('100%')
                .padding({ top: 10, bottom: 10 })
                .textAlign(TextAlign.Start)
                .fontColor($r('app.color.color_828282'))
            }

            // 底部操作栏
            Row() {
              Text('DID:4353')
                .fontSize(10)
                .fontColor($r('app.color.color_626262'))
                .layoutWeight(1)
                .visibility(Visibility.Hidden)

              Image($r('app.media.icon_audio_message'))
                .width(16)
                .height(16)
                .onClick(() => {

                })

              Image($r('app.media.icon_audio_edit'))
                .width(16)
                .height(16)
                .margin({ left: 16 })
                .visibility(Visibility.None)
            }.width('100%')
            .height(32)
            .padding({ top: 10, bottom: 10 })
          }
          .padding({ left: 12, right: 12 })
          .backgroundColor($r('app.color.color_ffffff'))
          .borderRadius(16)
        }
        .margin({ left: 10, right: 10 })
        .layoutWeight(1)
        .onAreaChange((oldVal: Area, newVal: Area) => {
          let hei = newVal.height.valueOf() as number
          this.heightMap.set(position, hei)
        })
      }.width('100%')
    }
  }

  /*
  * 空布局
  */
  @Builder
  emptyLayout() {
    Column() {
      Image($r('app.media.icon_data_empty')).width(50).height(50).margin({ top: 40 })
      Text("暂时没有数据").fontSize(14).margin({ top: 10 }).fontColor($r('app.color.color_333333'))
    }.width("100%")
    .height("100%")
    .backgroundColor($r('app.color.color_EDEEF5'))
    .align(Alignment.Top)
  }

  aboutToDisappear(): void {
    // 在aboutToDisappear中:
    this.lazyDataSource?.deleteAll()
  }
}

function getMedia(result: FileBean[]): DefaultMediaModel[] {
  let models: DefaultMediaModel[] = []
  result.forEach(element => {
    models.push(new DefaultMediaModel(element.path, element.snapshot,
      element.cate == 4 ? DefaultMediaType.VIDEO : DefaultMediaType.IMAGE))
  });
  return models
}

更多关于HarmonyOS鸿蒙Next中list里在播放音频时如何更新item的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

在HarmonyOS鸿蒙Next中,当在List组件中播放音频时,更新item可以通过List组件的setItem方法来实现。首先,确保你已经在List组件中绑定了数据源,并且每个item都有唯一的标识符。当音频播放状态发生变化时,可以通过调用setItem方法来更新特定的item。例如,假设你有一个List组件,并且每个item包含一个音频播放按钮和播放状态显示。当用户点击播放按钮时,可以先获取当前item的数据,更新其播放状态,然后调用setItem方法将更新后的数据重新设置到List中。这样,List组件会自动刷新并显示更新后的item内容。具体代码示例如下:

// 假设listItem是List组件的一个item
let updatedItem = listItem;
updatedItem.audioState = 'playing'; // 更新播放状态
this.list.setItem(listItem.index, updatedItem); // 更新List中的item

更多关于HarmonyOS鸿蒙Next中list里在播放音频时如何更新item的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,当List中的某个item正在播放音频时,可以通过以下步骤更新item

  1. 数据更新:首先更新与item相关的数据源,例如播放状态、进度等。
  2. 通知更新:通过ListnotifyItemChanged(index)方法通知列表刷新指定的item,确保UI与数据同步。
  3. 避免性能问题:如果频繁更新,建议使用DiffUtilAsyncListDiffer来优化列表更新性能。
回到顶部