(颤振)在屏幕底部对齐水平列表视图(及相关问题)

Posted

技术标签:

【中文标题】(颤振)在屏幕底部对齐水平列表视图(及相关问题)【英文标题】:(Flutter) Align horizontal listview at bottom of screen (and related issues) 【发布时间】:2021-06-02 17:45:15 【问题描述】:

我的应用程序需要一个可以点击项目的水平列表视图。

下面的粗略布局说明

Container
|_ Stack
    |_ (background content)
    |_ Column (overlay, align end vertically)
          |_ Expand (fill up space above)
          |_ ListView (horizontal)
          |_ SizedBox (padding)

问题

当插入这个ListView(或取消注释)时,Flutter 会抛出以下错误:

======== Exception caught by rendering library =====================================================
The following assertion was thrown during performLayout():
'package:flutter/src/rendering/viewport.dart': Failed assertion: line 1852 pos 16: 'constraints.hasBoundedHeight': is not true.


Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=BUG.md

The relevant error-causing widget was: 
  ListView file:///C:/Users/CybeX/CozyUp/cozyup-mobile-flutter/lib/ui/components/ui_component_partner_selector.dart:76:13
When the exception was thrown, this was the stack: 
#2      RenderShrinkWrappingViewport.performLayout (package:flutter/src/rendering/viewport.dart:1852:16)
#3      RenderObject.layout (package:flutter/src/rendering/object.dart:1777:7)
#4      RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:113:14)
#5      RenderObject.layout (package:flutter/src/rendering/object.dart:1777:7)
#6      RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:113:14)
...
The following RenderObject was being processed when the exception was fired: RenderShrinkWrappingViewport#400d9 relayoutBoundary=up16 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
...  needs compositing
...  parentData: <none> (can use size)
...  constraints: BoxConstraints(0.0<=w<=395.4, 0.0<=h<=Infinity)
...  size: MISSING
...  axisDirection: right
...  crossAxisDirection: down
...  offset: ScrollPositionWithSingleContext#72596(offset: 0.0, range: null..null, viewport: null, ScrollableState, ClampingScrollPhysics -> RangeMaintainingScrollPhysics, IdleScrollActivity#601d0, ScrollDirection.idle)
RenderObject: RenderShrinkWrappingViewport#400d9 relayoutBoundary=up16 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE

我尝试将ListView 包装在Expanded 中,但这会将其重新定位在屏幕中间,正如建议的here 那样。我尝试按照here 的建议添加扩展顶部,但 Flutter 不喜欢这样。

fwiw,也尝试过ShrinkWrap(其实并没有期待太多 - 也没有帮助)

MVCE

import 'package:flutter/material.dart';

void main() async 
  runApp(MaterialApp(home: MyApp()));


class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    List<String> imgSrc = [
      "https://randomuser.me/api/portraits/women/84.jpg",
          "https://randomuser.me/api/portraits/men/82.jpg",
          "https://randomuser.me/api/portraits/women/11.jpg",
          "https://randomuser.me/api/portraits/women/61.jpg",
          "https://randomuser.me/api/portraits/men/1.jpg",
    ];

    List<Widget> _listviewItems() 
      return imgSrc
          .map((e) => Image.network(e))
          .map((e) => Image(
                image: e.image,
                fit: BoxFit.scaleDown,
                width: 64,
              ))
          .toList();
    

    Widget w = Container(
        child: Stack(
      children: [
        // Center(
        //   child: cards.first,
        // ),
        Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            Expanded(
                child: ListView(
              padding: const EdgeInsets.all(0.0),
              scrollDirection: Axis.horizontal,
              children: _listviewItems(),
            )),
            SizedBox(
              height: 8,
            )
          ],
        )
      ],
    ));

    return Scaffold(
      body: Container(width: double.infinity, child: w),
    );
  

上图是ListView包裹在Expanded中,即

   Column(
      mainAxisAlignment: MainAxisAlignment.end,
      children: [
        Expanded(
            child: Container()
        ),
        ListView(
          padding: const EdgeInsets.all(0.0),
          scrollDirection: Axis.horizontal,
          children: _listviewItems(),
        ),
        SizedBox(
          height: 8,
        )
      ],
    )

问题:

如何将水平列表视图对齐到屏幕底部以允许水平滚动(如上图所示)?

fyi:我添加了shrinkWrap: true 并将Column(在堆栈内)包装在Extended 内(如建议的here)导致错误导致我here 这表明相反 - 非常令人沮丧!

【问题讨论】:

【参考方案1】:

用容器包装你的列表视图,并像这样为容器赋予高度

Container(
 height:50,
  ListView(
      padding: const EdgeInsets.all(0.0),
      scrollDirection: Axis.horizontal,
      children: _listviewItems(),
    ),
),

【讨论】:

我正在避免使用静态尺寸 - 这不是一个好主意 但没有其他选择,我尝试了自己,但这是唯一的选择【参考方案2】:

您可以在下面复制粘贴运行 2 个完整代码 解决方案一:你可以使用bottomSheet 代码sn-p

return Scaffold(
      appBar: ...
      body: ...
      bottomSheet: SizedBox(height: 64, child: listview()),
    );

解决方案 2:用 AlignSizedBox 包裹 ListView 代码sn-p

Expanded(
        child: Align(
      alignment: Alignment.bottomCenter,
      child: SizedBox(
        height: 64,
        child: ListView(
          padding: const EdgeInsets.all(0.0),
          scrollDirection: Axis.horizontal,
          children: _listviewItems(),
        ),

工作演示

解决方案1的完整代码

import 'package:flutter/material.dart';

void main() async 
  runApp(MaterialApp(home: MyApp()));


class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    List<String> imgSrc = [
      "https://randomuser.me/api/portraits/women/84.jpg",
      "https://randomuser.me/api/portraits/men/82.jpg",
      "https://randomuser.me/api/portraits/women/11.jpg",
      "https://randomuser.me/api/portraits/women/61.jpg",
      "https://randomuser.me/api/portraits/men/1.jpg",
      "https://randomuser.me/api/portraits/men/2.jpg",
      "https://randomuser.me/api/portraits/men/3.jpg",
      "https://randomuser.me/api/portraits/men/4.jpg",
      "https://randomuser.me/api/portraits/men/5.jpg",
      "https://randomuser.me/api/portraits/men/6.jpg",
      "https://randomuser.me/api/portraits/men/7.jpg",
    ];

    Widget listview() 
      List<Widget> imgList = imgSrc
          .map((e) => Image.network(e))
          .map((e) => Image(
                image: e.image,
                fit: BoxFit.scaleDown,
                width: 64,
              ))
          .toList();

      return ListView(
        scrollDirection: Axis.horizontal,
        children: imgList,
      );
    

    return Scaffold(
      appBar: AppBar(title: const Text('AppBar Demo')),
      body: Center(
        child: Container(
            //width: double.infinity,
            child: Text("other content")),
      ),
      bottomSheet: SizedBox(height: 64, child: listview()),
    );
  

解决方案2的完整代码

import 'package:flutter/material.dart';

void main() async 
  runApp(MaterialApp(home: MyApp()));


class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    List<String> imgSrc = [
      "https://randomuser.me/api/portraits/women/84.jpg",
      "https://randomuser.me/api/portraits/men/82.jpg",
      "https://randomuser.me/api/portraits/women/11.jpg",
      "https://randomuser.me/api/portraits/women/61.jpg",
      "https://randomuser.me/api/portraits/men/1.jpg",
      "https://randomuser.me/api/portraits/men/2.jpg",
      "https://randomuser.me/api/portraits/men/3.jpg",
      "https://randomuser.me/api/portraits/men/4.jpg",
      "https://randomuser.me/api/portraits/men/5.jpg",
      "https://randomuser.me/api/portraits/men/6.jpg",
      "https://randomuser.me/api/portraits/men/7.jpg",
    ];

    List<Widget> _listviewItems() 
      return imgSrc
          .map((e) => Image.network(e))
          .map((e) => Image(
                image: e.image,
                fit: BoxFit.scaleDown,
                width: 64,
              ))
          .toList();
    

    Widget w = Container(
        child: Stack(
      children: [
        // Center(
        //   child: cards.first,
        // ),
        Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            Expanded(
                child: Align(
              alignment: Alignment.bottomCenter,
              child: SizedBox(
                height: 64,
                child: ListView(
                  padding: const EdgeInsets.all(0.0),
                  scrollDirection: Axis.horizontal,
                  children: _listviewItems(),
                ),
              ),
            )),
            SizedBox(
              height: 8,
            )
          ],
        )
      ],
    ));

    return Scaffold(
      body: Container(width: double.infinity, child: w),
    );
  

【讨论】:

以上是关于(颤振)在屏幕底部对齐水平列表视图(及相关问题)的主要内容,如果未能解决你的问题,请参考以下文章

如何在屏幕底部对齐视图,但也在相对布局中的其他视图下方对齐

如何使用自动布局将图像彼此水平对齐

如何在水平 UIStackView 中对齐两个标签的底部?

图像视图autolayout从水平边缘固定到60pt,但自动刻度高度?

Android:如何对齐底部的按钮和上方的列表视图?

使用 Autolayout 将 UILabel 沿其父视图的底部对齐