我們有一個(gè)協(xié)議頁(yè)面,上部為內(nèi)容,底部為按鈕,類似下圖。

image.png
我們可能會(huì)這樣實(shí)現(xiàn)
class TwoPage extends StatelessWidget {
const TwoPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Spacer(),
Container(
padding: EdgeInsets.all(10),
width: double.infinity,
child: OutlinedButton(
onPressed: () {},
child: Text('按鈕'),
),
)
],
);
}
}
運(yùn)行代碼后界面和例舉的界面效果一致,看似沒(méi)問(wèn)題,但是當(dāng)我們?cè)黾訁f(xié)議內(nèi)容超過(guò)屏幕高度時(shí)。

image.png
這時(shí)會(huì)發(fā)現(xiàn)內(nèi)容超出屏幕高度了,現(xiàn)在我們的需求是當(dāng)內(nèi)容超出高度時(shí)能連帶按鈕一起滾動(dòng)。有人說(shuō)這不簡(jiǎn)單,在外面加一個(gè)SingleChildScrollView即可。
但是當(dāng)我們?cè)贑olumn外包裹一層SingleChildScrollView時(shí)會(huì)發(fā)現(xiàn)界面不見(jiàn)了,而且會(huì)報(bào)出這樣的錯(cuò)誤
RenderFlex children have non-zero flex but incoming height constraints are unbounded.翻譯過(guò)來(lái)的意思是,在Column里設(shè)置了非0的fex但是外層的容器高度是無(wú)限制的,也就是說(shuō)SingleChildScrollView高度無(wú)法準(zhǔn)確計(jì)算導(dǎo)致報(bào)錯(cuò)。
既然沒(méi)有高度,那么我們就添加一個(gè)IntrinsicHeight去自適應(yīng)高度。添加完IntrinsicHeight后界面能像我們預(yù)想的一樣正常滾動(dòng),但是當(dāng)我們把協(xié)議內(nèi)容刪減到小于屏幕時(shí)問(wèn)題又出現(xiàn)了,按鈕沒(méi)有在底部。

image.png
class TwoPage extends StatelessWidget {
const TwoPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: IntrinsicHeight(
child: Column(
children: [
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Spacer(),
Container(
padding: EdgeInsets.all(10),
width: double.infinity,
child: OutlinedButton(
onPressed: () {},
child: Text('按鈕'),
),
)
],
),
),
);
}
}
真是像極了我們平常寫(xiě)代碼,改完這里那里又出問(wèn)題。
導(dǎo)致這個(gè)的原因是IntrinsicHeight的外層未限制高度,所以IntrinsicHeight子View只會(huì)按實(shí)際大小給,Column的高度為Text加按鈕,Spacer的高度計(jì)算為0。那么要解決這個(gè)問(wèn)題只需要將IntrinsicHeight的最小高度限制成SingleChildScrollView外層widget的高度即可。
下面給出解決辦法
class TwoPage extends StatelessWidget {
const TwoPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: constraints.copyWith(
minHeight: constraints.maxHeight,
maxHeight: double.infinity,
),
child: IntrinsicHeight(
child: Column(
children: [
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Spacer(),
Container(
padding: EdgeInsets.all(10),
width: double.infinity,
child: OutlinedButton(
onPressed: () {},
child: Text('按鈕'),
),
)
],
),
),
),
);
},
);
}
}
LayoutBuilder可以拿到父組件的約束,然后在IntrinsicHeight外層添加約束,最小高度為L(zhǎng)ayoutBuilder父組件約束的高度,最大高度不做限制。
我們還可以將上面的代碼抽成一個(gè)常用組件,傳入一個(gè)contentWidget和一個(gè)bottomWidget,contentWidget為滾動(dòng)的內(nèi)容,默認(rèn)占滿父組件大小,bottomWidget底部顯示,內(nèi)容超過(guò)父組件大小時(shí)跟隨滾動(dòng)。
import 'package:flutter/material.dart';
class ContentContainer extends StatelessWidget {
final Widget? contentWidget;
final Widget? bottomWidget;
final bool isScrollable;
final Clip? childClipBehavior;
const ContentContainer(
{Key? key,
this.contentWidget,
this.bottomWidget,
this.isScrollable = true,
this.childClipBehavior})
: super(key: key);
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return SingleChildScrollView(
clipBehavior: childClipBehavior ?? Clip.hardEdge,
physics: isScrollable ? null : NeverScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: constraints.copyWith(
minHeight: constraints.maxHeight,
maxHeight: double.infinity,
),
child: IntrinsicHeight(
child: Column(
children: <Widget>[
if (contentWidget != null)
Expanded(
child: contentWidget!,
)
else
Spacer(),
if (bottomWidget != null) bottomWidget!,
],
),
),
),
);
},
);
}
}
使用方式
class TwoPage extends StatelessWidget {
const TwoPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ContentContainer(
contentWidget: Column(
children: [
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
Text('協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議協(xié)議'),
],
),
bottomWidget: Container(
padding: EdgeInsets.all(10),
width: double.infinity,
child: OutlinedButton(
onPressed: () {},
child: Text('按鈕'),
),
),
);
}
}
上述組件可以用在任何可能需要滾動(dòng)的界面上,不需要bottomWidget時(shí)不傳參數(shù)即可。
最終效果圖

untitled.gif