Flutter?隊(duì)列任務(wù)的實(shí)現(xiàn)
前言
在電商的應(yīng)用中,最常見(jiàn)的就是在首頁(yè)或完成某事件之后,彈出一堆的活動(dòng)/廣告。假如重疊彈出,很丑,給用戶的體驗(yàn)也不好,所以一般都會(huì)依次依條件的彈出。
下面講講我是怎么實(shí)現(xiàn)一個(gè)方便的隊(duì)列任務(wù)管理。
隊(duì)列
任務(wù)隊(duì)列,那當(dāng)然要有個(gè)隊(duì)列。這個(gè)隊(duì)列的任務(wù)內(nèi)容應(yīng)該是返回Future的Function,因?yàn)槲倚枰玫剿?strong>處理完成的結(jié)果,比如等待彈窗關(guān)閉時(shí)return,才表示這個(gè)任務(wù)被完成。
typedef TaskEventFunction = Future Function();
class TaskQueue {
List<TaskEventFunction> _actionQueue = [];
}添加任務(wù)進(jìn)隊(duì)列
然后是加入隊(duì)列的方法add
void add(TaskEventFunction task) {
_actionQueue.add(task);
}然后想到,隊(duì)列是不是最好去重?或者可選去重。
那一個(gè)Function如何去重呢,我們可以使用它的hashCode,同一個(gè)Function的hashCode相同。
taskQueue.add(testFunction);
Future testFunction() async {
await Future.delayed(const Duration(milliseconds: 1000));
return Future.value(true);
}即我們可以按上面這樣定義,同一個(gè)類同一個(gè)實(shí)例下同一個(gè)Function,就是相同的hashCode。若是以下這種寫(xiě)法則hashCode不一樣,每次add都相當(dāng)于一個(gè)新的Function。
taskQueue.add(() async {
await Future.delayed(const Duration(milliseconds: 1000));
return Future.value(true);
});假如不需要去重,可以用第二種。也可以主動(dòng)指定taskId來(lái)選擇哪些需要去重(即使內(nèi)容不一樣),哪些不需要。
修改一下add,增加taskId。同時(shí)hashCode應(yīng)該作為主要的key,修改一下隊(duì)列類型。 最終如下:
/// 任務(wù)編號(hào)隊(duì)列
List<String> _actionQueue = [];
/// 任務(wù)隊(duì)列
Map<String, TaskEventFunction> _actionMap = {};
/// 指定taskId 為 -1 則不用去重
String add(TaskEventFunction task, {
String? taskId,
}) {
String? id = taskId;
id ??= task.hashCode.toString();
bool isContains = false;
if (taskId != '-1') {
isContains = _actionQueue.contains(id);
} else {
id = task.hashCode.toString();
}
if (!isContains) {
_actionQueue.add(id);
_actionMap[id] = task;
}
return id;
}-1時(shí)則不去重,也把最終的taskId返回。
移除隊(duì)列指定任務(wù)
有添加任務(wù)的方法,那也需有移除任務(wù)的方法
/// 這里需注意,add的時(shí)taskId為-1,那就直接把task傳入,add時(shí)有返回,可以對(duì)應(yīng)
bool remove(TaskEventFunction task, {String? taskId}) {
String? id = taskId;
id ??= task.hashCode.toString();
if (_actionQueue.contains(id)) {
_actionMap.remove(id);
return _actionQueue.remove(id);
}
return false;
}以taskId/hashCode為準(zhǔn)。
判斷是否包含對(duì)應(yīng)任務(wù)
使用者可以自己判斷是否已包含對(duì)應(yīng)的任務(wù)
/// 是否隊(duì)列中包含該任務(wù)
/// [task] 與 [taskId] 應(yīng)該只傳一個(gè)
bool containers({TaskEventFunction? task, String? taskId}) {
assert(task != null || taskId != null);
String? id = taskId;
id ??= task.hashCode.toString();
return _actionQueue.contains(taskId);
}執(zhí)行隊(duì)列任務(wù)
任務(wù)隊(duì)列的進(jìn)出基本成型,開(kāi)始處理任務(wù)。很簡(jiǎn)單,取出任務(wù),然后執(zhí)行任務(wù),最后移除任務(wù)
void startLoop() async {
if (dealing || _actionQueue.isEmpty) {
return;
}
dealing = true;
String taskId = _actionQueue.first;
TaskEventFunction? callback = _actionMap[taskId];
if (callback == null) {
_actionQueue.remove(taskId);
return;
}
try {
await callback();
} catch (e) {
log('_actionQueue 出錯(cuò) $e');
} finally {
_actionQueue.remove(taskId);
_actionMap.remove(taskId);
dealing = false;
if (_actionQueue.isNotEmpty) {
startLoop();
}
}
}這里加了個(gè)dealing,表示任務(wù)正在處理中的狀態(tài),正在處理的話,不允許執(zhí)行下一個(gè)任務(wù)。
在執(zhí)行完成(或失?。┖螅?strong>自動(dòng)觸發(fā)下一個(gè)任務(wù)。add中也可以加入startLoop,使添加任務(wù)后自動(dòng)啟動(dòng)任務(wù)循環(huán)。
任務(wù)條件
基本的任務(wù)隊(duì)列已經(jīng)完成。不過(guò)每個(gè)任務(wù)其實(shí)一般都會(huì)有個(gè)條件,確認(rèn)符合當(dāng)前場(chǎng)景才執(zhí)行。比如說(shuō)的確是新人且在首頁(yè),才顯示新人優(yōu)惠彈窗。
條件可以是整個(gè)任務(wù)隊(duì)列統(tǒng)一的條件,也可以是某個(gè)任務(wù)特定的條件。
typedef TaskConditionFunction = FutureOr<bool> Function();
這里類型用FutureOr<bool>,有可能需要等待一下才能確認(rèn)條件。任務(wù)對(duì)應(yīng)的條件,也根據(jù)taskId來(lái)存儲(chǔ)。
/// 任務(wù)條件
Map<String, TaskConditionFunction> _actionCondition = {};
/// 總條件
TaskConditionFunction? _condition;添加任務(wù)時(shí)加入條件
TaskEventFunction add(TaskEventFunction task, {
String? taskId,
TaskConditionFunction? condition,
}) {
...
if (condition != null && !_actionCondition.containsKey(id)) {
_actionCondition[id] = condition;
}
}設(shè)置總條件,或補(bǔ)充條件
/// 設(shè)置允許執(zhí)行條件
void setAcceptConditions(TaskConditionFunction condition,
{String? taskId}) {
if (taskId == null) {
_condition = condition;
} else {
_actionCondition[taskId] = condition;
}
}執(zhí)行任務(wù)前判斷條件是否滿足
bool canNext = await _nextConditions(taskId);
if (canNext) {
try {
await callback();
} catch (e) {
log('_actionQueue 出錯(cuò) $e');
} finally {
_actionQueue.remove(taskId);
_actionMap.remove(taskId);
dealing = false;
if (_actionQueue.isNotEmpty) {
startLoop();
}
}
} else {
// 不滿足條件一般后續(xù)的也不會(huì)執(zhí)行
dealing = false;
}Future<bool> _nextConditions([String? id]) async {
String taskId = id ?? _actionQueue.first;
bool canNext = true;
var taskCondition = _actionCondition[taskId];
if (_condition != null) {
canNext = await _condition!.call();
}
if (canNext && taskCondition != null) {
canNext = await taskCondition();
}
return Future.value(canNext);
}原則上應(yīng)該既滿足總條件也滿足任務(wù)條件。
使用和總結(jié)
實(shí)例化TaskQueue
TaskQueue taskQueue = TaskQueue();
添加任務(wù)并開(kāi)始任務(wù)
taskQueue.add(testFunction, condition: () async {
await Future.delayed(const Duration(milliseconds: 200));
return Future.value(true);
});
taskQueue.startLoop();
Future testFunction() async {
return showDialog(...);
}注意設(shè)置條件要返回bool。
還可以加入類似延時(shí)等操作,跟條件一樣配置即可。太久的操作不要像上面一樣寫(xiě)在condition中,可能執(zhí)行完之后又不滿足了,根據(jù)具體情況考慮。
一般來(lái)說(shuō)類似彈窗的return或await showDialog就可以等待彈窗頁(yè)面結(jié)束,再進(jìn)行下一個(gè)。
跨頁(yè)面還是當(dāng)前頁(yè)面的控制,只需要考慮是全局實(shí)例TaskQueue還是頁(yè)面內(nèi)實(shí)例TaskQueue。
這樣一個(gè)使用簡(jiǎn)單好用的任務(wù)隊(duì)列就實(shí)現(xiàn)好了,更多相關(guān)Flutter 隊(duì)列任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解Flutter自定義應(yīng)用程序內(nèi)鍵盤(pán)的實(shí)現(xiàn)方法
- Flutter實(shí)現(xiàn)切換應(yīng)用時(shí)隱藏應(yīng)用預(yù)覽
- Flutter獲取ListView當(dāng)前正在顯示的Widget信息(應(yīng)用場(chǎng)景)
- Android利用Flutter?path繪制粽子的示例代碼
- 基于Flutter實(shí)現(xiàn)多邊形和多角星組件
- Flutter?WebView?預(yù)加載實(shí)現(xiàn)方法(Http?Server)
- 基于flutter?sound插件實(shí)現(xiàn)錄音與播放功能
- Flutter添加頁(yè)面過(guò)渡動(dòng)畫(huà)實(shí)現(xiàn)步驟
- flutter項(xiàng)目引入iconfont阿里巴巴圖標(biāo)
相關(guān)文章
Android實(shí)現(xiàn)獲取聯(lián)系人電話號(hào)碼功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)獲取聯(lián)系人電話號(hào)碼功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
Android Studio之Debug運(yùn)行期代碼植入的方法
這篇文章主要介紹了Android Studio之Debug運(yùn)行期代碼植入的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
Android ConstraintLayout約束布局使用實(shí)例介紹
ConstraintLayout是Google在Google I/O 2016大會(huì)上發(fā)布的一種新的布局容器(ViewGroup),它支持以靈活的方式來(lái)放置子控件和調(diào)整子控件的大小,下面這篇文章主要給大家介紹了關(guān)于Android中ConstraintLayout約束布局詳細(xì)解析的相關(guān)資料,需要的朋友可以參考下2022-10-10
UIImage初始化的區(qū)別兩種方法介紹(面試常見(jiàn))
本文通過(guò)兩種方法給大家介紹UIImage初始化的區(qū)別,在面試過(guò)程中經(jīng)常遇到,對(duì)uiimage初始化相關(guān)知識(shí)感興趣的朋友一起了解下吧2016-05-05
MVVMLight項(xiàng)目Model?View結(jié)構(gòu)及全局視圖模型注入器
這篇文章主要為大家介紹了MVVMLight項(xiàng)目中Model及View的結(jié)構(gòu)及全局視圖模型注入器的使用說(shuō)明,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-01-01
Android Studio的安裝及第一次啟動(dòng)時(shí)的配置問(wèn)題
這篇文章主要介紹了Android Studio的安裝及第一次啟動(dòng)時(shí)的配置,需要的朋友可以參考下2019-09-09
item高度不同時(shí)Recyclerview獲取滑動(dòng)距離的方法
這篇文章主要介紹了item高度不同時(shí)Recyclerview獲取滑動(dòng)距離的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11
兩分鐘讓你徹底明白Android Activity生命周期的詳解(圖文介紹)
本篇文章是對(duì)Android的生命周期進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05

