HarmonyOS鸿蒙Next中仓颉使用this.scroller.scrollToIndex(Int32(index), smooth: true, align: ScrollAlign.CENTER)时,自动滚动时内容不能固定中央,觉得函数没生效

HarmonyOS鸿蒙Next中仓颉使用this.scroller.scrollToIndex(Int32(index), smooth: true, align: ScrollAlign.CENTER)时,自动滚动时内容不能固定中央,觉得函数没生效

/*

  • Copyright © Huawei Technologies Co., Ltd. 2025-2025. All rights reserved. */

package lyricview.view

internal import ohos.base.AppLog internal import ohos.base.LengthProp internal import ohos.component.Column internal import ohos.component.Row internal import ohos.component.ViewBuilder internal import ohos.component.Button internal import ohos.component.If internal import ohos.component.Text internal import ohos.component.CustomView internal import ohos.component.CJEntry internal import ohos.component.loadNativeView internal import ohos.state_manage.SubscriberManager internal import ohos.state_manage.ObservedProperty internal import ohos.state_manage.LocalStorage import ohos.state_macro_manage.Entry import ohos.state_macro_manage.Component import ohos.state_macro_manage.State import ohos.state_macro_manage.r import ohos.component.Stack import ohos.hilog.Hilog import std.math.* import lyricview.bean.* import lyricview.extensions.LyricLineDataSource import std.collection.* import ohos.component.* import ohos.base.* import std.core.Error import lyricview.* import cj_res_lyricview.app import ohos.state_macro_manage.Builder import ohos.system_date_time.* import ohos.component.Scroller

@Component public class LyricView { public var currentLyric: Option<Lyric> = None var listAdapter: LyricLineDataSource = LyricLineDataSource(ArrayList<LyricLine>()) let scroller: Scroller = Scroller() @State var isLyricEmpty: Bool = true @State var emptyHint: String = “” var appResource: String = “”; @State var textColor: Color = Color(0x000000) @State var textHighlightColor: Color = Color(0x000000) @State var textHighlightSize: Float64 = 18.0 @State var isHighlightBold: Bool = false @State var currentIndex: Int64 = 0 @State var isUserTouching: Bool = false @State var alignMode: AlignMode = AlignMode.Center @State var isLoadingData: Bool = false @State var mFromUserTouch = false @State var animDuration: Int32 = 300 @State var cacheSize: Int32 = 0 @State var textSize: Float64 = 16.0 @State var lineSpace: Float64 = 16.0 @State var sizeValue: String = “” @State var seekIndex:Int64 = -1 @State var scrollDurationText: String = ‘00:00’ @State var seekUIStyle: String = “listItem” @State var w: Float64 = 0.0 // the width of this view. @State var h: Float64 = 0.0 // the height of this view. var centerOffsetSize: Int64 = 0 @State var enableSeek: Bool = true @State var controller:LyricController = LyricController() @State var onSeekAction: (position: Int64) -> Bool = {_=> false} @State var seekUIColor: Color = Color(0x000000)

private var mLastBlankNum: Int64 = 0
@State
var seekLineColor: Color = Color(0x000000)

private var loadTimeout: ?Timer = None

private var seekUiHideTimeout: ?Timer = None

private var autoHideSeekUIDuration = 2000

public func getTextAlign(alignMode: AlignMode): TextAlign{
    if(alignMode.toString() == AlignMode.Center.toString()){
        Hilog.info(0x000001,"----getTextAlign--Center","--getTextAlign--Center")
        return TextAlign.Center
    } else {
        Hilog.info(0x000001,"-----getTextAlign Start","---getTextAlign Start")
        return TextAlign.Start
    }
}

public func getHighlightScaleXY(index: Int64): Float32{
    Hilog.info(0x000001,"-----getTextAlign Start","--getHighlightScaleXY-index:${index}")
    if(index == this.currentIndex){
        return this.controller.getHighlightScale()
    } else {
        return 1.0
    }
}
public func getCenterX(alignMode: AlignMode): Length{
    if(alignMode.toString() == 'Center'){
        return 50. percent
    } else {
        return 0. percent
    }
}

public func aboutToAppear() {
    this.controller.onDataChangedListener = this.onDataChangedListener
    this.controller.onPositionChangedListener = this.onPositionChangedListener
    this.controller.onInvalidated = this.onInvalidatedListener
    this.getAttrFromController()
}

public func aboutToDisappear() {
    if (!this.loadTimeout.isNone()) {
        this.loadTimeout.getOrThrow().cancel();
    }
    if (!this.seekUiHideTimeout.isNone()) {
        this.seekUiHideTimeout.getOrThrow().cancel();
    }
}

public func onPositionChangedListener(mediaPosition: Int64): Unit{
    this.onPositionChanged(mediaPosition)
}

public func onInvalidatedListener(reLayout: Bool): Unit{
    this.getAttrFromController()
    if (reLayout && this.currentIndex > 0) {
        this.animateToIndex(this.currentIndex)
    }
}

public func getAttrFromController() {
    this.currentLyric = this.controller.getLyric()
    this.textSize = this.controller.getTextSize()
    this.lineSpace = this.controller.getLineSpace()
    this.textColor = this.controller.getTextColor()
    this.textHighlightColor = this.controller.getHighlightColor()
    this.textHighlightSize = Float64(this.controller.getHighlightScale()) * this.textSize
    this.isHighlightBold = this.controller.isHighlightBold()
    this.animDuration = this.controller.getAnimationDuration()
    this.cacheSize = this.controller.getCacheSize()
    this.emptyHint = this.controller.getEmptyHint()
    this.alignMode = this.controller.getAlignMode()
}

public func onPositionChanged(mediaPosition: Int64):Unit {
    if (this.isLyricEmpty) {
        return
    }
    if (this.listAdapter.isEmpty()) {
        return
    }
    let index = this.getIndex(mediaPosition)
    if (index != this.currentIndex) {
        this.animateToIndex(index)
    }
}

public func getIndex(position: Int64): Int64 {
    let size = this.listAdapter.totalCount()
    let first: Int64 = this.listAdapter.getData(0).getBeginTime()
    if (position < first) {
        return 0
    }
    let last:Int64 = this.listAdapter.getData(size - 1).getBeginTime()
    if (position > last) {
        return size - 1
    }
    for (i in 0. size-1) {
        let line = this.listAdapter.getData(i)
        if (position >= line.getBeginTime() && position < line.getNextTime()) {
            return i
        }
    }
    return this.currentIndex
}

private func onDataChangedListener(lyric: ?Lyric):Unit{
    if(!this.loadTimeout.isNone()){
        this.loadTimeout.getOrThrow().cancel()
    }
    this.isLoadingData = true
    this.loadData(lyric)
    this.loadTimeout = Timer.repeat(Duration.Zero, 300 * Duration.millisecond, { =>
        this.isLoadingData = false
    })
}

private func getLyricsContent(lyricLine: ?LyricLine):String{
    var lyricsContent:String
    if(!lyricLine.getOrThrow().getSecondText().isEmpty()) {
        lyricsContent = lyricLine.getOrThrow().getText() + "\n" + lyricLine.getOrThrow().getSecondText()
    } else {
        lyricsContent = lyricLine.getOrThrow().getText()
    }
    return lyricsContent
}

@Builder
public func emptyView(){
    Text(this.emptyHint).fontSize(this.textSize-2.0).fontColor(this.textColor)
}

@State
var funcString: String = "";
@Builder
public func lyricListView(){
    List(space: Int64(this.lineSpace) - 16, scroller: this.scroller) {
            LazyForEach(this.listAdapter, itemGeneratorFunc: { item:LyricLine, index:Int64 =>
              ListItem() {
                     Stack() {
                        Row() {
                            if (this.seekUIStyle == 'listItem' && index == this.seekIndex && item.getText() != "" 
                            && this.enableSeek && this.isUserTouching) {
                                Text(this.scrollDurationText).fontSize(this.textSize)
                                .fontColor(this.seekUIColor)
                            }

                            Text(getLyricsContent(item)).width(100. percent).fontSize(this.textSize-2.0)
                            .textAlign(this.getTextAlign(this.alignMode))
                            .scale(x: getHighlightScaleXY(index), y:getHighlightScaleXY(index),centerX: getCenterX(this.alignMode))
                            .fontColor(if(index == this.currentIndex) { this.textHighlightColor } else { this.textColor})
                            .fontWeight(if(index == this.currentIndex && this.isHighlightBold) { FontWeight.Bold } else { FontWeight.Normal})
                            .animationStart(AnimateParam(
                                duration: this.animDuration,
                                curve: Curve.FastOutSlowIn,
                                iterations: 1,
                                playMode: PlayMode.Normal
                            ))
                            .animationEnd()
                        }.width(100. percent).align(Alignment.Center)
                   }.align(Alignment.Center)
                }

// .height(60) .padding(8) .borderRadius(4) .backgroundColor(if(this.seekUIStyle == ‘listItem’ && index == this.seekIndex && item.getText() != “” && this.enableSeek && this.isUserTouching) {Color(0x80a9a9a9)} else {Color.TRANSPARENT}) .onClick({ event => if (this.seekUIStyle == ‘listItem’ && index == this.seekIndex && item.getText() != “”) { if (this.seekIndex != -1 && this.seekIndex != this.currentIndex) { this.handleSeekAction(); } } }) }, keyGeneratorFunc: {item:LyricLine, index:Int64 => Hilog.info(0x000001,"----keyGeneratorFunc–LyricLine","-----keyGeneratorFunc–LyricLine11-${item.getText() + ‘’ + index.toString() + '’ + item.getBeginTime().toString() + ‘’ + item.getNextTime().toString()}") let time = SystemDateTime.getCurrentTime() if(funcString == time.toString()){ funcString = (time + 1).toString() } else { funcString = time.toString() } Hilog.info(0x000001,"----keyGeneratorFunc–LyricLine","-----keyGeneratorFunc–LyricLine33-${item.getText() + '’ + index.toString() + ‘’ + item.getBeginTime().toString() + '’ + item.getNextTime().toString() + ‘’ + funcString}") item.getText() + '’ + index.toString() + ‘’ + item.getBeginTime().toString() + '’ + item.getNextTime().toString() + ‘_’ + funcString; }); } .width(100. percent) .height(100. percent) .scrollBar(BarState.Off) // .listDirection(Axis.Horizontal) // .alignListItem(ListItemAlign.Start) .cachedCount(this.cacheSize) .visibility(if(this.isLoadingData) {Visibility.Hidden} else {Visibility.Visible}) .onScrollIndex({firstIndex: Int32, lastIndex: Int32 => if(this.isUserTouching){ var middle = (lastIndex + firstIndex)/2; Hilog.info(0x000001,"-----onScrollIndex middle","-----onScrollIndex middle–${middle}") this.seekIndex = Int64(middle) Hilog.info(0x000001,"-----onScrollIndex seekIndex","-----onScrollIndex seekIndex–${this.seekIndex}") let targetPosition = this.listAdapter.getData(Int64(middle)).getBeginTime() Hilog.info(0x000001,"-----onScrollIndex targetPosition","-----onScrollIndex targetPosition–${targetPosition}") this.scrollDurationText = duration2text(targetPosition) Hilog.info(0x000001,"-----onScrollIndex scrollDurationText","-----onScrollIndex scrollDurationText–${this.scrollDurationText}") } }) .onTouch({event => match (event.eventType) { case TouchType.Down => if (!this.seekUiHideTimeout.isNone()) { Hilog.info(0x000001,"-----TouchType.Down-Down","-----TouchType.Down Down") this.seekUiHideTimeout.getOrThrow().cancel(); } Hilog.info(0x000001,"-----TouchType.Down","-----TouchType.Down") case TouchType.Move => this.isUserTouching = true Hilog.info(0x000001,"-----TouchType.Move","-----TouchType.Move") case TouchType.Up | TouchType.Cancel => this.seekUiHideTimeout = Timer.repeat(2000 * Duration.millisecond, 2000 * Duration.millisecond, { => Hilog.info(0x000001,"-----TouchType.Cancel Up Cancel","-----TouchType.Cancel Up Cancel") this.seekIndex = -1 this.isUserTouching = false Hilog.info(0x000001,"-----TouchType.Cancel Up Cancel","-----TouchType.Cancel Up–${this.currentIndex}") this.animateToIndex(this.currentIndex) }) case _ => Hilog.info(0x000001,"-----TouchType.Unknown","-----TouchType.Unknown") } }) }

@Builder
public func seekLine(){
    Row() {
        Stack()
            .height(1)
            .layoutWeight(1)
            .backgroundColor(this.seekLineColor)
            .margin(left: 8, right: 8 )
    }
    .visibility(if(this.enableSeek && this.isUserTouching){Visibility.Visible} else {Visibility.Hidden})
    .width(100. percent)
    .height(100. percent)

// .hitTestBehavior(HitTestMode.Transparent) .transition(TransitionEffect.OPACITY.animation(AnimateParam(duration: this.animDuration))) }

func build() {
    Column() {
        Stack() {
            if (this.isLyricEmpty) {
                this.emptyView()
            } else {
               this.lyricListView()
            }
            this.seekLine()
        }.width(100. percent).height(100. percent)
         .onAreaChange({old, new =>
                this.h = px2vp(new.height.px).getOrThrow().value
                Hilog.info(0x000001,"----this.h---","----this.h----${this.h}")
                this.w = px2vp(new.width.px).getOrThrow().value
                Hilog.info(0x000001,"----this.w---","----this.w----${this.w}")
                let lineH = this.textSize + this.lineSpace
                Hilog.info(0x000001,"----lineH---","----lineH----${lineH}")
                this.centerOffsetSize = Int64(floor((this.h - lineH) / 2.0 / lineH))
                Hilog.info(0x000001,"----this.centerOffsetSize---","----this.centerOffsetSize----${this.centerOffsetSize}")
                if (!this.currentLyric.isNone()) {
                    this.loadData(this.currentLyric.getOrThrow())
                }
        })
    }
}

public func handleSeekAction() {
    if (!this.seekUiHideTimeout.isNone()) {
        this.seekUiHideTimeout.getOrThrow().cancel();
    }
    let targetPosition:Int64 = this.listAdapter.getData(this.seekIndex).getBeginTime();
    let isPlayerHandled: Bool = this.onSeekAction(targetPosition);
    if (!isPlayerHandled) {
        this.animateToIndex(this.currentIndex);
    }
    this.isUserTouching = false;
}

public func loadData(lyric: ?Lyric) {
    this.currentLyric = lyric
    if (this.w > 0.0 && this.h > 0.0) {
        if (!this.currentLyric.isNone()) {
            this.listAdapter.clear(autoFlush: false)
            let lyricLines = this.currentLyric.getOrThrow().getLyricList()
            let first = lyricLines[0].getBeginTime()
            for (i in 0. this.centerOffsetSize) {
                this.listAdapter.addData(LyricLine("","", 0, first), autoFlush: false)
            }
            for (line in lyricLines) {
                this.listAdapter.addData(line, autoFlush: false)
            }
            let last = lyricLines[lyricLines.size - 1].getNextTime()
            for (i in 0. this.centerOffsetSize) {
                this.listAdapter.addData(LyricLine("","", last + i, last + i), autoFlush: false)
            }
            this.listAdapter.notifyDataReload()
        } else {
            this.listAdapter.clear(autoFlush: true)
        }
    }

    this.isLyricEmpty = this.listAdapter.isEmpty()
    this.currentIndex = 0
    this.animateToIndex(0)
}

public func animateToIndex(index: Int64) {
    Hilog.info(0x000001,"----animateToIndex","----animateToIndex-currentIndex-${this.currentIndex}")
    Hilog.info(0x000001,"----animateToIndex","----animateToIndex-index-${index}")
    this.currentIndex = index
    if (this.isUserTouching) {
        return
    }
    this.scroller.scrollToIndex(Int32(index), smooth: true, align: ScrollAlign.CENTER)
}

public func duration2text(duration: Int64): String {
    let seconds = round(Float64(duration) / 1000.0)
    let minute = Int64(floor(seconds / 60.0))
    let second = Int64(seconds) - minute * 60
    let s1 = minute.toString()
    let s2 = if(second >= 10){second.toString()} else{"0" + second.toString()}
    return s1 + ":" + s2

} }


更多关于HarmonyOS鸿蒙Next中仓颉使用this.scroller.scrollToIndex(Int32(index), smooth: true, align: ScrollAlign.CENTER)时,自动滚动时内容不能固定中央,觉得函数没生效的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

在HarmonyOS鸿蒙Next中使用scrollToIndex时内容不能固定中央,可能是以下原因:

  1. 目标组件未正确设置layoutWeightalignRules
  2. ScrollAlign.CENTER需要确保容器有明确高度;
  3. 目标项尺寸未正确测量。

检查滚动容器是否为ScrollList组件,并确认index在有效范围内。若使用自定义组件,需实现measurelayout方法。

更多关于HarmonyOS鸿蒙Next中仓颉使用this.scroller.scrollToIndex(Int32(index), smooth: true, align: ScrollAlign.CENTER)时,自动滚动时内容不能固定中央,觉得函数没生效的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


从代码来看,您遇到的问题可能是由于以下几个原因导致的:

  1. List组件的高度或布局问题可能导致滚动居中失效。请确保List的父容器和List本身都设置了明确的高度(如100%)。

  2. 检查animateToIndex方法中的条件判断:

if (this.isUserTouching) {
    return
}

当用户触摸时直接返回,可能导致滚动不执行。

  1. 可以尝试在调用scrollToIndex后添加日志,确认方法确实被调用:
Hilog.info(0x000001,"----scroller","----scrolling to index ${index}")
this.scroller.scrollToIndex(Int32(index), smooth: true, align: ScrollAlign.CENTER)
  1. 确保List的item高度计算正确,特别是lineSpace和textSize的设置会影响居中效果。

  2. 考虑在onAreaChange回调中重新计算并设置滚动位置,因为尺寸变化可能导致原有位置失效。

建议先通过日志确认scrollToIndex是否被正确调用,再检查布局尺寸是否正确计算。

回到顶部