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)呈昔。
效果
本方案的 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 中鹿榜,我們可以通過 GlobalKey
和 RenderBox
獲取每個(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