亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Flutter之PageView頁(yè)面緩存與KeepAlive

 更新時(shí)間:2022年10月27日 17:22:34   作者:風(fēng)雨_83  
這篇文章主要為大家介紹了Flutter之PageView頁(yè)面緩存與KeepAlive示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

如果要實(shí)現(xiàn)頁(yè)面切換和 Tab 布局,我們可以使用 PageView 組件。需要注意,PageView 是一個(gè)非常重要的組件,因?yàn)樵谝苿?dòng)端開(kāi)發(fā)中很常用,比如大多數(shù) App 都包含 Tab 換頁(yè)效果、圖片輪動(dòng)以及抖音上下滑頁(yè)切換視頻功能等等,這些都可以通過(guò) PageView 輕松實(shí)現(xiàn)。

構(gòu)造函數(shù)

PageView({
  Key? key,
  this.scrollDirection = Axis.horizontal, // 滑動(dòng)方向
  this.reverse = false,
  PageController? controller,
  this.physics,
  List<Widget> children = const <Widget>[],
  this.onPageChanged,
  //每次滑動(dòng)是否強(qiáng)制切換整個(gè)頁(yè)面,如果為false,則會(huì)根據(jù)實(shí)際的滑動(dòng)距離顯示頁(yè)面
  this.pageSnapping = true,
  //主要是配合輔助功能用的,后面解釋
  this.allowImplicitScrolling = false,
  //后面解釋
  this.padEnds = true,
})

我們看一個(gè) Tab 切換的實(shí)例,為了突出重點(diǎn),我們讓每個(gè) Tab 頁(yè)都只顯示一個(gè)數(shù)字。

// Tab 頁(yè)面 
class Page extends StatefulWidget {
  const Page({
    Key? key,
    required this.text
  }) : super(key: key);
  final String text;
  @override
  _PageState createState() => _PageState();
}
class _PageState extends State<Page> {
  @override
  Widget build(BuildContext context) {
    print("build ${widget.text}");
    return Center(child: Text("${widget.text}", textScaleFactor: 5));
  }
}
@override
Widget build(BuildContext context) {
  var children = <Widget>[];
  // 生成 10 個(gè) Tab 頁(yè)
  for (int i = 0; i < 10; ++i) {
    children.add( Page( text: '$i'));
  }
  return PageView(
    // scrollDirection: Axis.vertical, // 滑動(dòng)方向?yàn)榇怪狈较?
    children: children,
  );
}

如果將 PageView 的滑動(dòng)方向指定為垂直方向(上面代碼中注釋部分),則會(huì)變?yōu)樯舷禄瑒?dòng)切換頁(yè)面。

頁(yè)面緩存

我們?cè)谶\(yùn)行上面示例時(shí),可能已經(jīng)發(fā)現(xiàn):每當(dāng)頁(yè)面切換時(shí)都會(huì)觸發(fā)新 Page 頁(yè)的 build,比如我們從第一頁(yè)滑到第二頁(yè),然后再滑回第一頁(yè)時(shí),控制臺(tái)打印如下:

flutter: build 0
flutter: build 1
flutter: build 0

可見(jiàn) PageView 默認(rèn)并沒(méi)有緩存功能,一旦頁(yè)面滑出屏幕它就會(huì)被銷(xiāo)毀, 和ListView/GridView 不一樣,在創(chuàng)建 ListView/GridView 時(shí)我們可以手動(dòng)指定 ViewPort 之外多大范圍內(nèi)的組件需要預(yù)渲染和緩存(通過(guò) cacheExtent 指定),只有當(dāng)組件滑出屏幕后又滑出預(yù)渲染區(qū)域,組件才會(huì)被銷(xiāo)毀,但是不幸的是 PageView 并沒(méi)有 cacheExtent 參數(shù)!但是在真實(shí)的業(yè)務(wù)場(chǎng)景中,對(duì)頁(yè)面進(jìn)行緩存是很常見(jiàn)的一個(gè)需求,比如一個(gè)新聞 App,下面有很多頻道頁(yè),如果不支持頁(yè)面緩存,則一旦滑到新的頻道舊的頻道頁(yè)就會(huì)銷(xiāo)毀,滑回去時(shí)又得重新請(qǐng)求數(shù)據(jù)和構(gòu)建頁(yè)面,這樣極度消耗性能。

按道理 cacheExtent 是 Viewport 的一個(gè)配置屬性,且 PageView 也是要構(gòu)建 Viewport 的,那么為什么就不能透?jìng)饕幌逻@個(gè)參數(shù)呢?于是筆者帶著這個(gè)疑問(wèn)看了一下 PageView 的源碼,發(fā)現(xiàn)在 PageView 創(chuàng)建Viewport 的代碼中是這樣的:

child: Scrollable(
  ...
  viewportBuilder: (BuildContext context, ViewportOffset position) {
    return Viewport(
      // TODO(dnfield): we should provide a way to set cacheExtent
      // independent of implicit scrolling:
      // https://github.com/flutter/flutter/issues/45632
      cacheExtent: widget.allowImplicitScrolling ? 1.0 : 0.0,
      cacheExtentStyle: CacheExtentStyle.viewport,
      ...
    );
  },
)

我們發(fā)現(xiàn) 雖然 PageView 沒(méi)有透?jìng)?cacheExtent,但是卻在allowImplicitScrolling 為 true 時(shí)設(shè)置了預(yù)渲染區(qū)域,注意,此時(shí)的緩存類(lèi)型為 CacheExtentStyle.viewport,則 cacheExtent 則表示緩存的長(zhǎng)度是幾個(gè) Viewport 的寬度,cacheExtent 為 1.0,則代表前后各緩存一個(gè)頁(yè)面寬度,即前后各一頁(yè)。既然如此,那我們將 PageView 的 allowImplicitScrolling 置為 true 則不就可以緩存前后兩頁(yè)了?我們修改代碼,然后運(yùn)行示例,發(fā)現(xiàn)在第一頁(yè)時(shí),控制臺(tái)打印信息如下:

flutter: build 0
flutter: build 1 // 預(yù)渲染第二頁(yè)

當(dāng)再滑回第一頁(yè)時(shí),控制臺(tái)信息不變,這也就意味著第一頁(yè)緩存成功,它沒(méi)有被重新構(gòu)建。但是如果我們從第二頁(yè)滑到第三頁(yè),然后再滑回第一頁(yè)時(shí),控制臺(tái)又會(huì)輸出 ”build 0“,這也符合預(yù)期,因?yàn)槲覀冎胺治龅木褪窃O(shè)置 allowImplicitScrolling 置為 true 時(shí)就只會(huì)緩存前后各一頁(yè),所以滑到第三頁(yè)時(shí),第一頁(yè)就會(huì)銷(xiāo)毀。

能緩存前后各一頁(yè)也貌似比不能緩存好一點(diǎn),但還是不能徹底解決不了我們的問(wèn)題。為什么明明就是順手的事, flutter 就不讓開(kāi)發(fā)者指定緩存策略呢?然后我們翻譯一下源碼中的注釋?zhuān)?/p>

Todo:我們應(yīng)該提供一種獨(dú)立于隱式滾動(dòng)(implicit scrolling)的設(shè)置 cacheExtent 的機(jī)制。

放開(kāi) cacheExtent 透?jìng)鞑痪褪琼樖值氖旅矗瑸槭裁催€要以后再做,是有什么難題么?這就要看看 allowImplicitScrolling 到底是什么了,根據(jù)文檔以及注釋中 issue 的鏈接,發(fā)現(xiàn)PageView 中設(shè)置 cacheExtent 會(huì)和 iOS 中 輔助功能有沖突(讀者可以先不用關(guān)注),所以暫時(shí)還沒(méi)有什么好的辦法??吹竭@可能?chē)?guó)內(nèi)的很多開(kāi)發(fā)者要說(shuō)我們的 App 不用考慮輔助功能,既然如此,那問(wèn)題很好解決,將 PageView 的源碼拷貝一份,然后透?jìng)?cacheExtent 即可。 考源碼的方式雖然很簡(jiǎn)單,但畢竟不是正統(tǒng)做法,那有沒(méi)有更通用的方法嗎?有!可滾動(dòng)組件提供了一種通用的緩存子項(xiàng)的解決方案,答案是有的。

KeepAlive

AumaticKeepAlive的組件的主要作用是將列表項(xiàng)的根 RenderObject 的 keepAlive 按需自動(dòng)標(biāo)記 為 true 或 false。為了方便敘述,我們可以認(rèn)為根 RenderObject 對(duì)應(yīng)的組件就是列表項(xiàng)的根 Widget,代表整個(gè)列表項(xiàng)組件,同時(shí)我們將列表組件的 Viewport區(qū)域 + cacheExtent(預(yù)渲染區(qū)域)稱(chēng)為加載區(qū)域 :

  • 當(dāng) keepAlive 標(biāo)記為 false 時(shí),如果列表項(xiàng)滑出加載區(qū)域時(shí),列表組件將會(huì)被銷(xiāo)毀。
  • 當(dāng) keepAlive 標(biāo)記為 true 時(shí),當(dāng)列表項(xiàng)滑出加載區(qū)域后,Viewport 會(huì)將列表組件緩存起來(lái);當(dāng)列表項(xiàng)進(jìn)入加載區(qū)域時(shí),Viewport 從先從緩存中查找是否已經(jīng)緩存,如果有則直接復(fù)用,如果沒(méi)有則重新創(chuàng)建列表項(xiàng)。

那么 AutomaticKeepAlive 什么時(shí)候會(huì)將列表項(xiàng)的 keepAlive 標(biāo)記為 true 或 false 呢?答案是開(kāi)發(fā)者說(shuō)了算!Flutter 中實(shí)現(xiàn)了一套類(lèi)似 C/S 的機(jī)制,AutomaticKeepAlive 就類(lèi)似一個(gè) Server,它的子組件可以是 Client,這樣子組件想改變是否需要緩存的狀態(tài)時(shí)就向 AutomaticKeepAlive 發(fā)一個(gè)通知消息(KeepAliveNotification),AutomaticKeepAlive 收到消息后會(huì)去更改 keepAlive 的狀態(tài),如果有必要同時(shí)做一些資源清理的工作(比如 keepAlive 從 true 變?yōu)?false 時(shí),要釋放緩存)。

我們基于上面 PageView 示例,實(shí)現(xiàn)頁(yè)面緩存,根據(jù)上面的描述實(shí)現(xiàn)思路就很簡(jiǎn)單了:讓Page 頁(yè)變成一個(gè) AutomaticKeepAlive Client 即可。為了便于開(kāi)發(fā)者實(shí)現(xiàn),F(xiàn)lutter 提供了一個(gè) AutomaticKeepAliveClientMixin ,我們只需要讓 PageState 混入這個(gè) mixin,且同時(shí)添加一些必要操作即可:

class _PageState extends State<Page> with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context); // 必須調(diào)用
    return Center(child: Text("${widget.text}", textScaleFactor: 5));
  }
  @override
  bool get wantKeepAlive => true; // 是否需要緩存
}

代碼很簡(jiǎn)單,我們只需要提供一個(gè) wantKeepAlive,它會(huì)表示 AutomaticKeepAlive 是否需要緩存當(dāng)前列表項(xiàng);另外我們必須在 build 方法中調(diào)用一下 super.build(context),該方法實(shí)現(xiàn)在 AutomaticKeepAliveClientMixin 中,功能就是根據(jù)當(dāng)前 wantKeepAlive 的值給 AutomaticKeepAlive 發(fā)送消息,AutomaticKeepAlive 收到消息后就會(huì)開(kāi)始工作。

現(xiàn)在我們重新運(yùn)行一下示例,發(fā)現(xiàn)每個(gè) Page 頁(yè)只會(huì) build 一次,緩存成功了。需要注意,如果我們采用 PageView.custom 構(gòu)建頁(yè)面時(shí)沒(méi)有給列表項(xiàng)包裝 AutomaticKeepAlive 父組件,則上述方案不能正常工作,因?yàn)榇藭r(shí)Client 發(fā)出消息后,找不到 Server,404 了.

KeepAliveWrapper

雖然我們可以通過(guò) AutomaticKeepAliveClientMixin 快速的實(shí)現(xiàn)頁(yè)面緩存功能,但是通過(guò)混入的方式實(shí)現(xiàn)不是很優(yōu)雅,因?yàn)楸仨毟?Page 的代碼,有侵入性,這就導(dǎo)致不是很靈活,比如一個(gè)組件能同時(shí)在列表中和列表外使用,為了在列表中緩存它,則我們必須實(shí)現(xiàn)兩份。為了解決這個(gè)問(wèn)題,筆者封裝了一個(gè) KeepAliveWrapper 組件,如果哪個(gè)列表項(xiàng)需要緩存,只需要使用 KeepAliveWrapper 包裹一下它即可。

@override
Widget build(BuildContext context) {
  var children = <Widget>[];
  for (int i = 0; i < 10++i) {
    //只需要用 KeepAliveWrapper 包裝一下即可
    children.add(KeepAliveWrapper(child:Page( text: '$i'));
  }
  return PageView(children: children);
}

下面是 KeepAliveWrapper 的實(shí)現(xiàn)源碼:

class KeepAliveWrapper extends StatefulWidget {
  const KeepAliveWrapper({
    Key? key,
    this.keepAlive = true,
    required this.child,
  }) : super(key: key);
  final bool keepAlive;
  final Widget child;
  @override
  _KeepAliveWrapperState createState() => _KeepAliveWrapperState();
}
class _KeepAliveWrapperState extends State<KeepAliveWrapper>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return widget.child;
  }
  @override
  void didUpdateWidget(covariant KeepAliveWrapper oldWidget) {
    if(oldWidget.keepAlive != widget.keepAlive) {
      // keepAlive 狀態(tài)需要更新,實(shí)現(xiàn)在 AutomaticKeepAliveClientMixin 中
      updateKeepAlive();
    }
    super.didUpdateWidget(oldWidget);
  }
  @override
  bool get wantKeepAlive => widget.keepAlive;
}

可以看出也是基于AutomaticKeepAliveClientMixin實(shí)現(xiàn)了 bool get wantKeepAlive => widget.keepAlive;并且包裹了子組件。

總結(jié)

本章主要介紹了Pageview頁(yè)面緩存的兩種方式,AutomaticKeepAlive和KeepAliveWrapper包裹。另外還需要關(guān)注Viewport區(qū)域 + cacheExtent的緩存策略和場(chǎng)景。更多關(guān)于Flutter PageView頁(yè)面緩存的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • safari cookie設(shè)置中文失敗的解決方法

    safari cookie設(shè)置中文失敗的解決方法

    下面小編就為大家?guī)?lái)一篇safari cookie設(shè)置中文失敗的解決方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-08-08
  • IOS UI學(xué)習(xí)教程之設(shè)置UITextField各種屬性

    IOS UI學(xué)習(xí)教程之設(shè)置UITextField各種屬性

    這篇文章主要為大家詳細(xì)介紹了IOS UI學(xué)習(xí)教程之設(shè)置UITextField各種屬性,感興趣的小伙伴們可以參考一下
    2016-03-03
  • iOS開(kāi)發(fā)之視圖切換

    iOS開(kāi)發(fā)之視圖切換

    在iOS開(kāi)發(fā)中視圖的切換是很頻繁的,獨(dú)立的視圖應(yīng)用在實(shí)際開(kāi)發(fā)過(guò)程中并不常見(jiàn),除非你的應(yīng)用足夠簡(jiǎn)單。在iOS開(kāi)發(fā)中常用的視圖切換有三種,今天我們將一一介紹,希望大家能夠喜歡。
    2016-04-04
  • 舉例講解iOS中延遲加載和上拉刷新/下拉加載的實(shí)現(xiàn)

    舉例講解iOS中延遲加載和上拉刷新/下拉加載的實(shí)現(xiàn)

    這篇文章主要介紹了舉例講解iOS中延遲加載和上拉刷新/下拉加載的實(shí)現(xiàn),語(yǔ)言依然為傳統(tǒng)的Objective-C,需要的朋友可以參考下
    2015-09-09
  • iOS block循環(huán)引用詳解及常見(jiàn)誤區(qū)

    iOS block循環(huán)引用詳解及常見(jiàn)誤區(qū)

    這篇文章主要介紹了iOS block循環(huán)引用詳解和應(yīng)用,常見(jiàn)誤區(qū)詳解,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-08-08
  • iOS開(kāi)發(fā)之widget實(shí)現(xiàn)詳解

    iOS開(kāi)發(fā)之widget實(shí)現(xiàn)詳解

    這篇文章主要為大家詳細(xì)介紹了iOS開(kāi)發(fā)之widget實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • iOS中打包上傳常見(jiàn)的錯(cuò)誤與解決辦法

    iOS中打包上傳常見(jiàn)的錯(cuò)誤與解決辦法

    關(guān)于打包上傳至AppStore,大家都認(rèn)為是最后一步了,其實(shí)到了這里往往會(huì)遇到很多的坑。對(duì)于踩過(guò)的坑我不想再踩第二遍,所以在此將我遇到的所有奇葩問(wèn)題在此做一個(gè)記錄,當(dāng)作對(duì)自己的一個(gè)提醒,同時(shí)也分享給給位,需要的朋友可以參考下。
    2017-03-03
  • iPhoneX 各種適配記錄筆記(超全面)

    iPhoneX 各種適配記錄筆記(超全面)

    iPhone X出來(lái)之后,關(guān)于劉海的各種適配成了程序員們首要考慮的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于iPhoneX 各種適配的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-12-12
  • iOS使用音頻處理框架The Amazing Audio Engine實(shí)現(xiàn)音頻錄制播放

    iOS使用音頻處理框架The Amazing Audio Engine實(shí)現(xiàn)音頻錄制播放

    這篇文章主要為大家詳細(xì)介紹了iOS使用音頻處理框架The Amazing Audio Engine實(shí)現(xiàn)音頻錄制播放,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-04-04
  • iOS開(kāi)發(fā)教程之UIView和UIViewController的生命周期詳解

    iOS開(kāi)發(fā)教程之UIView和UIViewController的生命周期詳解

    UIViewController是IOS程序中的一個(gè)重要組成部分,下面這篇文章主要給大家介紹了關(guān)于iOS開(kāi)發(fā)教程之UIView和UIViewController的生命周期的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2018-04-04

最新評(píng)論