【问题标题】:How to show/hide widgets programmatically in Flutter如何在 Flutter 中以编程方式显示/隐藏小部件
【发布时间】:2017-11-13 08:40:08
【问题描述】:

在 Android 中,每个 View 子类都有一个 setVisibility() 方法,允许您修改 View 对象的可见性

有 3 种设置可见性的选项:

  • 可见:使 View 在布局内可见
  • 不可见:隐藏View,但留出的空隙相当于View 可见时所占据的空隙
  • 消失:隐藏View,并将其从布局中完全删除。就好像它的 heightwidth0dp

对于 Flutter 中的 Widgets 是否有与上述等价的内容?

快速参考: https://developer.android.com/reference/android/view/View.html#attr_android:visibility

【问题讨论】:

    标签: flutter dart flutter-layout flutter-widget


    【解决方案1】:

    定义:

    不可见:小部件占用屏幕上的物理空间,但对用户不可见。这可以使用Visibility 小部件来实现。

    消失:小部件不占用任何物理空间,完全消失。这可以使用Visibilityifif-else 条件来实现。

    不可见的例子:

    Visibility(
      child: Text("Invisible"),
      maintainSize: true, 
      maintainAnimation: true,
      maintainState: true,
      visible: false, 
    ),
    

    过去的例子:

    Visibility(
      child: Text("Gone"),
      visible: false,
    ),
    

    使用if:

    • 对于一个孩子:

      Column(
        children: <Widget>[
          Text('Good Morning'), // Always visible
          if (wishOnePerson) Text(' Mr ABC'), // Only visible if condition is true
        ],
      ) 
      
    • 对于多个孩子:

      Column(
        children: [
          Text('Good Morning'), // Always visible
          if (wishAll) ... [ // These children are only visible if condition is true
            Text('Mr ABC'),
            Text('Mr DEF'),
            Text('Mr XYZ'),
          ],
        ],
      )
      

    使用if-else:

    • 对于一个孩子:

      Column(
        children: <Widget>[
          // Only one of them is visible based on 'isMorning' condition
          if (isMorning) Text('Good Morning')
          else Text ('Good Evening'),
        ],
      ) 
      
    • 对于多个孩子:

      Column(
        children: [
          // Only one of the children will be shown based on `beforeSunset` condition
          if (beforeSunset) ... [
            Text('Good morning'),
            Text('Good afternoon'),
          ] else ... [
            Text('Good evening'),
            Text('Good night'),
          ],
        ],
      )
      

    【讨论】:

    • 这里如何使用else条件?
    • @Quicklearner 你可以使用if(show) Text('Showing) else Text('Not showing)
    • 对于不理解使用 if-else 的人,有一个示例 codeshare.io/qPLAPA
    【解决方案2】:

    更新:自从写了这个答案,Visibility 被引入并提供了这个问题的最佳解决方案。


    您可以使用Opacityopacity:0.0 来绘制隐藏元素但仍占用空间。

    为了不占用空间,将其替换为空的Container()

    编辑: 要将其包装在 Opacity 对象中,请执行以下操作:

                new Opacity(opacity: 0.0, child: new Padding(
                  padding: const EdgeInsets.only(
                    left: 16.0,
                  ),
                  child: new Icon(pencil, color: CupertinoColors.activeBlue),
                ))
    

    Google Developers 关于不透明度的快速教程:https://youtu.be/9hltevOHQBw

    【讨论】:

    • 谢谢!是的,这不是最干净的方法,但它肯定会达到目的。未来是否有可能将可见性功能与小部件集成?
    • 如果小部件正常响应用户输入,请务必将其包裹在IgnorePointer 中,否则用户仍然可以触发它。
    • 这并不理想,因为小部件仍然存在并且可以响应点击等。请使用 Visibility 小部件查看下面的答案以了解处理此问题的最佳方法。
    • 正如 cmets 上面所说,使用 opacity 会在 renderTree 上渲染 Widget,在某些情况下这不是您想要的。最推荐使用可见性小部件。
    • 使小部件不可见和不透明度为 0 是两件不同的事情。使用不可见的小部件,您仍然可以与之交互,它只是不可见的。可见性小部件允许您在需要时删除小部件。
    【解决方案3】:

    与问题协作并展示将其替换为空 Container() 的示例。

    下面是一个例子:

    import "package:flutter/material.dart";
    
    void main() {
      runApp(new ControlleApp());
    }
    
    class ControlleApp extends StatelessWidget { 
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: "My App",
          home: new HomePage(),
        );
      }
    }
    
    class HomePage extends StatefulWidget {
      @override
      HomePageState createState() => new HomePageState();
    }
    
    class HomePageState extends State<HomePage> {
      bool visibilityTag = false;
      bool visibilityObs = false;
    
      void _changed(bool visibility, String field) {
        setState(() {
          if (field == "tag"){
            visibilityTag = visibility;
          }
          if (field == "obs"){
            visibilityObs = visibility;
          }
        });
      }
    
      @override
      Widget build(BuildContext context){
        return new Scaffold(
          appBar: new AppBar(backgroundColor: new Color(0xFF26C6DA)),
          body: new ListView(
            children: <Widget>[
              new Container(
                margin: new EdgeInsets.all(20.0),
                child: new FlutterLogo(size: 100.0, colors: Colors.blue),
              ),
              new Container(
                margin: new EdgeInsets.only(left: 16.0, right: 16.0),
                child: new Column(
                  children: <Widget>[
                    visibilityObs ? new Row(
                      crossAxisAlignment: CrossAxisAlignment.end,
                      children: <Widget>[
                        new Expanded(
                          flex: 11,
                          child: new TextField(
                            maxLines: 1,
                            style: Theme.of(context).textTheme.title,
                            decoration: new InputDecoration(
                              labelText: "Observation",
                              isDense: true
                            ),
                          ),
                        ),
                        new Expanded(
                          flex: 1,
                          child: new IconButton(
                            color: Colors.grey[400],
                            icon: const Icon(Icons.cancel, size: 22.0,),
                            onPressed: () {
                              _changed(false, "obs");
                            },
                          ),
                        ),
                      ],
                    ) : new Container(),
    
                    visibilityTag ? new Row(
                      crossAxisAlignment: CrossAxisAlignment.end,
                      children: <Widget>[
                        new Expanded(
                          flex: 11,
                          child: new TextField(
                            maxLines: 1,
                            style: Theme.of(context).textTheme.title,
                            decoration: new InputDecoration(
                              labelText: "Tags",
                              isDense: true
                            ),
                          ),
                        ),
                        new Expanded(
                          flex: 1,
                          child: new IconButton(
                            color: Colors.grey[400],
                            icon: const Icon(Icons.cancel, size: 22.0,),
                            onPressed: () {
                              _changed(false, "tag");
                            },
                          ),
                        ),
                      ],
                    ) : new Container(),
                  ],
                )
              ),
              new Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  new InkWell(
                    onTap: () {
                      visibilityObs ? null : _changed(true, "obs");
                    },
                    child: new Container(
                      margin: new EdgeInsets.only(top: 16.0),
                      child: new Column(
                        children: <Widget>[
                          new Icon(Icons.comment, color: visibilityObs ? Colors.grey[400] : Colors.grey[600]),
                          new Container(
                            margin: const EdgeInsets.only(top: 8.0),
                            child: new Text(
                              "Observation",
                              style: new TextStyle(
                                fontSize: 12.0,
                                fontWeight: FontWeight.w400,
                                color: visibilityObs ? Colors.grey[400] : Colors.grey[600],
                              ),
                            ),
                          ),
                        ],
                      ),
                    )
                  ),
                  new SizedBox(width: 24.0),
                  new InkWell(
                    onTap: () {
                      visibilityTag ? null : _changed(true, "tag");
                    },
                    child: new Container(
                      margin: new EdgeInsets.only(top: 16.0),
                      child: new Column(
                        children: <Widget>[
                          new Icon(Icons.local_offer, color: visibilityTag ? Colors.grey[400] : Colors.grey[600]),
                          new Container(
                            margin: const EdgeInsets.only(top: 8.0),
                            child: new Text(
                              "Tags",
                              style: new TextStyle(
                                fontSize: 12.0,
                                fontWeight: FontWeight.w400,
                                color: visibilityTag ? Colors.grey[400] : Colors.grey[600],
                              ),
                            ),
                          ),
                        ],
                      ),
                    )
                  ),
                ],
              )                    
            ],
          )
        );
      }
    }
    

    【讨论】:

    • 这应该是公认的答案。这是“以编程方式显示/隐藏小部件”的正确实现
    • 是的,这绝对应该被接受,因为它使用了 Flutter 的基础支柱,即 setState()... 否则您将如何在 Stateful 小部件中的 Visible/InVisible 之间来回切换! ?.
    • 这个答案不完整。只处理“消失”的部分。关键是有时您只想隐藏/显示一个小部件,而不是一直重新创建它,因为这可能很昂贵。另一个用例是您想要保留布局时。在这种情况下,您需要的是不透明度或可见性。
    • 此代码已被完全弃用,在撰写评论时有一个错误。如果在第 45 行将颜色:更改为 textColor:,代码将起作用。使用“child: new FlutterLogo(size: 100.0, textColor: Colors.blue)”。其他 4 个弃用问题不会阻止代码在此阶段运行。
    【解决方案4】:

    Flutter 现在包含一个 Visibility Widget,您应该使用它来显示/隐藏小部件。该小部件还可以通过更改替换用于在2个小部件之间切换。

    这个小部件可以实现任何可见、不可见、消失等状态。

        Visibility(
          visible: true //Default is true,
          child: Text('Ndini uya uya'),
          //maintainSize: bool. When true this is equivalent to invisible;
          //replacement: Widget. Defaults to Sizedbox.shrink, 0x0
        ),
    

    【讨论】:

      【解决方案5】:

      试试Offstage 小部件

      如果属性offstage:true不占用物理空间且不可见,

      如果属性offstage:false会占用物理空间并且可见

      Offstage(
         offstage: true,
         child: Text("Visible"),
      ),
      

      【讨论】:

      • 对此请注意:Flutter docs 状态,“Offstage 可用于测量小部件的尺寸,而无需将其显示在屏幕上(还)。在不需要时将小部件隐藏在视图中,更愿意将小部件从树中完全移除,而不是让它在 Offstage 子树中保持活动状态。"。
      【解决方案6】:

      您可以使用名为 (Visibility) 的新小部件封装代码中的任何小部件,这是您希望它不可见的小部件最左侧的黄色灯

      例子:假设你想让一行不可见:

      1. 点击灯并选择(Wrap with widget)
      2. 将小部件重命名为可见性
      3. 添加 visible 属性并将其设置为 false
      4. 新创建的widget(Visibility Widget)的Child是那个Widget 你想让它不可见

                  Visibility(
                      visible: false,
                      child: Row(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          SizedBox(
                            width: 10,
                          ),
                          Text("Search",
                            style: TextStyle(fontSize: 20
                            ),),
                        ],
                      ),
                    ),
        

      希望以后能对大家有所帮助

      【讨论】:

        【解决方案7】:
        bool _visible = false;
        
         void _toggle() {
            setState(() {
              _visible = !_visible;
            });
          }
        
        onPressed: _toggle,
        
        Visibility(
                    visible:_visible,
                    child: new Container(
                    child: new  Container(
                      padding: EdgeInsets.fromLTRB(15.0, 0.0, 15.0, 10.0),
                      child: new Material(
                        elevation: 10.0,
                        borderRadius: BorderRadius.circular(25.0),
                        child: new ListTile(
                          leading: new Icon(Icons.search),
                          title: new TextField(
                            controller: controller,
                            decoration: new InputDecoration(
                                hintText: 'Search for brands and products', border: InputBorder.none,),
                            onChanged: onSearchTextChanged,
                          ),
                          trailing: new IconButton(icon: new Icon(Icons.cancel), onPressed: () {
                            controller.clear();
                            onSearchTextChanged('');
                          },),
                        ),
                      ),
                    ),
                  ),
                  ),
        

        【讨论】:

          【解决方案8】:

          更新

          Flutter 现在有一个 Visibility 小部件。要实施您自己的解决方案,请从以下代码开始。


          自己制作一个小部件。

          显示/隐藏

          class ShowWhen extends StatelessWidget {
            final Widget child;
            final bool condition;
            ShowWhen({this.child, this.condition});
          
            @override
            Widget build(BuildContext context) {
              return Opacity(opacity: this.condition ? 1.0 : 0.0, child: this.child);
            }
          }
          

          显示/删除

          class RenderWhen extends StatelessWidget {
            final Widget child;
            final bool condition;
            RenderWhen({this.child, this.show});
          
            @override
            Widget build(BuildContext context) {
              return this.condition ? this.child : Container();
            }
          }
          

          顺便问一下,上面的小部件有没有更好的名字?

          更多阅读

          1. Article 了解如何制作可见性小部件。

          【讨论】:

            【解决方案9】:

            flutter 1.5Dart 2.3 中,可见性消失了,您可以通过在集合中使用 if 语句来设置可见性,而无需使用容器。

            例如

            child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                          Text('This is text one'),
                          if (_isVisible) Text('can be hidden or shown'), // no dummy container/ternary needed
                          Text('This is another text'),
                          RaisedButton(child: Text('show/hide'), onPressed: (){
                              setState(() {
                                _isVisible = !_isVisible; 
                              });
                          },)
            
                      ],
                    )
            

            【讨论】:

            • 这比以前的颤振/飞镖版本中可用的选项好多了。谢谢!
            【解决方案10】:

            初学者也可以试试这个。

            class Visibility extends StatefulWidget {
              @override
              _VisibilityState createState() => _VisibilityState();
            }
            
            class _VisibilityState extends State<Visibility> {
              bool a = true;
              String mText = "Press to hide";
            
              @override
              Widget build(BuildContext context) {
                return new MaterialApp(
                  title: "Visibility",
                  home: new Scaffold(
                      body: new Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          new RaisedButton(
                            onPressed: _visibilitymethod, child: new Text(mText),),
                            a == true ? new Container(
                            width: 300.0,
                            height: 300.0,
                            color: Colors.red,
                          ) : new Container(),
                        ],
                      )
                  ),
                );
              }
            
              void _visibilitymethod() {
                setState(() {
                  if (a) {
                    a = false;
                    mText = "Press to show";
                  } else {
                    a = true;
                    mText = "Press to hide";
                  }
                });
              }
            }
            

            【讨论】:

              【解决方案11】:

              正如@CopsOnRoad 已经强调的那样, 您可以使用可见性小部件。但是,如果你想保持它的状态,例如,如果你想构建一个viewpager,并根据页面使某个按钮出现和消失,你可以这样做

              void checkVisibilityButton() {
                setState(() {
                isVisibileNextBtn = indexPage + 1 < pages.length;
                });
              }    
              
               Stack(children: <Widget>[
                    PageView.builder(
                      itemCount: pages.length,
                      onPageChanged: (index) {
                        indexPage = index;
                        checkVisibilityButton();
                      },
                      itemBuilder: (context, index) {
                        return pages[index];
                      },
                      controller: controller,
                    ),
                    Container(
                      alignment: Alignment.bottomCenter,
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.end,
                        children: <Widget>[
                          Visibility(
                            visible: isVisibileNextBtn,
                            child: "your widget"
                          )
                        ],
                      ),
                    )
                  ]))
              

              【讨论】:

              • visible: isVisibileNextBtn == true ? true : false 可以简化为visible: isVisibileNextBtn
              【解决方案12】:

              恕我直言,在 Flutter 中不需要可见性属性或特殊小部件,因为如果您不需要显示小部件 - 只是不要将其添加到小部件树或用空小部件替换它:

                @override
                Widget build(BuildContext context) {
                  return someFlag ? Text('Here I am') : SizedBox();
                }
              

              我认为 Visibility 小部件存在的原因是因为有很多人问:) 人们习惯于对某些属性控制的元素进行可见性

              【讨论】:

                【解决方案13】:

                在 Flutter 中有很多不同的方法可以实现这一点。在逐一解释之前,我先提供相当于 Android 原生的“不可见”和“消失”的快速解决方案:

                View.INVISIBLE:

                Opacity(
                  opacity: 0.0,
                  child: ...
                )
                

                View.GONE:

                Offstage(
                  child: ...
                )
                

                现在让我们比较一下这些方法和其他方法:

                不透明度

                此小部件将不透明度 (alpha) 设置为您想要的任何值。将其设置为0.0 比将其设置为0.1 稍微不那么明显,所以希望这很容易理解。小部件仍将保持其大小并占据相同的空间,并保持每个状态,包括动画。由于它留下了空隙,用户仍然可以触摸或点击它。 (顺便说一句,如果你不希望人们触摸一个不可见的按钮,你可以用 IgnorePointer 小部件包装它。)

                台下

                这个小部件隐藏了子小部件。您可以将其想象为将小部件放在“屏幕之外”,这样用户就不会看到它。小部件仍然会遍历颤振管道中的所有内容,直到它到达最后的“绘画”阶段,它根本不绘画任何东西。这意味着它将保留所有状态和动画,但不会在屏幕上呈现任何内容。另外,布局时也不占空间,不留空隙,用户自然无法点击。

                可见性

                为了您的方便,这个小部件结合了上述(以及更多)。它具有maintainStatemaintainAnimationmaintainSizemaintainInteractivity 等参数。根据您设置这些属性的方式,它从以下方面决定:

                1. 如果你想保持状态,它会用OpacityOffstage 包裹孩子,这取决于你是否还想保持大小。此外,除非你想maintainInteractivity,否则它还会为你包装一个IgnorePointer,因为点击透明按钮有点奇怪。

                2. 如果你根本不想maintainState,它直接用SizedBox 替换child,所以它完全消失了。您可以使用replacement 属性将空白SizedBox 更改为您想要的任何内容。

                删除小部件

                如果您不需要维护状态等,通常建议将小部件从树中完全删除。例如,您可以使用if (condition) 来决定是否在列表中包含一个小部件,或者使用condition ? child : SizedBox() 直接将其替换为SizedBox。这避免了不必要的计算,并且是最佳性能。

                【讨论】:

                  【解决方案14】:

                  有条件地添加/删除小部件

                  包含/排除一个小部件:

                  if (this.isLuckyTime) TextButton(
                   child: Text('I am feeling lucky')
                  )
                  

                  如果您想让小部件不可见但仍保持其大小,则将其包装到&lt;Visibility&gt; 并设置maintainSize: true。如果它是有状态的并且您需要保持它的状态,那么还要添加maintainState: true

                  动画小部件淡入淡出

                  要使小部件顺利淡入和淡出,您可以使用AnimatedOpacity

                  AnimatedOpacity(
                     opacity: this.isLuckyTime ? 1.0 : 0.0,
                     duration: Duration(milliseconds: 500),
                     child: Text('I am feeling lucky')
                  )
                  

                  特别是对于来自原生 android 的开发人员:可能值得一提的是,您从不显示/隐藏小部件,您在使用或不使用所需的小部件时重绘 UI:

                  ? Introduction to declarative UI
                  ? State Management
                  ? Simple app state management

                  【讨论】:

                    【解决方案15】:
                    class VisibilityExample extends StatefulWidget {
                      const VisibilityExample({Key? key}) : super(key: key);
                    
                      @override
                      _VisibilityExampleState createState() => _VisibilityExampleState();
                    }
                    
                    class _VisibilityExampleState extends State<VisibilityExample> {
                      bool visible = false;
                    
                      @override
                      Widget build(BuildContext context) {
                        return Scaffold(
                          appBar: AppBar(
                            title: const Text('Lines'),
                          ),
                          body: Container(
                            color: Colors.black87,
                            child: Stack(alignment: Alignment.bottomCenter, children: [
                              ListView(
                                shrinkWrap: true,
                                children: [
                                  Container(
                                    height: 200,
                                  ),
                                  InkWell(
                                    onTap: () {},
                                    onHover: (value) {
                                      print(value);
                                      setState(() {
                                        visible = !visible;
                                      });
                                    },
                                    child: Visibility(
                                      maintainSize: true,
                                      maintainAnimation: true,
                                      maintainState: true,
                                      visible: visible,
                                      child: Row(
                                        mainAxisAlignment: MainAxisAlignment.center,
                                        children: [
                                          IconButton(
                                            color: Colors.white54,
                                            icon: const Icon(
                                              Icons.arrow_left_outlined,
                                            ),
                                            onPressed: () {},
                                          ),
                                          const SizedBox(
                                            width: 5,
                                          ),
                                          IconButton(
                                            color: Colors.white54,
                                            icon: const Icon(
                                              Icons.add_circle_outlined,
                                            ),
                                            onPressed: () {},
                                          ),
                                          const SizedBox(
                                            width: 5,
                                          ),
                                          IconButton(
                                            color: Colors.white54,
                                            icon: const Icon(
                                              Icons.remove_circle,
                                            ),
                                            onPressed: () {},
                                          ),
                                          const SizedBox(
                                            width: 5,
                                          ),
                                          IconButton(
                                            color: Colors.white54,
                                            icon: const Icon(
                                              Icons.arrow_right_outlined,
                                            ),
                                            onPressed: () {},
                                          ),
                                          const SizedBox(
                                            width: 5,
                                          ),
                                          IconButton(
                                            color: Colors.white54,
                                            icon: const Icon(Icons.replay_circle_filled_outlined),
                                            onPressed: () {},
                                          ),
                                        ],
                                      ),
                                    ),
                                  ),
                                ],
                              ),
                            ]),
                          ),
                        );
                      }
                    }
                     
                    

                    【讨论】:

                      【解决方案16】:

                      也许你可以像这样使用导航器功能Navigator.of(context).pop();

                      【讨论】:

                        【解决方案17】:

                        一种解决方案是将 tis 小部件颜色属性设置为 Colors.transparent。例如:

                        IconButton(
                            icon: Image.asset("myImage.png",
                                color: Colors.transparent,
                            ),
                            onPressed: () {},
                        ),
                        

                        【讨论】:

                        • 不是一个好的解决方案,因为透明的IconButton 仍然会收到点击并占用空间。请在人们投反对票之前编辑或删除此答案。
                        猜你喜欢
                        • 2023-01-17
                        • 2018-12-29
                        • 1970-01-01
                        • 1970-01-01
                        • 2021-01-17
                        • 2020-10-17
                        • 2011-03-13
                        • 1970-01-01
                        相关资源
                        最近更新 更多