時(shí)間:2019年06月04日17:09:41
作者:CrazyQ1
轉(zhuǎn)載請(qǐng)注名
本人來(lái)自:http://www.flutterj.com/
做企業(yè)項(xiàng)目遇到了個(gè)坑,
那這個(gè)坑是怎么遇到的呢诗力,剛開(kāi)始是已經(jīng)做好了商品詳情頁(yè):
詳情頁(yè)面用的是NestedScrollView組件,輪播圖那一塊用的是SliverAppBar膜廊,
也就是寫在NestedScrollView的頭部,然后下面的都是在身體部分了淫茵,
身體部分是可以滑動(dòng)的爪瓜,剛開(kāi)始是沒(méi)任何問(wèn)題,正吵妆瘢滑動(dòng)運(yùn)行铆铆,
但是來(lái)了這個(gè)需求:
是在商品詳情加個(gè)tabbar,然后我就加在SliverAppBar里面的bottom內(nèi)個(gè)丹喻,
加上去顯示也是沒(méi)什么問(wèn)題薄货,但是錨點(diǎn)這個(gè)需求實(shí)現(xiàn)的時(shí)候就來(lái)了問(wèn)題了。
大家都知道碍论,想要錨點(diǎn)(jumpTo到指定位置)谅猾,嘚讓他的body也加個(gè)控制器啊,
然后我就把之前給的滾動(dòng)組件
new SingleChildScrollView(
? child: new Column(children: widget.widgets),
);
改成了
new ListView(children: widget.widgets);
雖然SingleChildScrollView也是可以加控制器并且jumpTo的鳍悠,
但是我感覺(jué)用ListView比較舒服税娜,代碼也比較簡(jiǎn)潔,所以就用這個(gè)藏研,
但是用哪個(gè)實(shí)現(xiàn)的效果都是差不多的敬矩。
然鵝
驚人的一幕就出現(xiàn)了。
NestedScrollView的頭部?jī)?nèi)容完全固定蠢挡,滑動(dòng)body部分是不能控制到頭部的弧岳,
但是滑動(dòng)頭部就是可以控制頭部,
也就是頭部和身體部分 分開(kāi)了袒哥。
這是為什么呢缩筛?
因?yàn)镹estedScrollView是有內(nèi)外兩個(gè)控制器的:
out控制header,inner控制body堡称。只有當(dāng)out不能滾動(dòng)了才會(huì)滾動(dòng)inner
body不寫控制器就沒(méi)事,寫了就出現(xiàn)這種情況瞎抛,
而且我去測(cè)試了下打印控制器最大滾動(dòng)位置發(fā)現(xiàn)只有300左右,
也就是只能打印出頭部的却紧,
print(_C.position.maxScrollExtent);
那我要怎么去實(shí)現(xiàn)這個(gè)功能啊桐臊,只能在輪播圖內(nèi)跳來(lái)跳去,
難道是貧窮限制了我的想象嗎晓殊?
頭部固定解決方案:(不是唯一的)
既然都說(shuō)了是有內(nèi)外兩個(gè)控制器那我們一定有辦法來(lái)獲取并使用他的內(nèi)部控制器断凶,
第一步:(嘗試封裝body為有狀態(tài)類來(lái)從context中取到內(nèi)控制器)
@override
? Widget build(BuildContext context) {
? ? return new Scaffold(
? ? ? body: new NestedScrollView(
? ? ? ? ? controller: _ctl,
? ? ? ? ? headerSliverBuilder: _sliverBuilder,
? ? ? ? ? body: new BodyView(widget.widgets, type)),
? ? );
? }
BodyView就是我們封裝的,
class BodyView extends StatefulWidget {
? final List<Widget> widgets;
? final int type;
? BodyView(this.widgets, this.type);
? @override
? _BodyViewState createState() => _BodyViewState();
}
class _BodyViewState extends State<BodyView> {
? @override
? Widget build(BuildContext context) {
? ? return new SingleChildScrollView(
? ? ? child: new Column(children: widget.widgets),
? ? );
? }
}
第二步:(type是干啥的先不用管)
class BodyView extends StatefulWidget {
? ...
}
class _BodyViewState extends State<BodyView> {
? Type typeOf<T>() => T;
? ScrollController _innerC;
? @override
? void initState() {
? ? super.initState();
? ? PrimaryScrollController primaryScrollController =
? ? ? ? context.ancestorWidgetOfExactType(typeOf<PrimaryScrollController>());
? ? _innerC = primaryScrollController.controller;
? }
? @override
? Widget build(BuildContext context) {
? ? ...
? }
}
我們定義了一個(gè)類型和控制器巫俺,然后再初始化的時(shí)候?qū)懥艘粋€(gè)主控制器认烁,
主控制器的值是從上下文的父類取的類型,然后typeOf的泛型就是我們寫的
主控制器,那么內(nèi)控制器就是等于我們?nèi)〉降倪@個(gè)控制器却嗡,
頭部固定問(wèn)題就完美解決了
只要能取到舶沛,就算不用也是可以的
當(dāng)然也可以直接使用:
@override
Widget build(BuildContext context) {
? _actions(widget.type);
? return PrimaryScrollController(controller: _innerC, child: new SingleChildScrollView(
? ? child: new Column(children: widget.widgets),
? ));
}
這個(gè)都無(wú)所謂的。
但是我們發(fā)現(xiàn)兩個(gè)控制器開(kāi)始分開(kāi)的窗价,打印外控制器最大滾動(dòng)還是300左右梭伐,
但是打印內(nèi)控制器最大滾動(dòng)位置是body的全部梭稚,2k左右,
那么我這個(gè)需求還有沒(méi)有解決方案了?
當(dāng)然是有的:
點(diǎn)擊錨點(diǎn)跳轉(zhuǎn)解決方案
第一步(直接使用外部控制器jumpTo)
@override
void initState() {
? super.initState();
? tabs = ['商品', '評(píng)價(jià)', '詳情'];
? _tabC = new TabController(length: tabs.length, vsync: this);
? _tabC.addListener(() => _onTabChanged());
}
_onTabChanged() {
? setState(() {
? ? switch (_tabC.index) {
? ? ? case 0:
? ? ? ? _ctl.jumpTo(0.1);
? ? ? ? type = 0;
? ? ? ? break;
? ? ? case 1:
? ? ? ? type = 1;
? ? ? ? break;
? ? ? case 2:
? ? ? ? type = 2;
? ? ? ? break;
? ? }
? });
}
_tabC就是外部控制器朴沿,在初始化的時(shí)候監(jiān)聽(tīng)tabbar是否被點(diǎn)擊烁挟,
如果被點(diǎn)擊的話直接寫個(gè)tab改變的方法愚隧,tabbar的三個(gè)Bar分別是0个少,1,2否灾,
所以我們也接收一個(gè)0卖擅,1,2墨技,來(lái)處理惩阶,
然后直接給它jumpTo跳轉(zhuǎn),然后那個(gè)type就是我們的BodyView接收的
具體有什么用呢扣汪?
class BodyView extends StatefulWidget {
...
}
class _BodyViewState extends State<BodyView> {
? ...
? _actions(int type) {
? ? setState(() {
? ? ? _binding.addPostFrameCallback((callback) {
? ? ? ? switch (type) {
? ? ? ? ? case 1:
? ? ? ? ? ? _innerC.jumpTo(1000);
? ? ? ? ? ? print(_innerC.position.maxScrollExtent);
? ? ? ? ? ? break;
? ? ? ? ? case 2:
? ? ? ? ? ? _innerC.jumpTo(2000);
? ? ? ? ? ? break;
? ? ? ? }
? ? ? });
? ? });
? }
? @override
? void initState() {
? ? ...
? }
? @override
? Widget build(BuildContext context) {
? ? _actions(widget.type);
? ? ...
? }
}
我們可以看到断楷,這邊也是監(jiān)聽(tīng)接收的int類型,
如果監(jiān)聽(tīng)到傳過(guò)來(lái)的是0的話就調(diào)到我們的頂部崭别,(heard控制器控制)
如果監(jiān)聽(tīng)到傳過(guò)來(lái)的是1的話就調(diào)到我們想要到的評(píng)論的位置冬筒。
如果監(jiān)聽(tīng)到傳過(guò)來(lái)的是2的話就跳到我們想要的商品詳情的位置。
Position為null的解決方案
當(dāng)我以為這樣就沒(méi)問(wèn)題的時(shí)候發(fā)現(xiàn)又出現(xiàn)了一個(gè)錯(cuò)誤茅主,
真的是坑一個(gè)接著一個(gè)啊舞痰,
解決方案為:
調(diào)用第一幀繪制完畢之后再執(zhí)行jumpTo
具體:
class BodyView extends StatefulWidget {
? ? ...
}
class _BodyViewState extends State<BodyView> {
? WidgetsBinding _binding = WidgetsBinding.instance;
? _actions(int type) {
? ? setState(() {
? ? ? _binding.addPostFrameCallback((callback) {
? ? ? ? ...
? ? });
? }
}
我們寫了一個(gè)小部件綁定的東西,讓他能監(jiān)聽(tīng)第一幀是否繪制完畢诀姚,
繪制完畢之后再執(zhí)行jumpTo
這樣就只差獲取評(píng)論和商品詳情的組件位置然后傳入具體的Offset就完美執(zhí)行了响牛,
因?yàn)闀r(shí)間關(guān)系就到這了,任何問(wèn)題可以加我微信:zonggeyl_com來(lái)問(wèn)我赫段。
接下來(lái)我把我這個(gè)文件的整體代碼發(fā)出來(lái)呀打,能看的懂的可以看一下,
直接運(yùn)行肯定是不能運(yùn)行的糯笙,因?yàn)槔锩嬲{(diào)用的資源文件和封裝你們都沒(méi)有贬丛,
要懂查看和使用,
import 'package:flutter/material.dart';
class SliverAppBarPage extends StatefulWidget {
? SliverAppBarPage({
? ? this.widgets,
? ? this.headerView,
? ? this.height = 200,
? ? this.background,
? });
? final List<Widget> widgets;
? final Widget headerView;
? final Widget background;
? final double height;
? @override
? State<StatefulWidget> createState() => new SliverAppBarPageState();
}
class SliverAppBarPageState extends State<SliverAppBarPage>
? ? with TickerProviderStateMixin {
? TabController _tabC;
? ScrollController _ctl = new ScrollController();
? int type;
? List tabs;
? WidgetsBinding _binding = WidgetsBinding.instance;
? @override
? void initState() {
? ? super.initState();
? ? tabs = ['商品', '評(píng)價(jià)', '詳情'];
? ? _tabC = new TabController(length: tabs.length, vsync: this);
? ? _tabC.addListener(() => _onTabChanged());
? }
? _onTabChanged() {
? ? setState(() {
? ? ? switch (_tabC.index) {
? ? ? ? case 0:
? ? ? ? ? _binding.addPostFrameCallback((callback) => _ctl.jumpTo(0.1));
? ? ? ? ? type = 0;
? ? ? ? ? break;
? ? ? ? case 1:
? ? ? ? ? type = 1;
? ? ? ? ? break;
? ? ? ? case 2:
? ? ? ? ? type = 2;
? ? ? ? ? break;
? ? ? }
? ? });
? }
? List<Widget> _sliverBuilder(BuildContext context, bool innerBoxIsScrolled) {
? ? return <Widget>[
? ? ? new SliverAppBar(
? ? ? ? centerTitle: true,
? ? ? ? expandedHeight: widget.height,
? ? ? ? floating: false,
? ? ? ? pinned: true,
? ? ? ? backgroundColor: Colors.white,
? ? ? ? elevation: 0,
? ? ? ? brightness: Brightness.light,
? ? ? ? leading: new InkWell(
? ? ? ? ? child: innerBoxIsScrolled
? ? ? ? ? ? ? ? new Container(
? ? ? ? ? ? ? ? ? width: 15,
? ? ? ? ? ? ? ? ? height: 20.0,
? ? ? ? ? ? ? ? ? child: new Image.asset('assets/images/nav_ic_back.webp',
? ? ? ? ? ? ? ? ? ? ? color: innerBoxIsScrolled ? mainFontColor : Colors.white),
? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? : new Container(
? ? ? ? ? ? ? ? ? padding: EdgeInsets.only(left: 10.0),
? ? ? ? ? ? ? ? ? alignment: Alignment.center,
? ? ? ? ? ? ? ? ? child: new Container(
? ? ? ? ? ? ? ? ? ? height: 35,
? ? ? ? ? ? ? ? ? ? width: 35,
? ? ? ? ? ? ? ? ? ? decoration: BoxDecoration(
? ? ? ? ? ? ? ? ? ? ? ? color: Color.fromRGBO(0, 0, 0, 0.2),
? ? ? ? ? ? ? ? ? ? ? ? borderRadius: BorderRadius.circular(17.5)),
? ? ? ? ? ? ? ? ? ? child: new Image.asset('assets/images/nav_ic_back.webp',
? ? ? ? ? ? ? ? ? ? ? ? color:
? ? ? ? ? ? ? ? ? ? ? ? ? ? innerBoxIsScrolled ? mainFontColor : Colors.white),
? ? ? ? ? ? ? ? ? ),
? ? ? ? ? ? ? ? ),
? ? ? ? ? onTap: () => Navigator.pop(context),
? ? ? ? ),
? ? ? ? title: new Text(
? ? ? ? ? innerBoxIsScrolled ? '商品詳情' : '',
? ? ? ? ? style: TextStyle(color: Color(0xff000000), fontSize: 19.0),
? ? ? ? ),
? ? ? ? bottom: innerBoxIsScrolled
? ? ? ? ? ? ? new PreferredSize(
? ? ? ? ? ? ? ? child: new Container(
? ? ? ? ? ? ? ? ? padding: EdgeInsets.symmetric(horizontal: 80.0),
? ? ? ? ? ? ? ? ? child: new TabBar(
? ? ? ? ? ? ? ? ? ? ? controller: _tabC,
? ? ? ? ? ? ? ? ? ? ? indicatorSize: TabBarIndicatorSize.label,
? ? ? ? ? ? ? ? ? ? ? labelColor: Color(0xffFF4F73),
? ? ? ? ? ? ? ? ? ? ? indicatorColor: Color(0xffFF4F73),
? ? ? ? ? ? ? ? ? ? ? unselectedLabelColor: Color(0xff000000),
? ? ? ? ? ? ? ? ? ? ? labelStyle: new TextStyle(fontSize: 14.0),
? ? ? ? ? ? ? ? ? ? ? labelPadding: EdgeInsets.only(bottom: 20),
? ? ? ? ? ? ? ? ? ? ? indicatorPadding: EdgeInsets.only(
? ? ? ? ? ? ? ? ? ? ? ? ? bottom: 15, top: 10, left: 5, right: 5.0),
? ? ? ? ? ? ? ? ? ? ? tabs: tabs.map((item) => new Text('$item')).toList()),
? ? ? ? ? ? ? ? ),
? ? ? ? ? ? ? ? preferredSize: Size(30, 50))
? ? ? ? ? ? : null,
? ? ? ? actions: <Widget>[],
? ? ? ? flexibleSpace: new FlexibleSpaceBar(
? ? ? ? ? ? centerTitle: true,
? ? ? ? ? ? title: widget.headerView,
? ? ? ? ? ? background: widget.background),
? ? ? ),
? ? ];
? }
? @override
? Widget build(BuildContext context) {
? ? return new Scaffold(
? ? ? body: new NestedScrollView(
? ? ? ? ? controller: _ctl,
? ? ? ? ? headerSliverBuilder: _sliverBuilder,
//? ? ? ? body: new SingleChildScrollView(
//? ? ? ? ? controller: _ctl,
//? ? ? ? ? ? child: new Column(children: widget.widgets)),
//? ? ? ),
? ? ? ? ? body: new BodyView(widget.widgets, type)),
? ? );
? }
}
class BodyView extends StatefulWidget {
? final List<Widget> widgets;
? final int type;
? BodyView(this.widgets, this.type);
? @override
? _BodyViewState createState() => _BodyViewState();
}
class _BodyViewState extends State<BodyView> {
? Type typeOf<T>() => T;
? ScrollController _innerC;
? WidgetsBinding _binding = WidgetsBinding.instance;
? _actions(int type) {
? ? setState(() {
? ? ? _binding.addPostFrameCallback((callback) {
? ? ? ? switch (type) {
? ? ? ? ? case 1:
? ? ? ? ? ? _innerC.jumpTo(1000);
? ? ? ? ? ? print(_innerC.position.maxScrollExtent);
? ? ? ? ? ? break;
? ? ? ? ? case 2:
? ? ? ? ? ? _innerC.jumpTo(2000);
? ? ? ? ? ? break;
? ? ? ? }
? ? ? });
? ? });
? }
? @override
? void initState() {
? ? super.initState();
? ? PrimaryScrollController primaryScrollController =
? ? ? ? context.ancestorWidgetOfExactType(typeOf<PrimaryScrollController>());
? ? _innerC = primaryScrollController.controller;
? }
? @override
? Widget build(BuildContext context) {
? ? _actions(widget.type);
? ? return new SingleChildScrollView(
? ? ? child: new Column(children: widget.widgets),
? ? );
? }
}
關(guān)注公眾號(hào)“Flutter前線”给涕,各種Flutter項(xiàng)目實(shí)戰(zhàn)經(jīng)驗(yàn)技巧豺憔,干活知識(shí)额获,F(xiàn)lutter面試題答案,等你來(lái)領(lǐng)取焕阿。