Dart部分
String擴(kuò)展一個(gè)方法
- 使用關(guān)鍵字
extension ... on
為String定義一個(gè)擴(kuò)展類 - 在為擴(kuò)展類添加一個(gè) 新的方法
- String類型對(duì)象調(diào)用這個(gè)擴(kuò)展的方法
extension StringExt on String{
// 2. 擴(kuò)展方法
int add(int x, int y){
return x + y;
}
}
void mian(){
String str = 'hello';
// 3. 使用擴(kuò)展類方法
int result = str.add(3, 7);
debugPrint(result.toString());
}
// 單元測(cè)試
import 'package:flutter_start/extTest.dart';
import 'package:flutter_test/flutter_test.dart';
void main(){
test('StringExt', (){
String ext = 'ext';
expect(ext.add(3, 7), 10);
});
}
dart是單繼承還是多繼承蛾方?
單繼承
dart如何達(dá)到多繼承的效果转捕?
Dart中使用Mixins
,可以達(dá)到多繼承的效果
mixin混入有什么特點(diǎn)
- 作為mixins的類只能
繼承自O(shè)bject
痘儡,不能繼承其他類 - 作為mixins的類
不能有構(gòu)造函數(shù)
- 一個(gè)類可以mixins
多個(gè)mixins類
- mixins絕不是繼承沉删,也不是接口醉途,而是一種全新的特性
// 類D 繼承A和B 關(guān)鍵字 with
class D extends A with B{
}
// mixin 的使用
class A {
String info="this is A";
void printA(){
print("A");
}
void run(){
print("A Run");
}
}
class B {
void printB(){
print("B");
}
void run(){
print("B Run");
}
}
class C extends Person with B,A{
C(String name, num age) : super(name, age);
}
混入相同方法的多個(gè)混入隘擎,最終會(huì)執(zhí)行哪一個(gè)货葬?
后面的類中的方法將前面的類中相同的方法覆蓋
dart運(yùn)行機(jī)制是什么樣的?
消息循環(huán)機(jī)制
- 兩個(gè)隊(duì)列休傍,微任務(wù)隊(duì)列和事件隊(duì)列蹲姐。
- microtask queue 的優(yōu)先級(jí)高于event queue柴墩。
- 在每一次事件循環(huán)中江咳,Dart總是先去第一個(gè)microtask queue中查詢是否有可執(zhí)行的任務(wù),如果沒有汹胃,才會(huì)處理后續(xù)的event queue的流程着饥。
如何向事件隊(duì)列插入任務(wù)惰赋?
Future就是將任務(wù)插入到事件隊(duì)列
向微任務(wù)隊(duì)列插入任務(wù)
Future.microtask()
scheduleMicrotask()
Stream
中的執(zhí)行異步的模式就是scheduleMicrotask
赁濒。因?yàn)閙icrotask的優(yōu)先級(jí)又高于event。所以挪拟,如果 microtask 太多
就可能會(huì)對(duì)觸摸玉组、繪制等外部事件造成阻塞卡頓
丁侄。
Future和Stream有什么區(qū)別鸿摇?
- Future中的任務(wù)會(huì)加入下一輪事件循環(huán)拙吉,而Stream中的任務(wù)則是加入微任務(wù)隊(duì)列
- Future 用于表示單個(gè)運(yùn)算的結(jié)果,而 Stream 則表示多個(gè)結(jié)果的序列恩商。
Stream 有同步流和異步流
之分怠堪。它們的區(qū)別在于同步流
會(huì)在執(zhí)行 add名眉,addError 或 close 方法時(shí)立即向流的監(jiān)聽器 StreamSubscription 發(fā)送事件损拢,而異步流
總是在事件隊(duì)列中的代碼執(zhí)行完成后在發(fā)送事件。`
Stream訂閱模式有哪幾種掏秩?
Stream分為Single Subscription和Broadcast兩種類型蒙幻, 前者只允許訂閱(listen)一次邮破,后者允許多次訂閱。
單訂閱在訂閱者出現(xiàn)之前會(huì)持有數(shù)據(jù)矫渔,在訂閱者出現(xiàn)之后就才轉(zhuǎn)交給它庙洼。
廣播訂閱可以同時(shí)有多個(gè)訂閱者范嘱,當(dāng)有數(shù)據(jù)時(shí)就會(huì)傳遞給所有的訂閱者丑蛤,而不管當(dāng)前是否已有訂閱者存在受裹。
Stream單訂閱,多次訂閱會(huì)出現(xiàn)什么結(jié)果厦章?
會(huì)報(bào)錯(cuò)照藻,單訂閱只能有一次訂閱.
即使取消了第一個(gè)監(jiān)聽器幸缕,也不允許在單訂閱流上設(shè)置其他的監(jiān)聽器发乔。
Stream 可以通過 transform() 方法(返回另一個(gè) Stream)進(jìn)行連續(xù)調(diào)用。
通過 Stream.asBroadcastStream() 可以將一個(gè)單訂閱模式的 Stream 轉(zhuǎn)換成一個(gè)多訂閱模式的 Stream起愈,isBroadcast 屬性可以判斷當(dāng)前 Stream 所處的模式抬虽。
dart是單線程還是多線程?
Dart是單線程模型
缰猴。
Dart是如何實(shí)現(xiàn)多任務(wù)并行的
主要依賴dart的并發(fā)編程(Isolate)、異步和事件驅(qū)動(dòng)機(jī)制
Completer
// 獲取圖片的寬高
Future<Size> getImageSize(String path) {
Completer<Size> completer = Completer<Size>();
Image image = Image.file(File.fromUri(Uri.parse(path)));
// 預(yù)先獲取圖片信息
image.image.resolve(const ImageConfiguration()).addListener(
ImageStreamListener((ImageInfo info, bool _) {
Size size = Size(info.image.width.toDouble(), info.image.height.toDouble());
completer.complete(size);
}));
return completer.future;
}
Isolate
- 使用場(chǎng)景:視頻的編碼轉(zhuǎn)碼隘膘,需要非常高的CPU計(jì)算
- 在dart中弯菊,一個(gè)Isolate對(duì)象其實(shí)就是一個(gè)Isolate執(zhí)行環(huán)境的引用管钳,一般來說我們都是通過當(dāng)前的Isolate去控制其他的Isolate完成彼此之間的交互软舌,而當(dāng)我們想要?jiǎng)?chuàng)建一個(gè)新的Isolate可以使用
Isolate.spawn
方法獲取一個(gè)新的Isolate對(duì)象佛点,兩個(gè)Isolate之間使用SendPort相互發(fā)送消息
超营,而Isolate中也存在了一個(gè)與之對(duì)應(yīng)ReceivePort
接收消息用來處理,但是我們需要注意的是SendPort
和ReceivePort
在每一個(gè)Isolate都有一對(duì)不跟,只有同一個(gè)Isolate中的ReceivePort才能接受當(dāng)前類的SendPort發(fā)送的消息并且處理窝革。 - Isolate可以把它理解為Dart中的線程聊闯。但它又不同于線程米诉,更恰當(dāng)?shù)恼f應(yīng)該是微線程。它與線程最大的區(qū)別就是不能共享內(nèi)存魏身,因此也不存在鎖競(jìng)爭(zhēng)問題蚪腐,兩個(gè)Isolate完全是兩條獨(dú)立的執(zhí)行線回季,且每個(gè)Isolate都有自己的事件循環(huán)泡一,它們之間只能通過發(fā)送消息通信,所以它的資源開銷低于線程涵但。
創(chuàng)建Isolate的兩種方式:Isolate.spawn() 和 compute()
compute的使用還是有些限制矮瘟,它沒有辦法多次返回結(jié)果澈侠,也沒有辦法持續(xù)性的傳值計(jì)算埋酬,每次調(diào)用奇瘦,相當(dāng)于新建一個(gè)隔離耳标,如果調(diào)用過多的話反而會(huì)適得其反。
@override
void initState() async {
super.initState();
// 主Isolate的ReceivePort
ReceivePort receivePort = ReceivePort();
SendPort? otherSendPort;
// 主Isolate接收到子Isolate中由主Isolate的SendPort發(fā)送過來的消息
receivePort.listen((message) {
if(message is SendPort){
otherSendPort = message;
otherSendPort.send(receivePort.sendPort());
}else{
// 處理消息
// ......
// 子Isolate的SendPort在主Isolate中向子Isolate發(fā)送消息
otherSendPort?.send('我是來自主Isolate的消息');
}
});
// 創(chuàng)建子Isolate
Isolate isolate = await Isolate.spawn((message) {
// message 是主Isolate的SendPort
// 在子Isolate中創(chuàng)建一個(gè)新的ReceivePort
ReceivePort recPort = ReceivePort();
// 主Isolate的SendPort
SendPort? mainSendPort;
// 運(yùn)用主Isolate的SendPort將子Isolate的SendPort發(fā)送給主Isolate
message.send(recPort.sendPort);
// 子Isolate監(jiān)聽接收到主Isolate那邊發(fā)送的消息 誰發(fā)送?子Isolate的SendPort
recPort.listen((msg) {
if(msg is SendPort){
mainSendPort = msg;
}else{
// 主Isolate的SendPort向主Isolate發(fā)送消息
mainSendPort?.send('我是來自子Isolate的消息');
}
});
}, receivePort.sendPort);// 參數(shù)二 將主Isolate的SendPort傳遞給子Isolate
}
// 方式二
// 注冊(cè)主 isolate 的 SendPort
ReceivePort mainReceivePort = ReceivePort();
mainReceivePort.listen((state){
// 獲取上次執(zhí)行任務(wù)的時(shí)間戳
final lastExecutedTime = SpUtil().getStandLastTime(); // 從本地存儲(chǔ)或數(shù)據(jù)庫獲取
final now = DateTime.now().millisecondsSinceEpoch;
final interval = now - lastExecutedTime;
if (kDebugMode) {
print("Native called background interval: $interval");
}
// 判斷是否達(dá)到 1 小時(shí)
if (interval >= 3600000) {
// 執(zhí)行你的任務(wù)
/// 檢查步數(shù)是否在變化 沒有變化需要站立提醒
GlobalEvent().emit(GlobalName.wearStandCheck);
// 更新上次執(zhí)行時(shí)間
SpUtil().setStandLastTime(now); // 將 now 保存到本地存儲(chǔ)或數(shù)據(jù)庫
}
});
IsolateNameServer.registerPortWithName(mainReceivePort.sendPort, 'main_isolate');
Workmanager().initialize(
callbackDispatcher, // The top level function, aka callbackDispatcher
isInDebugMode: false // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
);
/// Android系統(tǒng)限制 15分鐘執(zhí)行一次
Workmanager().registerPeriodicTask(
"periodic-task-identifier",
"simplePeriodicTask",
inputData: <String, dynamic>{
'key': 'value',
},
// When no frequency is provided the default 15 minutes is set.
// Minimum frequency is 15 min. Android will automatically change your frequency to 15 min if you have configured a lower frequency.
frequency: const Duration(minutes: 15),
);
await for如何使用?
await for是用來不斷獲取stream流中的數(shù)據(jù)谚赎,然后執(zhí)行循環(huán)體中的操作壶唤。它一般用在直到stream什么時(shí)候完成,并且必須等待傳遞完成后才能使用悯辙,不然會(huì)阻塞躲撰。
Stream<String> stream = new Stream<String>.fromIterable(['1', '2', '3','4']);
main() async{
await for(String s in stream){
print(s);
}
}
使用場(chǎng)景:網(wǎng)絡(luò)請(qǐng)求流式返回
// http庫流式返回
Future<void> postStream({required String token,required String url, required Map<String, dynamic> body,required Function(String value) onMessage}) async {
try {
final request = http.Request('POST', Uri.parse(url));
request.headers['Content-Type'] = 'application/json; charset=UTF-8';
request.headers['Authorization'] = token;
request.body = jsonEncode(body);
final response = await request.send();
if (response.statusCode == 200) {
await for (var chunk in response.stream.transform(utf8.decoder)) {
onMessage(chunk);
}
} else {
throw Exception('服務(wù)器錯(cuò)誤');
}
}catch (e) {
Logger.debugLog('Error: $e');
throw Exception('服務(wù)器錯(cuò)誤');
}
}
// dio流式返回
Future<void> postStream({required String url, required Map<String, dynamic> body,required Function(String value) onMessage,CancelToken? cancelToken,}) async {
try {
Options requestOptions = Options(receiveTimeout: 60000,sendTimeout: 30000);
requestOptions.headers = {
..._dio!.options.headers,
'Authorization': Ability().getLoginToken(),
'Content-Type': 'application/json'
};
requestOptions.responseType = ResponseType.stream;
Response<dynamic> response = await _dio!.post(
url,
data: jsonEncode(body),
options: requestOptions,
cancelToken: cancelToken,
);
if(response.statusCode == 200){
await for (var chunk in response.data.stream) {
final jsonData = utf8.decode(chunk);
onMessage(jsonData);
}
}
} catch (e) {
// 處理請(qǐng)求失敗的情況
print("postStream Request failed: $e");
rethrow;
}
}
如何實(shí)現(xiàn)websocket穩(wěn)定連接?
- 定期發(fā)送心跳包,一般1秒一次
- 捕獲關(guān)閉連接事件并重連websocket
- 實(shí)現(xiàn)斷線重連
Flutter部分
A批幌、B兩個(gè)組件在setState前修改背景顏色嗓节,是否會(huì)修改成功拦宣?
會(huì)
Flutter 渲染流程是什么鸵隧?(GPU)
將dart語言的UI代碼轉(zhuǎn)換成skia能識(shí)別的數(shù)據(jù),進(jìn)行渲染珊蟀。
Flutter向GPU提供視圖數(shù)據(jù)的過程育灸。
Flutter只關(guān)心向 GPU提供視圖數(shù)據(jù)磅崭,GPU的 VSync信號(hào)同步到 UI線程瓦哎,UI線程使用 Dart來構(gòu)建抽象的視圖結(jié)構(gòu),這份數(shù)據(jù)結(jié)構(gòu)在 GPU線程進(jìn)行圖層合成卒落,視圖數(shù)據(jù)提供給 Skia引擎渲染為 GPU數(shù)據(jù)儡毕,這些數(shù)據(jù)通過 OpenGL或者 Vulkan提供給 GPU扑媚。
Widget费坊、Element附井、RenderObject三者關(guān)系是什么两残?
Widget包含業(yè)務(wù)代碼人弓,widget樹更龐大崔赌;Element是對(duì)widget的抽取,只包含build函數(shù)县钥,去除業(yè)務(wù)代碼魁蒜。
簡(jiǎn)述
widget是用于描述Element配置信息的兜看,flutter中一切都是widget狭瞎,尺寸熊锭、顏色、組件等都是widget
element是widget樹上特定位置的實(shí)例
renderobject是渲染樹上的一個(gè)對(duì)象
依賴關(guān)系:Element樹依賴Widget樹速缨,渲染樹依賴Element樹代乃,最終的UI樹是由獨(dú)立的Element節(jié)點(diǎn)構(gòu)成搁吓。
一個(gè)widget會(huì)創(chuàng)建一個(gè)element
一個(gè)element持有一個(gè)widget和render object原茅,element
會(huì)對(duì)比widget的變化,將那寫需要更新和重建的widget堕仔,同步到render object樹擂橘,以最小的開銷來渲染
一、它們是什么摩骨?
- Widget:對(duì)一個(gè)Element配置的描述通贞,刷新的過程中隨時(shí)會(huì)重建。(不參與真正的渲染恼五,widget的屬性是不可以改變的,要想改變只能重新創(chuàng)建一個(gè)widget對(duì)象)
- Element:表示一個(gè)Widget樹中特定位置的實(shí)例唤冈,用于對(duì)比widget,找出需要更新和重建的widget银伟,更新Element樹和RenderObject樹你虹。
- RenderObject:渲染樹上的一個(gè)對(duì)象,用于界面的布局和繪制彤避,負(fù)責(zé)真正的渲染傅物,實(shí)例化一個(gè) RenderObject 是非常耗能。
二琉预、關(guān)系
- 一個(gè)Widget會(huì)創(chuàng)建一個(gè)Element對(duì)象董饰,是通過createElement()創(chuàng)建的。
- 一個(gè)Element持有一個(gè)RenderObject和一個(gè)Widget圆米。Element樹與Widget樹一一對(duì)應(yīng)卒暂,每個(gè)Element負(fù)責(zé)管理一個(gè)Widget的配置和生命周期。
- Widget 具有不可變性娄帖,但 Element 卻是可變的也祠。Element 樹將 Widget 樹的變化做了抽象,可以只將真正需要修改的部分同步到真實(shí)的 RenderObject 樹中近速,最大程度降低對(duì)真實(shí)渲染視圖的修改诈嘿,提高渲染效率堪旧,而不是銷毀整個(gè)渲染視圖樹重建。
flutter生命周期奖亚,setstate會(huì)執(zhí)行哪些生命周期淳梦?
- setState()執(zhí)行后,會(huì)執(zhí)行
build()
- 父Widget使用了InheritedWidget管理狀態(tài)昔字,子Widget使用了狀態(tài)數(shù)據(jù)爆袍,set State時(shí),子組件的
didChangeDependencies
會(huì)調(diào)用李滴。
https://juejin.cn/post/7348680291935862818?searchId=20240410133635350846A909709A89034C
- app的狀態(tài):AppLifecycleState
inactive
:活躍可見
paused
:關(guān)閉或者切換到后臺(tái)時(shí)螃宙,不可見的狀態(tài)
hidden
:后臺(tái)運(yùn)行狀態(tài)
resumed
:切回到前臺(tái)可見狀態(tài)
detached
:關(guān)閉狀態(tài)
Flutter SDK 3.13 之前的方式:
with WidgetsBindingObserver
在initState()
中注冊(cè) WidgetsBinding.instance.addObserver(this);
在dispose()
移除 WidgetsBinding.instance.removeObserver(this);
在didChangeAppLifecycleState()
回調(diào)中,檢測(cè)app的狀態(tài)Flutter SDK 3.13 之后的方式:
AppLifecycleListener
late final AppLifecycleListener _listener;
@override
void initState() {
super.initState();
// Initialize the AppLifecycleListener class and pass callbacks
_listener = AppLifecycleListener(
onStateChange: _onStateChanged,
);
}
@override
void dispose() {
// Do not forget to dispose the listener
_listener.dispose();
super.dispose();
}
// Listen to the app lifecycle state changes
void _onStateChanged(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.detached:
_onDetached();
case AppLifecycleState.resumed:
_onResumed();
case AppLifecycleState.inactive:
_onInactive();
case AppLifecycleState.hidden:
_onHidden();
case AppLifecycleState.paused:
_onPaused();
}
}
StatefulWidget 的生命周期方法
createState
:可以調(diào)用多次
State 對(duì)象的生命周期方法
class _MyWidgetState extends State<MyWidget> {
@override
void initState() {
super.initState();
// 在 State 對(duì)象被插入樹中時(shí)調(diào)用所坯,這個(gè)方法只會(huì)被調(diào)用一次谆扎。
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// 在 initState 之后調(diào)用,表示 State 對(duì)象的依賴關(guān)系發(fā)生變化芹助。
// state對(duì)象依賴發(fā)生改變會(huì)調(diào)用
}
@override
Widget build(BuildContext context) {
// 在此構(gòu)建 Widget 樹
return Container();
}
@override
void didUpdateWidget(MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// 當(dāng)父 Widget 重建時(shí)堂湖,canUpdate 返回true時(shí)調(diào)用。
// 如果父 Widget 重建時(shí)需要重新配置子 Widget状土,則會(huì)調(diào)用此方法无蜂。
}
@override
void deactivate() {
// 在此處理 State 對(duì)象從樹中被移除的操作
super.deactivate();
}
@override
void dispose() {
// 當(dāng) State 對(duì)象被永久從樹中移除時(shí)調(diào)用
super.dispose();
}
}
// 打開頁面執(zhí)行:initState、didChangeDependencies
// 關(guān)閉頁面執(zhí)行:deactivate蒙谓、dispose
StatelessWidget 的生命周期方法
class MyWidget extends StatefulWidget {
@override
Widget build(BuildContext context) {
return Widget;
}
}
StatelessWidget組件如何監(jiān)聽數(shù)據(jù)變化斥季?
- context.watch<Counter>
- ChangeNotifierProvider + Consumer
Element的生命周期
initial 初始化
active 激活狀態(tài)
inactive 未激活狀態(tài)
defunct 失效狀態(tài)
如何監(jiān)聽頁面paused和resume狀態(tài)?
利用RouteObserver
在MaterialApp
中注冊(cè)navigatorObservers
累驮,然后在頁面中注冊(cè)監(jiān)聽routeObserver.subscribe(this, ModalRoute.of(context)!)
酣倾,同時(shí)混入RouteAware
,重寫didPopNext 和 didPushNext
谤专,可實(shí)現(xiàn)對(duì)頁面的監(jiān)聽躁锡。
setState()執(zhí)行做了什么事?
setState()過程主要工作是記錄所有的臟元素置侍,會(huì)引起build函數(shù)執(zhí)行映之,更新widget樹、更新Element樹和RenderObject樹蜡坊,最后重新渲染杠输。
flutter中的key?
- 作用:比較兩個(gè)Widget是不是同一個(gè)Widget
- 分類:LocaleKey秕衙、GlobalKey
- LocaleKey:ValueKey抬伺、ObjectKey、UniqueKey灾梦。
statelesswidget和statefullwidget有什么區(qū)別峡钓?
StatelessWidget 沒有要管理的內(nèi)部狀態(tài).
無狀態(tài)widget的build方法通常只會(huì)在以下三種情況調(diào)用:
- 將widget插入樹中時(shí)
- 當(dāng)widget的父級(jí)更改其配置時(shí)
- 當(dāng)它依賴的InheritedWidget發(fā)生變化時(shí)
StatefullWidget是可變狀態(tài)的widget妓笙。 使用setState方法管理StatefulWidget的狀態(tài)的改變。調(diào)用setState告訴Flutter框架能岩,某個(gè)狀態(tài)發(fā)生了變化寞宫,F(xiàn)lutter會(huì)重新運(yùn)行build方法,以便應(yīng)用程序可以應(yīng)用最新狀態(tài)拉鹃。
在有狀態(tài)類中編寫一個(gè)按鈕調(diào)用初始化生命周期(initState)方法辈赋,會(huì)發(fā)生什么?
報(bào)錯(cuò)膏燕,但不影響布局钥屈;會(huì)報(bào)生命周期創(chuàng)建錯(cuò)誤;
如何獲取控件的大小和位置坝辫?
- 使用Key拿到上下文取得findRenderObject拿內(nèi)容的尺寸數(shù)據(jù)篷就;
- 使用context取得findRenderObject拿內(nèi)容的尺寸數(shù)據(jù);
Flutter 是如何與原生Android近忙、iOS進(jìn)行通信的竭业?
PlatformChannel
BasicMessageChannel :用于傳遞字符串和半結(jié)構(gòu)化的信息。
MethodChannel :用于傳遞方法調(diào)用(method invocation)及舍。
EventChannel : 用于數(shù)據(jù)流(event streams)的通信未辆。
// 1. 創(chuàng)建java類。 實(shí)現(xiàn)FlutterPlugin和MethodCallHandler
public class MsaOaidPlugin implements FlutterPlugin, MethodCallHandler{
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
Log.e("---------","==========onAttachedToEngine");
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "msa_oaid");
channel.setMethodCallHandler(this);
this.context = flutterPluginBinding.getApplicationContext();
System.loadLibrary("msaoaidsec");
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
Log.e("---------","==========onMethodCall");
if(call.method.equals("isSupport")){
new DemoHelper().getDeviceIds(context, new IIdentifierListener() {
@Override
public void onSupport(IdSupplier idSupplier) {
result.success(idSupplier.isSupported());
}
});
}else if(call.method.equals("getOaid")){
new DemoHelper().getDeviceIds(context, new IIdentifierListener() {
@Override
public void onSupport(IdSupplier idSupplier) {
result.success(idSupplier.getOAID());
}
});
}else{
result.notImplemented();
}
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
Log.e("---------","==========onDetachedFromEngine");
}
}
// 2. flutter lib包下創(chuàng)建dart類
class MsaOaid {
static const MethodChannel _channel = MethodChannel('msa_oaid');
static Future<bool> isSupport() async {
final bool support = await _channel.invokeMethod('isSupport');
return support;
}
static Future<String?> getOaid() async {
final String? oaid = await _channel.invokeMethod('getOaid');
return oaid;
}
}
Flutter中Widget的分類有哪些锯玛?Widget狀態(tài)有哪些咐柜?
Widget的分類有三類。
- 組合類Widget,通過繼承StatelessWidget和StatefulWidget的類。
- 代理類Widget,如功能組件InheritedWidget。Theme蔬浙、MediaQuery正是基于InheritedWidget實(shí)現(xiàn)的。
- 繪制類Widget浩螺,通過RenderObjectWidget實(shí)現(xiàn)的Widget疫剃,如Align、Padding涉瘾、ConstrainedBox等知态。
Widget狀態(tài)有: StatelessWidget 和 StatefulWidget