HarmonyOS鸿蒙Next中Slider组件设置changemode为end之后,外部改变value的值,但是滑块不动

HarmonyOS鸿蒙Next中Slider组件设置changemode为end之后,外部改变value的值,但是滑块不动 Slider组件,因为需要就设置了其onChange的changemode属性,判断了一下begin和end

如果end时的值比begin时的值大20,且end的值大于30之后才触发下面逻辑:
弹一个弹窗,点击确定的话,改变UI的值,

点击取消的话,值不变,Slider变成滑动之前的值。

但是现在就出现了一个问题,点击取消之后,Slider的value值变成了原先的值,但是Slider组件的UI视图却是滑动之后的样子。比如:当前的值为2,滑动到38时松手,弹窗提示。点击取消,然后Slider组件的value值赋为2,但是Slider组件的UI却是38的。
有没有人能说一下这是什么情况?


更多关于HarmonyOS鸿蒙Next中Slider组件设置changemode为end之后,外部改变value的值,但是滑块不动的实战教程也可以访问 https://www.itying.com/category-93-b0.html

9 回复

Index页面:

import { IndexViewModel, IndexViewState } from './IndexViewModel';

@Entry
@ComponentV2
struct Index {
  private viewModel: IndexViewModel = new IndexViewModel();

  @Local private viewState: IndexViewState = this.viewModel.viewState;

  aboutToAppear(): void {
    this.viewModel.pageInit();
  }

  build() {
    Column({ space: 15 }) {
      Text(`当前的值为:${this.viewState.value}`);

      Row() {
        Button("-")
          .onClick(() => {
            if (this.viewState.value > this.viewState.minLevel) {
              this.viewState.value--;
            }
          });
        Button("+")
          .onClick(() => {
            if (this.viewState.value < this.viewState.maxLevel) {
              this.viewState.value++;
            }
          });
      }
      .width("75%")
      .justifyContent(FlexAlign.SpaceBetween);

      PluseSlider({
        maxLevel: this.viewState.maxLevel,
        minLevel: this.viewState.minLevel,
        stepLen: 10,
        currentValue: this.viewState.value!!, // 双向绑定 UI 与 Model 的数据确保一致
        onPulseProgressLevelChange: (value: number, mode: SliderChangeMode) => {
          this.viewModel.onValueChange(value, mode);
        }
      });
    }
    .width("100%")
    .height("100%")
    .alignItems(HorizontalAlign.Center)
    .padding(20)
    .padding({ top: 30 });
  }
}


@ComponentV2
export struct PluseSlider {
  @Param maxLevel: number = 0;

  @Param minLevel: number = 0;

  @Param stepLen: number = 0;

  @Require
  @Param currentValue: number;

  @Event
  $currentValue: (value: number) => void;

  @Event
  onPulseProgressLevelChange: (value: number, mode: SliderChangeMode) => void;

  @Local private stepValue: number = 0;

  aboutToAppear() {
    this.stepValue = this.maxLevel / this.stepLen;
  }

  build() {
    Column() {
      Stack() {
        Slider({
          min: this.minLevel,
          max: this.stepValue,
          value: 0,
          style: SliderStyle.InSet
        })
          .showSteps(true)
          .width("100%");

        Slider({
          min: this.minLevel,
          max: this.maxLevel,
          value: this.currentValue,
          style: SliderStyle.InSet
        })
          .trackColor(Color.Transparent)
          .blockSize({
            width: 20,
            height: 20
          })
          .trackThickness(24)
          .width("100%")
          .sliderInteractionMode(SliderInteraction.SLIDE_ONLY)
          .onChange((value, mode) => {
            // 确保 UI 和 父组件的数据一致
            this.$currentValue(value);
            // 执行校验函数
            this.onPulseProgressLevelChange(value, mode);
          });
      };

      Button("查看当前值").onClick(() => {
        console.log("查看当前数据:currentValue:{}", this.currentValue);
      });
    }
    .width("100%");
  }
}

IndexViewModel页面:

@ObservedV2
export class IndexViewState {
  @Trace maxLevel: number = 60;
  @Trace minLevel: number = 0;
  @Trace value: number = 15;
}


export class IndexViewModel {
  public viewState: IndexViewState = new IndexViewState();

  public async pageInit() {

  }

  private begin: number = 0;
  private end: number = 0;

  public changeValue(value: number) {
    this.viewState.value = value;
  }

  public onValueChange(value: number, mode: SliderChangeMode) {
    if (mode === SliderChangeMode.Begin) {
      this.begin = value;
    }
    if (mode === SliderChangeMode.Moving) {
      this.changeValue(value);
    }
    if (mode === SliderChangeMode.End) {
      this.end = value;
      if (this.end === this.begin) {
        return;
      } else if (this.end - this.begin > 20 && this.end >= 30) {
        // 如果滑动距离超过20且终点大于30,则回退到起始位置
        // 此时由于 Moving 过程中 value 已经变了,这里设置 begin 会触发 UI 重新渲染
        this.changeValue(this.begin)
      } else {
        this.changeValue(this.end);
      }
    }
  }
}

更多关于HarmonyOS鸿蒙Next中Slider组件设置changemode为end之后,外部改变value的值,但是滑块不动的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


可以了,非常感谢,能麻烦您给我详细解释一下为什么嘛?

这里主要是到你在子组件直接修改的是父组件中的值,也就是IndexViewState这个类的值,而子组件的值是原先父组件的值。所以导致了父组件中类的值实际已经变化了,但子组件中的值还是之前的值。

详细原理请小伙伴阅读一下MVVM模式: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-mvvm-v2#%E6%A6%82%E8%BF%B0

子组件的值不是父组件传过去的吗?父组件变化了子组件应该也会变化的。而且子组件里的button点击获取的是子组件的值,也是跟父组件一样的值啊。

大体意思就是这样的:

Index页面:

import { IndexViewModel, IndexViewState } from './IndexViewModel';
import { UIContext } from "@kit.ArkUI";

@Entry
@ComponentV2
struct Index {
  private viewModel: IndexViewModel = new IndexViewModel();

  @Local private viewState: IndexViewState = this.viewModel.viewState;

  aboutToAppear(): void {
    this.viewModel.pageInit();
  }

  build() {
    Column({ space: 15 }) {
      Text(`当前的值为:${this.viewState.value}`);

      Row() {
        Button("-")
          .onClick(() => {
            if (this.viewState.value > this.viewState.minLevel) {
              this.viewState.value--;
            }
          });
        Button("+")
          .onClick(() => {
            if (this.viewState.value < this.viewState.maxLevel) {
              this.viewState.value++;
            }
          });
      }
      .width("75%")
      .justifyContent(FlexAlign.SpaceBetween);

      PluseSlider({
        maxLevel: this.viewState.maxLevel,
        minLevel: this.viewState.minLevel,
        stepLen: 10,
        currentValue: this.viewState.value,
        onPulseProgressLevelChange: (value: number, mode: SliderChangeMode) => {
          let uiContext: UIContext = this.getUIContext();
          this.viewModel.onValueChange(value, mode, uiContext);
        }
      });
    }
    .width("100%")
    .height("100%")
    .alignItems(HorizontalAlign.Center)
    .padding(20)
    .padding({ top: 30 });
  }
}


@ComponentV2
export struct PluseSlider {
  @Param maxLevel: number = 0;

  @Param minLevel: number = 0;

  @Param stepLen: number = 0;

  @Require
  @Param currentValue: number;

  @Event
  onPulseProgressLevelChange: (value: number, mode: SliderChangeMode) => void;

  @Local private stepValue: number = 0;

  aboutToAppear() {
    this.stepValue = this.maxLevel / this.stepLen;
  }

  build() {
    Column() {
      Stack() {
        Slider({
          min: this.minLevel,
          max: this.stepValue,
          value: 0,
          style: SliderStyle.InSet
        })
          .showSteps(true)
          .width("100%");

        Slider({
          min: this.minLevel,
          max: this.maxLevel,
          value: this.currentValue,
          style: SliderStyle.InSet
        })
          .trackColor(Color.Transparent)
          .blockSize({
            width: 20,
            height: 20
          })
          .trackThickness(24)
          .width("100%")
          .sliderInteractionMode(SliderInteraction.SLIDE_ONLY)
          .onChange((value, mode) => {
            this.onPulseProgressLevelChange(value, mode);
          });
      };

      Button("查看当前值").onClick(() => {
        console.log("查看当前数据:currentValue:{}", this.currentValue);
      });
    }
    .width("100%");
  }
}

IndexViewModel页面:

import { AdvancedDialogV2Button, ConfirmDialogV2, UIContext } from "@kit.ArkUI";

@ObservedV2
export class IndexViewState {
  @Trace maxLevel: number = 60;
  @Trace minLevel: number = 0;
  @Trace value: number = 15;
}


export class IndexViewModel {
  public viewState: IndexViewState = new IndexViewState();

  public async pageInit() {

  }

  private begin: number = 0;
  private end: number = 0;

  public changeValue(value: number) {
    this.viewState.value = value;
  }

  public onValueChange(value: number, mode: SliderChangeMode) {
    if (mode === SliderChangeMode.Begin) {
      this.begin = value;
    }
    if (mode === SliderChangeMode.End) {
      this.end = value;
      if (this.end === this.begin) {
        return;
      } else if (this.end - this.begin > 20 && this.end >= 30) {
        // 这里值变回去了,但是Slider的UI没变
        this.changeValue(this.begin)
      } else {
        this.changeValue(this.end);
      }
    }
  }
}

小伙伴你好,可以参考一下文档中的 $$双向绑定

Slider - 示例7(设置滑动条的双向绑定)

如果还不能解决问题的话,麻烦提供一个最小的 Demo 进行后续的参考回复。

如果可以的话,麻烦您看一下,

在HarmonyOS Next中,Slider组件的changeMode设置为’end’时,外部修改value值不会实时更新滑块位置。这是设计行为:'end’模式仅在用户交互结束时触发onChange事件并更新value。若需外部修改时滑块同步移动,应将changeMode设为’start’或默认值。

根据你的描述,这是一个典型的Slider组件数据与UI状态不同步的问题。

核心原因分析:

当你将 onChangechangemode 设置为 'end' 时,Slider 的行为是:在用户滑动结束(松手)时才触发 onChange 事件,并在此刻将内部的 value 更新为最终值。

你的逻辑流程是:

  1. 用户从 2 滑动到 38 后松手。
  2. onChange 事件触发(changemode: 'end'),此时 value 变为 38,你判断条件满足,弹出对话框。
  3. 用户点击“取消”,你将 Slider 绑定的数据变量(假设为 currentValue)重置为 2。
  4. 问题出现:Slider 组件内部在步骤2已经将自身的状态更新为 38。当你从外部将 currentValue 改为 2 时,Slider 组件可能因为内部状态优先级或未正确响应数据回退,导致其 UI(滑块位置)仍然停留在内部状态 38,而显示的值(通过 value 属性绑定的文本)是 2。

解决方案:

你需要强制让 Slider 组件根据最新的 value 属性值更新其内部状态和 UI。在 HarmonyOS ArkUI 中,最直接有效的方法是使用 @State 装饰器 来管理 Slider 的值,并确保数据变化能触发 UI 更新。

关键代码思路:

// 使用 @State 装饰器声明响应式数据
@State currentValue: number = 2;

// Slider 组件
Slider({
  value: this.currentValue, // 绑定响应式数据
  min: 0,
  max: 100,
  onChange: (value: number, mode: SliderChangeMode) => {
    // 仅在滑动结束时处理
    if (mode === SliderChangeMode.End) {
      let beginValue = this.currentValue; // 滑动开始的值
      let endValue = value; // 滑动结束的值

      // 你的判断逻辑
      if ((endValue - beginValue > 20) && endValue > 30) {
        // 弹出对话框
        AlertDialog.show({
          // ... 对话框配置
          onAccept: () => {
            // 用户点击确定,更新为结束值
            this.currentValue = endValue;
          },
          onCancel: () => {
            // 用户点击取消,关键步骤:将值重置回开始值
            // @State 装饰器会确保此变更触发Slider UI重新渲染
            this.currentValue = beginValue;
          }
        })
      } else {
        // 不满足条件时,正常更新值
        this.currentValue = endValue;
      }
    }
  }
})

重点说明:

  1. 使用 @State:确保 currentValue 是响应式的。当在 onCancel 回调中执行 this.currentValue = beginValue; 时,ArkUI 框架会检测到状态变化,并自动触发 Slider 组件使用新的 value (2) 重新渲染,从而将滑块位置正确更新到 2。
  2. 逻辑清晰:在 onChange 事件中,明确区分了 beginValue(滑动前的值)和 endValue(松手时的值)。在取消操作时,将状态回滚至 beginValue
  3. 避免直接操作组件实例:ArkUI 声明式 UI 的核心是数据驱动。你应该通过改变绑定数据(this.currentValue)来驱动 UI 变化,而不是尝试直接调用 Slider 组件的方法去“重置”其内部状态。

按照以上方式修改代码,即可解决滑块 UI 与数据值不同步的问题。

回到顶部