HarmonyOS 鸿蒙Next中BindSheet在获取焦点后的650ms后自动退出

HarmonyOS 鸿蒙Next中BindSheet在获取焦点后的650ms后自动退出

// 添加待办事项的半模态
[@Builder](/user/Builder)
AddTodoSheet() {
  Column() {
    /*Text('添加待办事项')
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .margin({ top: 20, bottom: 20 })

    // 批量添加开关
    Row() {
      Text('批量添加模式')
        .fontSize(16)
        .fontColor('#333333')
        .layoutWeight(1)

      Toggle({ type: ToggleType.Switch, isOn: $$this.batchAddMode })
        .selectedColor('#007AFF')
        .onChange((isOn: boolean) => {
          this.batchAddMode = isOn;
        })
    }
    .width('90%')
    .margin({ bottom: 15 })
    .padding({ left: 5, right: 5 })

    // 置顶/常规选择按钮
    Row() {
      Button('添加置顶事项')
        .layoutWeight(1)
        .backgroundColor(this.isAddingPinned ? '#007AFF' : '#F0F0F0')
        .fontColor(this.isAddingPinned ? Color.White : Color.Black)
        .onClick(() => {
          this.isAddingPinned = true;
        })

      Button('添加常规事项')
        .layoutWeight(1)
        .margin({ left: 10 })
        .backgroundColor(!this.isAddingPinned ? '#007AFF' : '#F0F0F0')
        .fontColor(!this.isAddingPinned ? Color.White : Color.Black)
        .onClick(() => {
          this.isAddingPinned = false;
        })
    }
    .width('90%')
    .margin({ bottom: 20 })

    // 事项内容输入
    TextInput({
      placeholder: "输入待办事项内容...",
      text: this.newTodoText
    })
      .width('90%')
      .height(50)
      .margin({ bottom: 15 })
      .onChange((value: string) => {
        this.newTodoText = value;
      })

    // 备注输入
    TextArea({
      placeholder: "添加备注(可选)...",
      text: this.newTodoNote
    })
      .width('90%')
      .height(80)
      .margin({ bottom: 15 })
      .onChange((value: string) => {
        this.newTodoNote = value;
      })

    // 颜色选择
    Text('选择颜色')
      .width('90%')
      .fontSize(16)
      .margin({ bottom: 10 })
      .textAlign(TextAlign.Start)

    this.ColorPicker()


    // 操作按钮
    Row() {
      Button('取消')
        .layoutWeight(1)
        .backgroundColor('#F0F0F0')
        .fontColor(Color.Black)
        .onClick(() => {
          this.showAddSheet = false;
          this.newTodoText = '';
          this.newTodoNote = '';
          this.selectedColor = TodoColor.RED;
          this.batchAddMode = false;
        })

      Button('添加')
        .layoutWeight(1)
        .margin({ left: 10 })
        .backgroundColor('#007AFF')
        .onClick(() => {
          this.addTodo();
        })
    }
    .width('90%')
    .margin({ bottom: 20 })*/
  }
  .height('70%')
  .backgroundColor(Color.White)
  .borderRadius({ topLeft: 20, topRight: 20 })
}

更多关于HarmonyOS 鸿蒙Next中BindSheet在获取焦点后的650ms后自动退出的实战教程也可以访问 https://www.itying.com/category-93-b0.html

10 回复

开发者你好,这边测试半模态未能复现,为了更快解决您的问题,麻烦请补充以下信息:

针对该问题最好上传一下问题的录屏或者gif图片;

复现代码(如最小复现demo);

版本信息(如:开发工具、手机系统版本信息);

以下是测试代码:

import { promptAction, PromptAction } from '@kit.ArkUI';

@Entry
@Component
struct Page {
  @State isShow: boolean = false;
  @State enableHoverMode: boolean = true;

  @Builder
  myBuilder() {
    Column() {

    }.width(200)
    .height('70%')
    .backgroundColor(Color.White)
    .borderRadius({ topLeft: 20, topRight: 20 })
  }

  build() {
    Column() {
      Button("拉起半模态")
        .onClick(() => {
          this.isShow = true;
        })
        .fontSize(20)
        .margin(10)
        .bindSheet(this.isShow, this.myBuilder(), {
          height: 300,
          backgroundColor: Color.Green,
          preferType: SheetType.CENTER,
          detents:[SheetSize.LARGE],
          onDisappear:()=>{
            this.isShow = false;
            promptAction.showToast({
              message:"关闭弹窗"
            })
          }
        })
    }
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
  }
}

更多关于HarmonyOS 鸿蒙Next中BindSheet在获取焦点后的650ms后自动退出的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


亲,在吗,再看看完整的代码?可能有助于复现,

试试在Column中添加一个占位按钮维持焦点:

Column() {

  Button('PLACEHOLDER').opacity(0)

}

显式设置防误触参数:

.bindSheet($$this.showAddSheet, this.AddTodoSheet(), {

  detents: [SheetSize.LARGE],

  showClose: false,

  enableOutsideInteractive: false

})

移除onDisappear中的状态修改逻辑,仅在用户点击取消/确认时修改showAddSheet值

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

对不起,未能解决问题 后面代码已完整列出,大佬可否赏脸一看,查找一下问题?

// 原代码
.bindSheet($$this.showAddSheet, ...)

// 修改后(使用!!双向绑定)
.bindSheet(!!this.showAddSheet, this.AddTodoSheet(), {
  detents: [SheetSize.MEDIUM],
  preferType: SheetType.CENTER,
  duration: 0, // 禁用动画
  // 其他配置保持不变...
})

感谢您的回复 但无效,窗口依旧闪退 另注:bindsheet无duration属性,

录屏文件在 https://p.cv0.cn/s/mneIW

仍然没有解决,因为程序没有崩溃,日志未提及错误,而且打开一个半模态不涉及调用任何函数

所以我倾向于这是异步数据加载与Sheet渲染的时序冲突导致的
以下是全部代码,问题解决后会删除

感谢您的回复!

import { promptAction } from '[@kit](/user/kit).ArkUI';
import dataPreferences from '[@ohos](/user/ohos).data.preferences';

// 颜色枚举
enum TodoColor {
  RED = 'red',
  ORANGE = 'orange',
  YELLOW = 'yellow',
  GREEN = 'green',
  CYAN = 'cyan',
  BLUE = 'blue',
  PURPLE = 'purple'
}

// 颜色配置
interface ColorConfig {
  color: string;
  name: string;
}

const COLOR_CONFIG = new Map<TodoColor, ColorConfig>([
  [TodoColor.RED, { color: '#FF4444', name: '红色' }],
  [TodoColor.ORANGE, { color: '#FF8800', name: '橙色' }],
  [TodoColor.YELLOW, { color: '#FFBB33', name: '黄色' }],
  [TodoColor.GREEN, { color: '#00C851', name: '绿色' }],
  [TodoColor.CYAN, { color: '#00BCD4', name: '青色' }],
  [TodoColor.BLUE, { color: '#2196F3', name: '蓝色' }],
  [TodoColor.PURPLE, { color: '#9C27B0', name: '紫色' }]
]);

// 增强的待办事项接口
interface EnhancedTodoItem {
  id: string;
  text: string;
  completed: boolean;
  createdAt: string;
  isPinned: boolean;          // 是否置顶
  color: TodoColor;           // 颜色
  note: string;               // 备注
}

[@Component](/user/Component)
export struct TodoComponent {
  [@State](/user/State) todos: Array<EnhancedTodoItem> = [];
  [@State](/user/State) newTodoText: string = '';
  [@State](/user/State) newTodoNote: string = '';
  [@State](/user/State) selectedColor: TodoColor = TodoColor.RED;
  [@State](/user/State) showAddSheet: boolean = false;
  [@State](/user/State) showDetailSheet: boolean = false;
  [@State](/user/State) selectedTodo: EnhancedTodoItem | null = null;
  [@State](/user/State) isAddingPinned: boolean = false;
  [@State](/user/State) batchAddMode: boolean = false;

  private todoStore: dataPreferences.Preferences | null = null;

  async aboutToAppear() {
    // 初始化待办事项存储
    this.todoStore = await dataPreferences.getPreferences(getContext(this), 'enhanced_todos');
    await this.loadTodos();
  }

  // 加载待办事项
  private async loadTodos() {
    if (!this.todoStore) return;
    try {
      const keysObj = await this.todoStore.getAll();
      const todoItems: EnhancedTodoItem[] = [];

      const entries: [string, string][] = Object.entries(keysObj);

      for (const entry of entries) {
        const key: string = entry[0];
        const value: string = entry[1];

        if (key.startsWith('enhanced_todo_')) {
          try {
            const todoData: Partial<EnhancedTodoItem> = JSON.parse(value);
            const todoItem: EnhancedTodoItem = {
              id: todoData.id || key,
              text: todoData.text || '',
              completed: Boolean(todoData.completed),
              createdAt: todoData.createdAt || new Date().toISOString(),
              isPinned: Boolean(todoData.isPinned),
              color: todoData.color || TodoColor.RED,
              note: todoData.note || ''
            };
            todoItems.push(todoItem);
          } catch (parseErr) {
            console.error('解析待办事项失败: ' + JSON.stringify(parseErr));
          }
        }
      }

      // 排序:置顶 > 未完成 > 已完成,然后按创建时间倒序
      todoItems.sort((a: EnhancedTodoItem, b: EnhancedTodoItem) => {
        // 首先按置顶状态排序(未完成的置顶项优先)
        if (!a.completed && !b.completed) {
          if (a.isPinned !== b.isPinned) {
            return a.isPinned ? -1 : 1;
          }
        }
        // 然后按完成状态排序
        if (a.completed !== b.completed) {
          return a.completed ? 1 : -1;
        }
        // 最后按创建时间排序
        return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
      });

      this.todos = todoItems;
    } catch (err) {
      console.error('加载待办事项失败: ' + JSON.stringify(err));
    }
  }

  // 添加待办事项
  private async addTodo() {
    if (!this.todoStore || !this.newTodoText.trim()) return;

    try {
      const todoId = 'enhanced_todo_' + new Date().getTime();
      const newTodo: EnhancedTodoItem = {
        id: todoId,
        text: this.newTodoText.trim(),
        completed: false,
        createdAt: new Date().toISOString(),
        isPinned: this.isAddingPinned,
        color: this.selectedColor,
        note: this.newTodoNote.trim()
      };

      await this.todoStore.put(todoId, JSON.stringify(newTodo));
      await this.todoStore.flush();

      // 根据批量添加模式决定是否关闭窗口
      if (this.batchAddMode) {
        // 批量模式:只重置表单,不关闭窗口
        this.newTodoText = '';
        this.newTodoNote = '';
        this.selectedColor = TodoColor.RED;
      } else {
        // 普通模式:重置表单并关闭窗口
        this.newTodoText = '';
        this.newTodoNote = '';
        this.selectedColor = TodoColor.RED;
        this.showAddSheet = false;
      }

      await this.loadTodos();

      promptAction.showToast({
        message: this.isAddingPinned ? '已添加置顶待办事项' : '已添加待办事项',
        duration: 2000,
        showMode: promptAction.ToastShowMode.DEFAULT,
        bottom: 85
      });
    } catch (err) {
      console.error('添加待办事项失败: ' + JSON.stringify(err));
    }
  }

  // 切换待办事项状态
  private async toggleTodoStatus(todoId: string, completed: boolean) {
    if (!this.todoStore) return;

    try {
      const todoIndex = this.todos.findIndex(item => item.id === todoId);
      if (todoIndex === -1) return;

      const originalTodo = this.todos[todoIndex];

      // 手动复制所有属性
      const updatedTodo: EnhancedTodoItem = {
        id: originalTodo.id,
        text: originalTodo.text,
        completed: !completed,
        createdAt: originalTodo.createdAt,
        isPinned: originalTodo.isPinned,
        color: originalTodo.color,
        note: originalTodo.note
      };

      await this.todoStore.put(todoId, JSON.stringify(updatedTodo));
      await this.todoStore.flush();

      await this.loadTodos();

      promptAction.showToast({
        message: updatedTodo.completed ? '已完成待办事项' : '已恢复待办事项',
        duration: 2000,
        showMode: promptAction.ToastShowMode.DEFAULT,
        bottom: 85
      });
    } catch (err) {
      console.error('更新待办事项状态失败: ' + JSON.stringify(err));
    }
  }

  // 切换置顶状态
  private async togglePinStatus(todoId: string) {
    if (!this.todoStore) return;

    try {
      const todoIndex = this.todos.findIndex(item => item.id === todoId);
      if (todoIndex === -1) return;

      const originalTodo = this.todos[todoIndex];

      // 手动复制所有属性
      const updatedTodo: EnhancedTodoItem = {
        id: originalTodo.id,
        text: originalTodo.text,
        completed: originalTodo.completed,
        createdAt: originalTodo.createdAt,
        isPinned: !originalTodo.isPinned, // 切换置顶状态
        color: originalTodo.color,
        note: originalTodo.note
      };

      await this.todoStore.put(todoId, JSON.stringify(updatedTodo));
      await this.todoStore.flush();

      await this.loadTodos();

      promptAction.showToast({
        message: updatedTodo.isPinned ? '已设为置顶' : '已取消置顶',
        duration: 2000,
        showMode: promptAction.ToastShowMode.DEFAULT,
        bottom: 85
      });
    } catch (err) {
      console.error('更新置顶状态失败: ' + JSON.stringify(err));
    }
  }
  // 删除待办事项
  private async deleteTodo(todoId: string) {
    if (!this.todoStore) return;

    try {
      await this.todoStore.delete(todoId);
      await this.todoStore.flush();

      await this.loadTodos();

      promptAction.showToast({
        message: '已删除待办事项',
        duration: 2000,
        showMode: promptAction.ToastShowMode.DEFAULT,
        bottom: 85
      });
    } catch (err) {
      console.error('删除待办事项失败: ' + JSON.stringify(err));
    }
  }

  // 显示详情
  private showTodoDetail(todo: EnhancedTodoItem) {
    this.selectedTodo = todo;
    this.showDetailSheet = true;
  }

  // 颜色选择器组件
  [@Builder](/user/Builder)
  ColorPicker() {
    Row() {
      ForEach(Object.values(TodoColor), (color: TodoColor) => {
        Button() {
          Circle()
            .width(30)
            .height(30)
            .fill(COLOR_CONFIG.get(color)?.color ?? '#CCCCCC')
            .border({
              width: this.selectedColor === color ? 3 : 1,
              color: this.selectedColor === color ? '#007AFF' : '#E0E0E0'
            })
        }
        .backgroundColor(Color.Transparent)
        .onClick(() => {
          this.selectedColor = color;
        })
      }, (color: TodoColor) => color)
    }
    .width('90%')
    .margin({ bottom: 20 })
    .justifyContent(FlexAlign.SpaceAround)
  }

  // 添加待办事项的半模态
  [@Builder](/user/Builder)
  AddTodoSheet() {
    Column() {
      Text('添加待办事项')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 20 })

      // 批量添加开关
      Row() {
        Text('批量添加模式')
          .fontSize(16)
          .fontColor('#333333')
          .layoutWeight(1)

        Toggle({ type: ToggleType.Switch, isOn: this.batchAddMode })
          .selectedColor('#007AFF')
          .onChange((isOn: boolean) => {
            this.batchAddMode = isOn;
          })
      }
      .width('90%')
      .margin({ bottom: 15 })
      .padding({ left: 5, right: 5 })

      // 置顶/常规选择按钮
      Row() {
        Button('添加置顶事项')
          .layoutWeight(1)
          .backgroundColor(this.isAddingPinned ? '#007AFF' : '#F0F0F0')
          .fontColor(this.isAddingPinned ? Color.White : Color.Black)
          .onClick(() => {
            this.isAddingPinned = true;
          })

        Button('添加常规事项')
          .layoutWeight(1)
          .margin({ left: 10 })
          .backgroundColor(!this.isAddingPinned ? '#007AFF' : '#F0F0F0')
          .fontColor(!this.isAddingPinned ? Color.White : Color.Black)
          .onClick(() => {
            this.isAddingPinned = false;
          })
      }
      .width('90%')
      .margin({ bottom: 20 })

      // 事项内容输入
      TextInput({
        placeholder: "输入待办事项内容...",
        text: this.newTodoText
      })
        .width('90%')
        .height(50)
        .margin({ bottom: 15 })
        .onChange((value: string) => {
          this.newTodoText = value;
        })

      // 备注输入
      TextArea({
        placeholder: "添加备注(可选)...",
        text: this.newTodoNote
      })
        .width('90%')
        .height(80)
        .margin({ bottom: 15 })
        .onChange((value: string) => {
          this.newTodoNote = value;
        })

      // 颜色选择
      Text('选择颜色')
        .width('90%')
        .fontSize(16)
        .margin({ bottom: 10 })
        .textAlign(TextAlign.Start)

      this.ColorPicker()


      // 操作按钮
      Row() {
        Button('取消')
          .layoutWeight(1)
          .backgroundColor('#F0F0F0')
          .fontColor(Color.Black)
          .onClick(() => {
            this.showAddSheet = false;
            this.newTodoText = '';
            this.newTodoNote = '';
            this.selectedColor = TodoColor.RED;
            this.batchAddMode = false;
          })

        Button('添加')
          .layoutWeight(1)
          .margin({ left: 10 })
          .backgroundColor('#007AFF')
          .onClick(() => {
            this.addTodo();
          })
      }
      .width('90%')
      .margin({ bottom: 20 })
    }
    .height('70%')
    .backgroundColor(Color.White)
    .borderRadius({ topLeft: 20, topRight: 20 })
  }

  // 待办事项详情的半模态
  [@Builder](/user/Builder)
  TodoDetailSheet() {
    if (this.selectedTodo) {
      Column() {
        Text('待办事项详情')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 20 })

        // 颜色和置顶标识
        Row() {
          Circle()
            .width(20)
            .height(20)
            .fill(COLOR_CONFIG.get(this.selectedTodo.color)?.color ?? '#CCCCCC')

            .margin({ right: 10 })

          if (this.selectedTodo.isPinned) {
            Text('置顶')
              .fontSize(14)
              .fontColor('#FF6B35')
              .backgroundColor('#FFE5DB')
              .padding({ left: 8, right: 8, top: 4, bottom: 4 })
              .borderRadius(12)
              .margin({ right: 10 })
          }

          if (this.selectedTodo.completed) {
            Text('已完成')
              .fontSize(14)
              .fontColor('#28A745')
              .backgroundColor('#D4EDDA')
              .padding({ left: 8, right: 8, top: 4, bottom: 4 })
              .borderRadius(12)
          }
        }
        .width('90%')
        .margin({ bottom: 20 })

        // 事项内容
        Text('事项内容')
          .width('90%')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .margin({ bottom: 5 })
          .textAlign(TextAlign.Start)

        Text(this.selectedTodo.text)
          .width('90%')
          .fontSize(18)
          .padding(15)
          .backgroundColor('#F8F9FA')
          .borderRadius(10)
          .margin({ bottom: 20 })

        // 备注(如果有)
        if (this.selectedTodo.note) {
          Text('备注')
            .width('90%')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .margin({ bottom: 5 })
            .textAlign(TextAlign.Start)

          Text(this.selectedTodo.note)
            .width('90%')
            .fontSize(16)
            .fontColor('#6C757D')
            .padding(15)
            .backgroundColor('#F8F9FA')
            .borderRadius(10)
            .margin({ bottom: 20 })
        }

        // 创建时间
        Text('创建时间')
          .width('90%')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .margin({ bottom: 5 })
          .textAlign(TextAlign.Start)

        Text(new Date(this.selectedTodo.createdAt).toLocaleString())
          .width('90%')
          .fontSize(16)
          .fontColor('#6C757D')
          .margin({ bottom: 30 })

        // 操作按钮
        Row() {
          Button('关闭')
            .layoutWeight(1)
            .backgroundColor('#6C757D')
            .onClick(() => {
              this.showDetailSheet = false;
            })

          Button(this.selectedTodo.isPinned ? '取消置顶' : '设为置顶')
            .layoutWeight(1)
            .margin({ left: 10 })
            .backgroundColor('#FF6B35')
            .onClick(() => {
              if (this.selectedTodo) {
                this.togglePinStatus(this.selectedTodo.id);
                this.showDetailSheet = false;
              }
            })

          Button('删除')
            .layoutWeight(1)
            .margin({ left: 10 })
            .backgroundColor('#DC3545')
            .onClick(() => {
              if (this.selectedTodo) {
                this.deleteTodo(this.selectedTodo.id);
                this.showDetailSheet = false;
              }
            })
        }
        .width('90%')
        .margin({ bottom: 20 })
      }
      .height('80%')
      .backgroundColor(Color.White)
      .borderRadius({ topLeft: 20, topRight: 20 })
    }
  }

  build() {
    Column() {
      // 标题和添加按钮
      Row() {
        Text('待办事项')
          .fontSize(24)
          .fontWeight('700')
          .fontFamily('HarmonyHeiTi-Bold')
          .layoutWeight(1)

        Button() {
          Text() {
            SymbolSpan($r('sys.symbol.plus'))
          }
          .fontColor('#007AFF')
          .fontSize(20)
        }
        .backgroundColor(Color.Transparent)
        .onClick(() => {
          this.showAddSheet

在HarmonyOS Next中,BindSheet组件获取焦点后650ms自动退出,可能是由于系统默认行为或组件生命周期控制机制触发。该行为可能与焦点丢失、超时设置或特定交互逻辑相关。建议检查BindSheet的焦点事件处理逻辑及系统自动隐藏策略,确认是否存在默认超时配置。

从日志分析,问题出现在焦点管理机制上。日志显示Sheet在获取焦点后触发了bindsheet lifecycle change to onDisappear state,这通常是由于焦点丢失导致的自动关闭。

建议检查:

  1. 确保Sheet内容组件包含可聚焦元素(如TextInput)
  2. 验证showAddSheet状态管理逻辑,避免在onDisappear中重复设置为false
  3. 检查是否有其他组件意外获取焦点导致Sheet失焦

可以尝试在Sheet的Column中添加一个默认的TextInput组件,或者检查页面中是否存在自动触发焦点转移的逻辑。

回到顶部