HarmonyOS鸿蒙Next中使用ArkTS开发多页面应用时,如何在页面销毁前保存当前滚动位置,以便下次进入时恢复?

HarmonyOS鸿蒙Next中使用ArkTS开发多页面应用时,如何在页面销毁前保存当前滚动位置,以便下次进入时恢复? 新闻列表页内容较长,用户滚动到中部后跳转详情页,返回时希望停留在原位置,而非重新加载到顶部。这种效果谁会?

8 回复

1. 定义全局状态保存滚动位置

// AppState.ets(可选,也可直接用 AppStorage)
let scrollOffsetMap: Record<string, number> = {};

export function saveScrollOffset(pageName: string, offset: number): void {
  scrollOffset[offset] = offset;
}

export function getScrollOffset(pageName: string): number {
  return scrollOffsetMap[pageName] ?? 0;
}

或者更推荐使用 AppStorage(支持跨页面共享):

// 在 main.ts 或入口处初始化
import { AppStorage } from '@kit.ArkTS';

// 可选:持久化(重启 App 也保留)
// PersistentStorage.PersistProp('newsListScrollOffset', 0);
AppStorage.SetOrCreate('newsListScrollOffset', 0);

2. 在新闻列表页监听页面生命周期并操作滚动

// NewsList.ets
import { router } from '@kit.ArkTS';
import { AppStorage, LocalStorage, Preferences } from '@kit.ArkTS';

@Entry
@Component
struct NewsList {
  @StorageLink('newsListScrollOffset') savedOffset: number = 0;
  scroller: Scroller = new Scroller();

  build() {
    Column() {
      List({ space: 10, scroller: this.scroller }) {
        ForEach(this.newsData, (item) => {
          ListItem() {
            // 新闻项内容
            Text(item.title)
              .onClick(() => {
                // 跳转详情页前,先保存当前位置
                this.saveCurrentOffset();
                router.pushUrl({ url: 'pages/NewsDetail', params: { id: item.id } });
              })
          }
        }, item => item.id)
      }
      .scrollPosition(this.savedOffset) // ⭐ 关键:恢复滚动位置
      .onScrollIndex((first, last) => {
        // 可选:实时监听(但不建议频繁保存)
      })
    }
  }

  // 保存当前滚动偏移
  saveCurrentOffset() {
    // 注意:Scroller.currentOffset() 是异步的!
    this.scroller.currentOffset().then((offset: number) => {
      this.savedOffset = offset; // 自动同步到 AppStorage
    });
  }

  // 页面即将退出时保存(兜底)
  aboutToDisappear() {
    this.saveCurrentOffset();
  }

  // 页面重新显示时(可选刷新)
  onPageShow() {
    // 如果需要,可以在这里触发恢复(但 .scrollPosition 已自动处理)
  }
}

更多关于HarmonyOS鸿蒙Next中使用ArkTS开发多页面应用时,如何在页面销毁前保存当前滚动位置,以便下次进入时恢复?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


问ai了吗?我的做法是滑动停止的时候保存位置,而不是离开时再想办法。

可以使用路由模式保留页面实例

在页面跳转时不要使用popclear等方法,否则NavDestination页面会被回收。可以采用单例模式MOVE_TO_TOP_SINGLETON跳转至指定的页面,该方式会使用栈内已存在的页面实例(即浏览位置不变)。
由于单例模式是从栈底到栈顶依次查找,当栈内存在多个同名页面实例时,会默认跳转最底层的同名页面实例,所以需确保栈内只存在一个同名实例,否则跳转的页面保留的滚动位置与上一次显示的页面会不一致(显示的是栈底同名实例保留的滚动位置)。

可参考:

import { PageOneS1Builder } from './PageOneS1';
import { PageTwoS1Builder } from './PageTwoS1';

@Entry
@Component
struct Solution1 {
  private pathStack: NavPathStack = new NavPathStack();

  aboutToAppear(): void {
    this.pathStack.pushPath({ name: 'PageOneS1' }); // 推送第一个子页作为首页
  }

  @Builder
  pageMap(name: string) {
    if (name === 'PageOneS1') {
      PageOneS1Builder();
    } else if (name === 'PageTwoS1') {
      PageTwoS1Builder();
    }
  }

  build() {
    Navigation(this.pathStack) {
    }
    .navDestination(this.pageMap)
    .width('100%')
    .height('100%')
    .hideNavBar(true);
  }
}
@Builder
export function PageOneS1Builder() {
  PageOneS1();
}

@Component
struct PageOneS1 {
  private pathStack: NavPathStack = new NavPathStack();
  private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];

  build() {
    NavDestination() {
      Column({ space: 20 }) {
        Row() {
          Button('跳转页面')
            .margin({ top: 20 })
            .onClick(() => {
              this.pathStack.pushPathByName('PageTwoS1', null, false);
            });
        };

        List({ space: 16 }) {
          ForEach(this.arr, (item: number) => {
            ListItem() {
              Text(item.toString())
                .width('100%')
                .height(100)
                .fontSize(16)
                .textAlign(TextAlign.Center)
                .borderRadius(16)
                .backgroundColor('#F1F3F5');
            }.width('100%');
          }, (item: string) => item);
        }.padding({ left: 12, right: 12 })
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
        .alignListItem(ListItemAlign.Center)
        .height('90%');
      };
    }.width('100%').height('100%')
    .onReady((context: NavDestinationContext) => {
      this.pathStack = context.pathStack;
    });
  }
}
@Builder
export function PageTwoS1Builder() {
  PageTwoS1();
}

@Component
struct PageTwoS1 {
  pathStack: NavPathStack = new NavPathStack();

  build() {
    NavDestination() {
      Column({ space: 20 }) {
        Button('单例跳转')
          .width('30%')
          .margin('20vp')
          .onClick(() => {
            // 或者使用单例模式跳转回其它页面
            this.pathStack.pushPath({ name: 'PageOneS1' }, { launchMode: LaunchMode.MOVE_TO_TOP_SINGLETON });
          });
      }.width('100%').height('100%');
    }
    .onReady((context: NavDestinationContext) => {
      this.pathStack = context.pathStack;
    });
  }
}

【背景知识】

  • Navigation:Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏、内容区和工具栏,其中内容区默认首页显示导航内容(NavDestination的子组件)或非首页显示(NavDestination的子组件),首页和非首页通过路由进行切换。
  • 单例模式MOVE_TO_TOP_SINGLETON:从栈底向栈顶查找,如果指定的名称已经存在,则将对应的NavDestination页面移到栈顶(replace操作会将最后的栈顶替换成指定的NavDestination),否则行为和STANDARD一致

正常的列表页面和详情页面通过路由跳转不会有这个问题,出现这个问题,应该是列表页的生命周期有问题,在列表页重新显示时,触发了重新渲染,请检查列表页面的生命周期使用是否正确

找AI看看

你好,在 HarmonyOS ArkTS 开发中,实现多页面应用的滚动位置保存与恢复是一个非常常见且实用的需求。这能极大地提升用户体验,避免用户返回列表页时需要重新寻找之前的位置。

实现关键代码:

// 使用@StorageLink将组件内状态与AppStorage中的'myListScrollOffset'键双向绑定
// 初始值为0,表示默认滚动到顶部
@StorageLink('myListScrollOffset') scrollOffset: number = 0;

// 使用Scroll组件包裹列表
Scroll() {
//...
}

// 将Scroll组件的scrollOffset属性绑定到我们的状态变量
// 1. 当页面首次加载时,Scroll会读取scrollOffset的值(从AppStorage中恢复)并滚动到相应位置。
// 2. 当用户手动滚动时,Scroll组件会实时更新scrollOffset变量的值。
.scrollOffset(this.scrollOffset)

// 监听滚动事件,当滚动发生时,更新我们的状态变量。
// onScroll回调会在滚动时被频繁触发,event.offset.y就是当前的垂直滚动距离。
.onScroll((event: ScrollOffset) => {
this.scrollOffset = event.offset.y;
})

如果解决了您的问题,请采纳,谢谢!

在ArkTS中,可通过@State@StorageLink装饰器保存滚动位置。使用Scroll组件的onScroll事件监听滚动偏移量,在aboutToDisappear生命周期中存储数据。下次进入页面时,在aboutToAppear中读取并设置scrollTo方法恢复位置。

在HarmonyOS Next中,使用ArkTS开发多页面应用时,可以通过以下方式在页面销毁前保存滚动位置,并在下次进入时恢复:

核心思路

使用LocalStorageAppStorage存储页面的滚动位置,在页面离开时保存状态,进入时读取并恢复。

具体实现步骤

  1. 定义存储键名和滚动状态 在页面组件中定义用于存储滚动位置的键名:

    const SCROLL_POS_KEY = 'newsListScrollPos';
    
  2. 保存滚动位置 在新闻列表页面的aboutToDisappear生命周期回调中保存当前滚动位置:

    import { Scroll, LocalStorage } from '[@kit](/user/kit).ArkUI';
    
    [@Component](/user/Component)
    struct NewsListPage {
      private scrollController: ScrollController = new ScrollController();
      private localStorage: LocalStorage = new LocalStorage();
      
      aboutToDisappear() {
        // 获取当前滚动位置
        const currentOffset = this.scrollController.currentOffset();
        // 保存到LocalStorage
        this.localStorage.setOrCreate(SCROLL_POS_KEY, currentOffset.y);
      }
      
      build() {
        Scroll(this.scrollController) {
          // 新闻列表内容
        }
      }
    }
    
  3. 恢复滚动位置 在新闻列表页面的aboutToAppear生命周期回调中恢复滚动位置:

    aboutToAppear() {
      // 从LocalStorage读取保存的位置
      const savedPosition = this.localStorage.get<number>(SCROLL_POS_KEY);
      if (savedPosition) {
        // 延迟执行以确保Scroll组件已渲染完成
        setTimeout(() => {
          this.scrollController.scrollTo({ yOffset: savedPosition });
        }, 50);
      }
    }
    
  4. 处理详情页返回 从详情页返回时,新闻列表页面会重新触发aboutToAppear,从而自动恢复滚动位置。

注意事项

  • 使用setTimeout确保Scroll组件完成布局后再恢复位置
  • 考虑在页面完全退出时清理存储,避免数据残留
  • 对于复杂场景,可将滚动位置存储在ViewModel中实现更精细的控制

这种方法能有效实现新闻列表页的位置保持,提升用户体验。

回到顶部