Flutter-自适用高度PageView

简介: 【8月更文挑战第18天】

需求
在 Flutter 中,PageView 是一个非常常用的组件,能够实现多个页面的滑动切换。然而,默认的 PageView 高度是固定的,这在展示不同高度的页面时,可能会导致不必要的空白或内容裁剪问题。为了使 PageView 能够根据每个页面的内容高度动态调整,我们需要一个自适应高度的 PageView 实现。

效果

本方案的 PageView 可以根据每个页面内容的高度自动调整,保证每个页面的内容在其实际高度内完整显示,并且在页面滑动时,使用平滑的过渡效果。具体来说:

每个页面的内容高度不同,PageView 能够动态调整高度。
页面切换时,高度变化平滑,不会造成突兀的视觉效果。
实现思路

  1. 测量每个页面的高度
    首先,我们需要为每个页面添加一个高度测量的机制,测量页面内容的高度。在 Flutter 中,我们可以通过 GlobalKey 和 RenderBox 获取每个 Widget 的实际高度。

  2. 动态调整 PageView 高度
    在页面滑动时,我们需要根据滑动的进度动态计算当前 PageView 的高度。这就需要在 PageView 的滑动过程中实时更新高度,使得高度随滑动位置逐步过渡。

  3. 动态高度过渡
    我们可以使用 AnimatedContainer 来平滑过渡 PageView 的高度,避免切换时高度变化过于突兀。通过监听 PageView 的滑动状态,实时调整容器高度。

实现代码

  1. 高度测量组件 HeightMeasureWidget
    这个组件负责测量每个页面的高度,并将测量结果通过回调传递出去。

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 {
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,
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

  1. 自适应高度的 AutoHeightPageView
    这个组件使用了前面创建的 HeightMeasureWidget 来测量每个页面的高度,然后根据滑动进度调整高度。

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

class AutoHeightPageView extends StatefulWidget {
final List 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 {
final List _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();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

  1. 使用示例 AutoHeightPageViewPage
    该页面演示了如何使用自适应高度的 PageView,通过内容高度的动态调整,确保 PageView 始终适应当前页面的高度。

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 createState() => AutoHeightPageViewState();
}

class AutoHeightPageViewState extends State {
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("第一个界面"),
),
Container(
color: Colors.yellow,
height: 50,
alignment: Alignment.center,
child: const Text("第一个界面"),
),
Container(
color: Colors.blue,
height: 50,
alignment: Alignment.center,
child: const Text("第一个界面"),
),
],
),
),
Container(
color: Colors.green,
height: 250,
child: const Center(child: Text('第二个界面'))),
],
),
Image.asset(
R.vp_content_jpg,
width: MediaQuery.of(context).size.width,
fit: BoxFit.fill,
),
],
),
),
),
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
总结
通过本方案,我们实现了一个自适应高度的 PageView,它能够根据每个页面的内容高度进行动态调整。该实现依赖于对每个页面内容的测量,并使用 AnimatedContainer 来平滑地过渡高度变化。这样可以确保页面切换时,用户体验更加自然流畅,避免了内容被裁剪或空白区域的出现。这种自适应高度的 PageView 非常适合用于不同页面内容高度不一致的场景。

相关文章
|
1月前
|
UED 开发者 容器
Flutter&鸿蒙next 中的 Expanded 和 Flexible 使用技巧详解
在 Flutter 开发中,Expanded 和 Flexible 是两个常用的布局控件,用于管理 UI 布局的空间分配。Expanded 使子组件占据主轴上的所有剩余空间,而 Flexible 允许通过 flex 参数按比例分配空间。掌握两者的区别和使用场景,可以让你在构建复杂 UI 时更加得心应手。
89 1
|
7月前
|
Android开发 iOS开发
Flutter.源码分析 flutter/packages/flutter/lib/src/widgets/scroll_view.dart/ScrollView
Flutter.源码分析 flutter/packages/flutter/lib/src/widgets/scroll_view.dart/ScrollView
79 0
|
7月前
|
索引
Flutter.源码分析.flutter/packages/flutter/lib/src/widgets/scroll_view.dart/GridView
Flutter.源码分析.flutter/packages/flutter/lib/src/widgets/scroll_view.dart/GridView
53 0
|
7月前
Flutter.源码分析.flutter/packages/flutter/lib/src/widgets/scroll_view.dart/ListView
Flutter.源码分析.flutter/packages/flutter/lib/src/widgets/scroll_view.dart/ListView
61 0
重识Flutter 用于解决复杂滑动视窗问题的Slivers - part1
在日常的开发工作中,仅仅使用ListView、ListView.builder等这样的滑动组件就能满足大部分的业务需求,但在碰到较为复杂的滑动页面时,我认为Slivers可以帮你更简单的实现。
重识Flutter  用于解决复杂滑动视窗问题的Slivers - part1
Flutter灵活布局要掌握的两个控件Expanded和Flexible
Flutter灵活布局要掌握的两个控件Expanded和Flexible
Flutter Expanded和Flexible组件
在开发过程中,不能只想到Expanded,其实在很多场景中,Flexible都能很好地解决布局问题。
Flutter Expanded和Flexible组件
|
容器
【布局 widget】 Flutter Padding
Flutter Padding 是用来给 child 在四周增加空白的 widget。实际上 Flutter 中的 margin 也是用 Padding 来实现的。
201 0
|
JSON 安全 数据格式
Flutter(五)之Flutter滚动Widget(下)
列表是移动端经常使用的一种视图展示方式,在Flutter中提供了ListView和GridView。 为了可能展示出更好的效果,我这里提供了一段Json数据,所以我们可以先学习一下Json解析。
405 0
Flutter(五)之Flutter滚动Widget(下)
|
JSON Dart 安全
Flutter(五)之Flutter滚动Widget(上)
列表是移动端经常使用的一种视图展示方式,在Flutter中提供了ListView和GridView。 为了可能展示出更好的效果,我这里提供了一段Json数据,所以我们可以先学习一下Json解析。
232 0
Flutter(五)之Flutter滚动Widget(上)