Flutter-自適用高度PageView

Flutter-自適應(yīng)高度的 PageView

需求

在 Flutter 中,PageView 是一個(gè)非常常用的組件袒餐,能夠?qū)崿F(xiàn)多個(gè)頁面的滑動(dòng)切換疾渣。然而汛骂,默認(rèn)的 PageView 高度是固定的,這在展示不同高度的頁面時(shí)溯街,可能會(huì)導(dǎo)致不必要的空白或內(nèi)容裁剪問題诱桂。為了使 PageView 能夠根據(jù)每個(gè)頁面的內(nèi)容高度動(dòng)態(tài)調(diào)整,我們需要一個(gè)自適應(yīng)高度的 PageView 實(shí)現(xiàn)呈昔。

效果

Screen_recording_20240818_122505 (1) (2).gif

本方案的 PageView 可以根據(jù)每個(gè)頁面內(nèi)容的高度自動(dòng)調(diào)整挥等,保證每個(gè)頁面的內(nèi)容在其實(shí)際高度內(nèi)完整顯示,并且在頁面滑動(dòng)時(shí)堤尾,使用平滑的過渡效果肝劲。具體來說:

  • 每個(gè)頁面的內(nèi)容高度不同,PageView 能夠動(dòng)態(tài)調(diào)整高度郭宝。
  • 頁面切換時(shí)辞槐,高度變化平滑,不會(huì)造成突兀的視覺效果粘室。

實(shí)現(xiàn)思路

1. 測量每個(gè)頁面的高度

首先榄檬,我們需要為每個(gè)頁面添加一個(gè)高度測量的機(jī)制,測量頁面內(nèi)容的高度衔统。在 Flutter 中鹿榜,我們可以通過 GlobalKeyRenderBox 獲取每個(gè) Widget 的實(shí)際高度海雪。

2. 動(dòng)態(tài)調(diào)整 PageView 高度

在頁面滑動(dòng)時(shí),我們需要根據(jù)滑動(dòng)的進(jìn)度動(dòng)態(tài)計(jì)算當(dāng)前 PageView 的高度犬缨。這就需要在 PageView 的滑動(dòng)過程中實(shí)時(shí)更新高度喳魏,使得高度隨滑動(dòng)位置逐步過渡。

3. 動(dòng)態(tài)高度過渡

我們可以使用 AnimatedContainer 來平滑過渡 PageView 的高度怀薛,避免切換時(shí)高度變化過于突兀刺彩。通過監(jiān)聽 PageView 的滑動(dòng)狀態(tài),實(shí)時(shí)調(diào)整容器高度枝恋。

實(shí)現(xiàn)代碼

1. 高度測量組件 HeightMeasureWidget

這個(gè)組件負(fù)責(zé)測量每個(gè)頁面的高度创倔,并將測量結(jié)果通過回調(diào)傳遞出去。

import 'package:flutter/material.dart';

class HeightMeasureWidget extends StatefulWidget {
  final Widget child;
  final Function(double height) onHeightChanged;

  const HeightMeasureWidget(
      {super.key, required this.child, required this.onHeightChanged});

  @override
  HeightMeasureState createState() => HeightMeasureState();
}

class HeightMeasureState extends State<HeightMeasureWidget> {
  final GlobalKey _key = GlobalKey();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _measureHeight();
    });
  }

  void _measureHeight() {
    final RenderBox? renderBox =
        _key.currentContext!.findRenderObject() as RenderBox?;
    if (renderBox != null) {
      widget.onHeightChanged(renderBox.size.height);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      key: _key,
      child: widget.child,
    );
  }
}

2. 自適應(yīng)高度的 AutoHeightPageView

這個(gè)組件使用了前面創(chuàng)建的 HeightMeasureWidget 來測量每個(gè)頁面的高度焚碌,然后根據(jù)滑動(dòng)進(jìn)度調(diào)整高度畦攘。

import 'package:flutter/material.dart';
import 'measure_height_widget.dart';

class AutoHeightPageView extends StatefulWidget {
  final List<Widget> children;
  final PageController pageController;

  const AutoHeightPageView({
    Key? key,
    required this.children,
    required this.pageController,
  }) : super(key: key);

  @override
  AutoHeightPageViewState createState() => AutoHeightPageViewState();
}

class AutoHeightPageViewState extends State<AutoHeightPageView> {
  final List<double> _heights = [];
  double _currentHeight = 0;

  @override
  void initState() {
    super.initState();
    widget.pageController.addListener(_updateHeight);
  }

  void _updateHeight() {
    if (widget.pageController.position.haveDimensions && _heights.isNotEmpty) {
      double page = widget.pageController.page ?? 0.0;
      int index = page.floor();
      int nextIndex = (index + 1) < _heights.length ? index + 1 : index;
      double percent = page - index;
      double height =
          _heights[index] + (_heights[nextIndex] - _heights[index]) * percent;
      setState(() {
        _currentHeight = height;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    var isMeasureHeight =
        _heights.length == widget.children.length ? false : true;
    return Column(
      children: [
        Stack(
          children: [
            Visibility(
              visible: isMeasureHeight,
              child: Stack(
                children: widget.children
                    .map((e) => HeightMeasureWidget(
                          child: e,
                          onHeightChanged: (height) {
                            _heights.add(height);
                            if (_heights.length == widget.children.length) {
                              setState(() {
                                _currentHeight = _heights[0];
                              });
                            }
                          },
                        ))
                    .toList(),
              ),
            ),
            if (!isMeasureHeight)
              AnimatedContainer(
                duration: const Duration(milliseconds: 200),
                height: _currentHeight,
                curve: Curves.easeOut,
                child: PageView(
                  controller: widget.pageController,
                  children: widget.children,
                ),
              ),
          ],
        )
      ],
    );
  }

  @override
  void dispose() {
    widget.pageController.dispose();
    super.dispose();
  }
}

3. 使用示例 AutoHeightPageViewPage

該頁面演示了如何使用自適應(yīng)高度的 PageView,通過內(nèi)容高度的動(dòng)態(tài)調(diào)整十电,確保 PageView 始終適應(yīng)當(dāng)前頁面的高度知押。

import 'package:flutter/material.dart';
import 'package:flutter_xy/r.dart';
import 'package:flutter_xy/xydemo/vp/pageview/auto_height_page_view.dart';

class AutoHeightPageViewPage extends StatefulWidget {
  const AutoHeightPageViewPage({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => AutoHeightPageViewState();
}

class AutoHeightPageViewState extends State<AutoHeightPageViewPage> {
  final PageController _pageController = PageController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('自適用高度PageView'),
      ),
      body: Container(
        color: Colors.white,
        child: SingleChildScrollView(
          child: Column(
            children: [
              AutoHeightPageView(
                pageController: _pageController,
                children: [
                  Container(
                    color: Colors.white,
                    child: ListView(
                      shrinkWrap: true,
                      physics: const NeverScrollableScrollPhysics(),
                      children: [
                        Container(
                          color: Colors.red,
                          height: 50,
                          alignment: Alignment.center,
                          child: const Text("第一個(gè)界面"),
                        ),
                        Container(
                          color: Colors.yellow,
                          height: 50,
                          alignment: Alignment.center,
                          child: const Text("第一個(gè)界面"),
                        ),
                        Container(
                          color: Colors.blue,
                          height: 50,
                          alignment: Alignment.center,
                          child: const Text("第一個(gè)界面"),
                        ),
                      ],
                    ),
                  ),
                  Container(
                      color: Colors.green,
                      height: 250,
                      child: const Center(child: Text('第二個(gè)界面'))),
                ],
              ),
              Image.asset(
                R.vp_content_jpg,
                width: MediaQuery.of(context).size.width,
                fit: BoxFit.fill,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

總結(jié)

通過本方案,我們實(shí)現(xiàn)了一個(gè)自適應(yīng)高度的 PageView鹃骂,它能夠根據(jù)每個(gè)頁面的內(nèi)容高度進(jìn)行動(dòng)態(tài)調(diào)整台盯。該實(shí)現(xiàn)依賴于對(duì)每個(gè)頁面內(nèi)容的測量,并使用 AnimatedContainer 來平滑地過渡高度變化畏线。這樣可以確保頁面切換時(shí)静盅,用戶體驗(yàn)更加自然流暢,避免了內(nèi)容被裁剪或空白區(qū)域的出現(xiàn)寝殴。這種自適應(yīng)高度的 PageView 非常適合用于不同頁面內(nèi)容高度不一致的場景蒿叠。

詳情:github.com/yixiaolunhui/flutter_xy

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蚣常,隨后出現(xiàn)的幾起案子市咽,更是在濱河造成了極大的恐慌,老刑警劉巖史隆,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魂务,死亡現(xiàn)場離奇詭異,居然都是意外死亡泌射,警方通過查閱死者的電腦和手機(jī)粘姜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來熔酷,“玉大人孤紧,你說我怎么就攤上這事【苊兀” “怎么了号显?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵臭猜,是天一觀的道長。 經(jīng)常有香客問我押蚤,道長蔑歌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任揽碘,我火速辦了婚禮次屠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雳刺。我一直安慰自己劫灶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布掖桦。 她就那樣靜靜地躺著本昏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枪汪。 梳的紋絲不亂的頭發(fā)上涌穆,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音雀久,去河邊找鬼蒲犬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛岸啡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赫编,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼巡蘸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了擂送?” 一聲冷哼從身側(cè)響起悦荒,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嘹吨,沒想到半個(gè)月后搬味,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蟀拷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年碰纬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片问芬。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悦析,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出此衅,到底是詐尸還是另有隱情强戴,我是刑警寧澤亭螟,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站骑歹,受9級(jí)特大地震影響预烙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜道媚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一扁掸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧衰琐,春花似錦也糊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至狗热,卻和暖如春钞馁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背匿刮。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國打工僧凰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人熟丸。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓训措,卻偏偏與公主長得像,于是被迫代替她去往敵國和親光羞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绩鸣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容