我试图在 Scrollview 中嵌套一个 tabview,但找不到完成任务的好方法。
如下图所示:
所需的功能是拥有一个普通的可滚动页面,其中一个条是具有不同大小(和动态调整大小)选项卡的选项卡视图。
不幸的是,尽管查看了几个资源和颤振文档,我还没有遇到任何好的解决方案。
这是我尝试过的:
SingleChildScrollView
带有子列,TabBarView 包装在 IntrinsicHeight 小部件中(未绑定约束)CustomScrollView
变体,TabBarView 用 a 包裹,SliverFillRemaining
页眉和页脚分别用SliverToBoxAdapter
. 在所有情况下,如果内容更小,则内容被迫扩展到视口的完整大小(就像使用SliverFillViewport
视口分数为 1.0 的 Sliver),或者如果更大,则在空间内创建嵌套滚动/溢出(见下文)- 如果 TabBarView 的子项是可滚动的小部件,则带有标签栏的条子的高度等于 ViewPort (1.0),并且任何剩余空间都是空的。
- 如果子项不可滚动,则它们会被强制扩展以适应较小的大小,或者如果较大则给出溢出错误。
NestedScrollView
最接近,但仍然受到先前实现的不良影响(参见下面的代码示例)- 各种其他非正统方法(例如删除 TabBarView 并尝试
AnimatedSwitcher
与 TabBar 上的侦听器结合使用以在“选项卡”之间设置动画,但这不是可滑动的,并且动画卡顿并且切换的小部件重叠)
迄今为止“最佳”实现的代码如下所示,但并不理想。
有谁知道有什么方法可以做到这一点?
先感谢您。
// best (more "Least-bad") solution code
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
routes: {
'root': (context) => const Scaffold(
body: ExamplePage(),
),
},
initialRoute: 'root',
);
}
}
class ExamplePage extends StatefulWidget {
const ExamplePage({
Key? key,
}) : super(key: key);
@override
State<ExamplePage> createState() => _ExamplePageState();
}
class _ExamplePageState extends State<ExamplePage>
with TickerProviderStateMixin {
late TabController tabController;
@override
void initState() {
super.initState();
tabController = TabController(length: 2, vsync: this);
tabController.addListener(() {
setState(() {});
});
}
@override
void dispose() {
tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Colors.grey[100],
appBar: AppBar(),
body: NestedScrollView(
floatHeaderSlivers: false,
physics: const AlwaysScrollableScrollPhysics(),
headerSliverBuilder: (BuildContext context, bool value) => [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(
left: 16.0,
right: 16.0,
bottom: 24.0,
top: 32.0,
),
child: Column(
children: [
// TODO: Add scan tab thing
Container(
height: 94.0,
width: double.infinity,
color: Colors.blueGrey,
alignment: Alignment.center,
child: Text('A widget with information'),
),
const SizedBox(height: 24.0),
GenaricTabBar(
controller: tabController,
tabStrings: const [
'Tab 1',
'Tab 2',
],
),
],
),
),
),
],
body: CustomScrollView(
slivers: [
SliverFillRemaining(
child: TabBarView(
physics: const AlwaysScrollableScrollPhysics(),
controller: tabController,
children: [
// Packaging Parts
SingleChildScrollView(
child: Container(
height: 200,
color: Colors.black,
),
),
// Symbols
SingleChildScrollView(
child: Column(
children: [
Container(
color: Colors.red,
height: 200.0,
),
Container(
color: Colors.orange,
height: 200.0,
),
Container(
color: Colors.amber,
height: 200.0,
),
Container(
color: Colors.green,
height: 200.0,
),
Container(
color: Colors.blue,
height: 200.0,
),
Container(
color: Colors.purple,
height: 200.0,
),
],
),
),
],
),
),
SliverToBoxAdapter(
child: ElevatedButton(
child: Text('Button'),
onPressed: () => print('pressed'),
),
),
],
),
),
);
}
class GenaricTabBar extends StatelessWidget {
final TabController? controller;
final List<String> tabStrings;
const GenaricTabBar({
Key? key,
this.controller,
required this.tabStrings,
}) : super(key: key);
@override
Widget build(BuildContext context) => Container(
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(8.0),
),
padding: const EdgeInsets.all(4.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// if want tab-bar, uncomment
TabBar(
controller: controller,
indicator: ShapeDecoration.fromBoxDecoration(
BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
color: Colors.white,
),
),
tabs: tabStrings
.map((String s) => _GenaricTab(tabString: s))
.toList(),
),
],
),
);
}
class _GenaricTab extends StatelessWidget {
final String tabString;
const _GenaricTab({
Key? key,
required this.tabString,
}) : super(key: key);
@override
Widget build(BuildContext context) => Container(
child: Text(
tabString,
style: const TextStyle(
color: Colors.black,
),
),
height: 32.0,
alignment: Alignment.center,
);
}
以上适用于 Dartpad (dartpad.dev),不需要任何外部库