Flutter 三方库 cached_network_image 的鸿蒙化适配与实战指南
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

前言

随着鸿蒙系统的快速发展,越来越多的开发者开始关注鸿蒙原生应用开发。本文将带你深入理解如何在鸿蒙平台上使用ArkTS语言实现MVVM架构模式,构建一个功能完善的待办事项应用。通过本实战,你将掌握鸿蒙原生开发的核心概念和最佳实践。

一、MVVM架构概述

1.1 什么是MVVM?

MVVM(Model-View-ViewModel)是一种软件架构模式,它将应用程序分为三个核心部分:

  • Model(模型层):负责数据结构和业务逻辑
  • View(视图层):负责UI展示和用户交互
  • ViewModel(视图模型层):连接Model和View的桥梁,处理数据转换和状态管理

1.2 MVVM的优势

┌─────────────────────────────────────────────────────────┐
│                        View Layer                        │
│  ┌─────────────────────────────────────────────────┐   │
│  │  TodoPage.ets - UI组件、用户交互、状态绑定       │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘
                          │
                          │ 订阅/通知
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    ViewModel Layer                       │
│  ┌─────────────────────────────────────────────────┐   │
│  │  TodoViewModel.ets - 状态管理、业务逻辑处理      │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘
                          │
                          │ 数据操作
                          ▼
┌─────────────────────────────────────────────────────────┐
│                     Model Layer                          │
│  ┌─────────────────────────────────────────────────┐   │
│  │  TodoModel.ets - 数据模型、数据持久化            │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

二、项目结构设计

entry/src/main/ets/
├── models/
│   └── TodoModel.ets          # 数据模型和仓库
├── viewmodels/
│   └── TodoViewModel.ets      # 视图模型
├── pages/
│   ├── Index.ets              # 启动页
│   └── TodoPage.ets           # 待办事项主页面
└── entryability/
    └── EntryAbility.ets       # 应用入口

三、Model层实现

3.1 数据模型定义

export interface TodoModel {
  id: string;              // 唯一标识
  title: string;           // 标题
  description: string;     // 详细描述
  isCompleted: boolean;    // 完成状态
  createdAt: number;       // 创建时间戳
  priority: TodoPriority;  // 优先级
}

export enum TodoPriority {
  low = 0,      // 低优先级
  medium = 1,   // 中优先级
  high = 2      // 高优先级
}

3.2 数据仓库实现

数据仓库负责数据的持久化存储,使用鸿蒙的Preferences API实现本地存储:

export class TodoRepository {
  private static readonly TODOS_KEY = 'mvvm_todos_data';
  private prefs: dataPreferences.Preferences | null = null;

  async init(context: common.UIAbilityContext): Promise<void> {
    this.prefs = await dataPreferences.getPreferences(context, 'mvvm_todo_store');
  }

  async getAllTodos(): Promise<TodoModel[]> {
    if (!this.prefs) return [];
    const todosJson = await this.prefs.get(TodoRepository.TODOS_KEY, '[]') as string;
    return JSON.parse(todosJson) as TodoModel[];
  }

  async saveTodos(todos: TodoModel[]): Promise<void> {
    if (!this.prefs) return;
    await this.prefs.put(TodoRepository.TODOS_KEY, JSON.stringify(todos));
    await this.prefs.flush();
  }

  async addTodo(todo: TodoModel): Promise<void> {
    const todos = await this.getAllTodos();
    todos.unshift(todo);
    await this.saveTodos(todos);
  }

  async updateTodo(todo: TodoModel): Promise<void> {
    const todos = await this.getAllTodos();
    const index = todos.findIndex(t => t.id === todo.id);
    if (index !== -1) {
      todos[index] = todo;
      await this.saveTodos(todos);
    }
  }

  async deleteTodo(id: string): Promise<void> {
    const todos = await this.getAllTodos();
    const filtered = todos.filter(t => t.id !== id);
    await this.saveTodos(filtered);
  }

  async clearCompleted(): Promise<void> {
    const todos = await this.getAllTodos();
    const filtered = todos.filter(t => !t.isCompleted);
    await this.saveTodos(filtered);
  }
}

四、ViewModel层实现

4.1 核心功能设计

ViewModel负责管理应用状态,处理业务逻辑,并通过观察者模式通知View更新:

export class TodoViewModel {
  private _todos: TodoModel[] = [];
  private _filter: TodoFilter = TodoFilter.all;
  private _sortType: SortType = SortType.createdAt;
  private _isLoading: boolean = true;
  private _error: string = '';
  private listeners: Set<() => void> = new Set();

  // 计算属性:获取过滤和排序后的待办列表
  get todos(): TodoModel[] {
    return this.getFilteredAndSortedTodos();
  }

  // 统计数据
  get totalCount(): number {
    return this._todos.length;
  }

  get activeCount(): number {
    return this._todos.filter((t: TodoModel) => !t.isCompleted).length;
  }

  get completedCount(): number {
    return this._todos.filter((t: TodoModel) => t.isCompleted).length;
  }
}

4.2 观察者模式实现

// 订阅状态变化
subscribe(listener: () => void): void {
  this.listeners.add(listener);
}

// 取消订阅
unsubscribe(listener: () => void): void {
  this.listeners.delete(listener);
}

// 通知所有监听者
private notifyListeners(): void {
  this.listeners.forEach((listener: () => void) => listener());
}

4.3 过滤和排序逻辑

private getFilteredAndSortedTodos(): TodoModel[] {
  let result: TodoModel[] = [];
  for (let i = 0; i < this._todos.length; i++) {
    result.push(this._todos[i]);
  }

  // 过滤逻辑
  if (this._filter === TodoFilter.active) {
    result = result.filter((t: TodoModel) => !t.isCompleted);
  } else if (this._filter === TodoFilter.completed) {
    result = result.filter((t: TodoModel) => t.isCompleted);
  }

  // 排序逻辑
  if (this._sortType === SortType.createdAt) {
    result.sort((a: TodoModel, b: TodoModel) => b.createdAt - a.createdAt);
  } else if (this._sortType === SortType.priority) {
    result.sort((a: TodoModel, b: TodoModel) => b.priority - a.priority);
  } else if (this._sortType === SortType.title) {
    result.sort((a: TodoModel, b: TodoModel) => a.title.localeCompare(b.title));
  }

  return result;
}

五、View层实现

5.1 页面状态管理

@Entry
@Component
struct TodoPage {
  // 状态变量
  @State todos: TodoModel[] = [];
  @State filter: TodoFilter = TodoFilter.all;
  @State sortType: SortType = SortType.createdAt;
  @State isLoading: boolean = true;
  @State totalCount: number = 0;
  @State activeCount: number = 0;
  @State completedCount: number = 0;
  
  // 底部弹窗状态
  @State showAddSheet: boolean = false;
  @State showDeleteConfirm: boolean = false;
  @State showClearConfirm: boolean = false;
  
  // 输入状态
  @State inputTitle: string = '';
  @State inputDescription: string = '';
  @State selectedPriority: TodoPriority = TodoPriority.medium;

  private viewModel: TodoViewModel = todoViewModel;
}

5.2 生命周期管理

async aboutToAppear(): Promise<void> {
  // 初始化数据仓库
  const context = getContext(this) as common.UIAbilityContext;
  await todoRepository.init(context);
  
  // 订阅ViewModel状态变化
  this.viewModel.subscribe((): void => this.updateState());
  await this.viewModel.loadTodos();
}

aboutToDisappear(): void {
  // 取消订阅,防止内存泄漏
  this.viewModel.unsubscribe((): void => this.updateState());
}

updateState(): void {
  this.todos = this.viewModel.todos;
  this.filter = this.viewModel.filter;
  this.sortType = this.viewModel.sortType;
  this.isLoading = this.viewModel.isLoading;
  this.totalCount = this.viewModel.totalCount;
  this.activeCount = this.viewModel.activeCount;
  this.completedCount = this.viewModel.completedCount;
}

5.3 UI组件构建

头部导航栏
@Builder
buildHeader(): void {
  Row() {
    Text('MVVM 待办事项')
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .fontColor(Color.White)
    
    Blank()
    
    Row() {
      Button() {
        Text('排序')
          .fontSize(14)
          .fontColor(Color.White)
      }
      .backgroundColor(Color.Transparent)
      .bindMenu(this.getSortMenuItems())

      Button() {
        Text('清除')
          .fontSize(14)
          .fontColor(Color.White)
      }
      .onClick((): void => {
        if (this.completedCount === 0) {
          promptAction.showToast({ message: '没有已完成的待办事项' });
        } else {
          this.showClearConfirm = true;
        }
      })
    }
  }
  .width('100%')
  .padding({ left: 16, right: 16, top: 12, bottom: 12 })
  .backgroundColor('#007DFF')
}
过滤标签栏
@Builder
buildFilterTabs(): void {
  Row() {
    ForEach(this.filterItems, (item: FilterItem): void => {
      Column() {
        Text(item.label)
          .fontSize(15)
          .fontColor(this.filter === item.filter ? '#007DFF' : '#666666')
          .fontWeight(this.filter === item.filter ? FontWeight.Bold : FontWeight.Normal)
        
        if (this.filter === item.filter) {
          Divider()
            .width(40)
            .height(3)
            .color('#007DFF')
            .margin({ top: 8 })
        }
      }
      .layoutWeight(1)
      .padding({ top: 12, bottom: 12 })
      .onClick((): void => this.viewModel.setFilter(item.filter))
    }, (item: FilterItem): string => item.label)
  }
  .width('100%')
  .backgroundColor(Color.White)
}
待办事项列表项
@Builder
buildTodoItem(todo: TodoModel): void {
  Row() {
    Checkbox()
      .select(todo.isCompleted)
      .selectedColor('#007DFF')
      .onChange((): void => { this.viewModel.toggleTodo(todo.id) })

    Column() {
      Text(todo.title)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor(todo.isCompleted ? '#999999' : '#333333')
        .decoration({ 
          type: todo.isCompleted ? TextDecorationType.LineThrough : TextDecorationType.None 
        })

      if (todo.description) {
        Text(todo.description)
          .fontSize(14)
          .fontColor(todo.isCompleted ? '#CCCCCC' : '#999999')
          .margin({ top: 4 })
      }
    }
    .layoutWeight(1)

    Text(getPriorityLabel(todo.priority))
      .fontSize(12)
      .fontColor(getPriorityColor(todo.priority))
      .backgroundColor(getPriorityColor(todo.priority) + '20')
      .borderRadius(4)

    Button() {
      Text('删除')
        .fontSize(14)
        .fontColor('#FF4444')
    }
    .backgroundColor(Color.Transparent)
    .onClick((): void => {
      this.deleteTodoId = todo.id;
      this.showDeleteConfirm = true;
    })
  }
  .width('100%')
  .padding(16)
  .backgroundColor(Color.White)
  .borderRadius(12)
  .margin({ top: 8 })
  .shadow({ radius: 4, color: '#00000010', offsetX: 0, offsetY: 2 })
}

5.4 底部弹窗实现

使用 bindSheet 实现添加待办事项的底部弹窗:

build() {
  Column() {
    this.buildHeader()
    this.buildFilterTabs()
    this.buildStatsBar()
    
    if (this.isLoading && this.todos.length === 0) {
      this.buildLoadingState()
    } else if (this.todos.length === 0) {
      this.buildEmptyState()
    } else {
      this.buildTodoList()
    }
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#F5F5F5')
  .bindSheet($$this.showAddSheet, this.buildAddSheet(), {
    height: 450,
    backgroundColor: Color.White,
    dragBar: true
  })
  .bindSheet($$this.showDeleteConfirm, this.buildDeleteConfirmSheet(), {
    height: 200,
    backgroundColor: Color.White,
    dragBar: false
  })
}

六、ArkTS编译注意事项

在鸿蒙ArkTS开发中,需要注意以下编译规则:

6.1 对象字面量类型声明

// ❌ 错误:对象字面量必须有明确的类型声明
.bindMenu([
  { value: '排序', action: () => {} }
])

// ✅ 正确:定义接口并使用
interface MenuItem {
  value: string;
  action: () => void;
}

private getSortMenuItems(): MenuItem[] {
  const items: MenuItem[] = [
    { value: '按创建时间', action: (): void => { this.viewModel.setSortType(SortType.createdAt) } }
  ];
  return items;
}

6.2 异步函数返回类型

// ❌ 错误:箭头函数隐式返回Promise<void>
.onChange((): void => this.viewModel.toggleTodo(todo.id))

// ✅ 正确:使用大括号避免隐式返回
.onChange((): void => { this.viewModel.toggleTodo(todo.id) })

6.3 ForEach key函数

// ✅ 正确:key函数必须返回string类型
ForEach(this.todos, (todo: TodoModel): void => {
  ListItem() {
    this.buildTodoItem(todo)
  }
}, (todo: TodoModel): string => todo.id)

七、应用功能展示

7.1 核心功能

功能 描述
添加待办 支持标题、描述、优先级设置
完成状态 点击复选框切换完成状态
删除待办 滑动或点击删除按钮
过滤筛选 全部/进行中/已完成
排序功能 按时间/优先级/标题排序
批量清除 一键清除已完成事项
数据持久化 本地存储,应用重启数据不丢失

7.2 界面预览

┌────────────────────────────────────┐
│  MVVM 待办事项      [排序] [清除]  │  ← 头部导航
├────────────────────────────────────┤
│   全部    进行中    已完成         │  ← 过滤标签
├────────────────────────────────────┤
│ ● 总计: 5  ● 进行中: 3  ● 已完成: 2│  ← 统计栏
├────────────────────────────────────┤
│ ☐ 完成项目文档          [高] [删除]│
│    详细描述内容...                 │  ← 待办列表
│ ☑ 提交代码审查          [中] [删除]│
│ ☐ 修复Bug               [低] [删除]│
│                                    │
└────────────────────────────────────┘

在这里插入图片描述

八、总结

通过本实战项目,我们学习了:

  1. MVVM架构在鸿蒙原生开发中的应用
  2. ArkTS语言的特性和编译规则
  3. 数据持久化存储方案
  4. 组件化UI开发模式
  5. 状态管理和观察者模式

这个待办事项应用展示了鸿蒙原生开发的完整流程,从数据模型设计到UI实现,涵盖了移动应用开发的核心知识点。希望本文能帮助你更好地理解鸿蒙原生开发,为你的项目开发提供参考。

九、参考资料


作者简介:专注于鸿蒙原生开发和Flutter跨平台技术,致力于分享移动开发最佳实践。

版权声明:本文为原创文章,转载请注明出处。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐