博客 / 詳情

返回

記錄ValueNotifier(ValueListenableBuilder)的用法

參考文章:https://juejin.cn/post/7381767811679502346
一般如果不依賴第三方庫的話,刷新UI的方式通常有以下幾種方式:
1、setState
2、ChangeNotifier
3、ValueNotifierValueListenableBuilder
這裏主要記錄第三種方式,因為他有如下優點:
1、可以控制“局部”刷新,不像setState需要刷新整個“頁面”;
2、寫法簡單,不像ChangeNotifier需要addListener(還需要配置setState來刷新),也不需要removeListener

ValueNotifier可以監聽單個屬性,如果需要監聽的屬性多,也可以包裝成類,類對象裏面的任何屬性變動,依賴該監聽對象的所有build都會執行:

“缺點”:因為ValueListenableBuilder屬性valueListenable依賴的是整個“對象”,所以哪怕是不同屬性分裝成不同的組件,也只能依賴整個對象,也就是説,任何對象屬性的改變,所有的依賴都會重新渲染(provider組件可以解決這個問題);

例子如下(場景:需要監聽多個屬性的時候):

需要監聽的對象類型:emp_info.dart

class EmpInfo {
  int age;
  int count;
  EmpInfo({this.age = 0, this.count = 0});
}

包裝器:user_info_notifier.dart

import 'package:flutter/material.dart';
import 'package:octasync_client/views/projects/components/task_tab/emp_info.dart';

class UserInfoNotifier extends ValueNotifier<EmpInfo> {
  UserInfoNotifier() : super(EmpInfo()); // 默認構造函數

  /// 添加設置初始值的方法
  /// 因為初始化時,是改變的對應的引用,所以不需要 notifyListeners() 外部依賴也會刷新
  void setInitialValue(EmpInfo initialEmp) {
    value = initialEmp;
  }

  /// 改變單個屬性,需要調用notifyListeners()通知依賴
  void increase() {
    value.count++;
    notifyListeners();
  }

  void changeAge() {
    value.age++;
    notifyListeners();
  }
}

消費組件:test_widget_page.dart

import 'package:flutter/cupertino.dart';
import 'package:octasync_client/imports.dart';
import 'package:octasync_client/views/projects/components/task_tab/emp_info.dart';
import 'package:octasync_client/views/projects/components/task_tab/user_info_notifier.dart';

class TestWidgetPage extends StatefulWidget {
  const TestWidgetPage({super.key});

  @override
  State<TestWidgetPage> createState() => _TestWidgetPageState();
}

class _TestWidgetPageState extends State<TestWidgetPage> {
  /// 需要監聽的對象通過包裝器包裝
  final UserInfoNotifier _userInfoNotify = UserInfoNotifier();

  @override
  void initState() {
    super.initState();

    /// 模擬api請求獲取emp對象,用於初始化
    _loadInitialData();
  }

  /// 模擬api請求獲取數據,用於初始化數據
  Future<void> _loadInitialData() async {
    try {
      // 模擬 API 調用
      await Future.delayed(Duration(seconds: 3));

      // 假設從 API 獲取的數據
      EmpInfo apiData = EmpInfo(age: 25, count: 10);

      // 設置初始值
      _userInfoNotify.setInitialValue(apiData);
    } catch (e) {
      // 錯誤處理
      print('加載數據失敗: $e');
    } finally {}
  }

  @override
  void dispose() {
    _userInfoNotify.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print('1111 - 總入口');
    return Column(
      children: [
        // 展示 age 屬性
        _buildUserAge(context),
        // 修改 age 屬性
        AppButton(
          text: 'user 通過empInfoNotifier 改變 age',
          onPressed: () {
            print('aaaaaaaaaaaaa');
            _userInfoNotify.changeAge();
          },
        ),

        // 展示 count 屬性
        _buildUserCount(context),
        // 修改 count 屬性
        AppButton(
          text: 'user 通過empInfoNotifier 改變 count',
          onPressed: () {
            print('bbbbbb');
            _userInfoNotify.increase();
          },
        ),

        SizedBox(height: 20),

        // 模擬獲取最新對象
        AppButton(
          text: '獲取最新對象值',
          onPressed: () {
            print('獲取最新對象值');
            print(_userInfoNotify.value.age);
            print(_userInfoNotify.value.count);
          },
        ),
      ],
    );
  }

  /// 構建依賴 count 屬性對應的組件
  Widget _buildUserCount(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: _userInfoNotify,
      builder: (context, value, child) {
        print('User count 組件重新渲染 build');
        return Text('User count 當前值:${value.count}');
      },
    );
  }

  /// 構建依賴 age 屬性對應的組件
  Widget _buildUserAge(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: _userInfoNotify,
      builder: (context, value, child) {
        print('User age 組件重新渲染 build');
        return Text('User age 當前值:${value.age}');
      },
    );
  }
}

如上,當你點擊changeAge()對應的按鈕,還是點擊increase()對應的按鈕,兩個分別展示age、count屬性的組件內的build方法都會執行。

操作描述:
1、進入頁面後,等待3s(模擬api獲取對象數據),會刷新age、count(因為調用setInitialValue方法,是修改了對象的引用,哪怕沒有調用notifyListeners(),外部能夠被通知);
2、點擊按鈕上面兩個按鈕,雖然看上去只是改變了對應的屬性,實際上,他們對應的build方法都執行了;

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.