Flutter SingleChildScrollView嵌套Column的滾動(dòng)問(wèn)題

我們有一個(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容