參考文章:https://juejin.cn/post/7381767811679502346
一般如果不依賴第三方庫的話,刷新UI的方式通常有以下幾種方式:
1、setState
2、ChangeNotifier
3、ValueNotifier(ValueListenableBuilder)
這裏主要記錄第三種方式,因為他有如下優點:
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方法都執行了;