likes
comments
collection
share

Flutter应用程序添加交互性

作者站长头像
站长
· 阅读数 39
通过此篇你会学到什么
· 如何响应点击
· 如何创建自定义小部件
· 无状态和有状态小部件之间的区别。

您如何修改您的应用程序以使其对用户输入做出反应?此篇博客,您将向仅包含非交互式小部件的应用程序添加交互性。具体来说,您将通过创建管理两个无状态小部件的自定义有状态小部件来修改图标,使其可点击。

例如,布局如下图所示:

Flutter应用程序添加交互性

应用程序啥时候次启动时,星星呈纯红色,表示此湖之前已被收藏。星星旁边的数字表示有41人收藏了这个湖。完成本教程后,点击星星会删除其收藏状态,用轮廓替实心信息并减少计数。再次点击收藏湖,画出一颗实心星星并增加计数。

Flutter应用程序添加交互性

为此,您将创建一个包含星号哦和计数的自定义小部件,它们本身就是小哦不见。点击星标会更改两个小部件的状态,因此同一个小部件应该管理这两个小部件。

有状态和无状态的小部件

小部件是有状态和无状态的。如果一个小部件可以改变——例如,当用户与之交互时——它就是有状态的。

无状态小部件永远不会改变。IconIconButtonText是无状态小部件的示例。无状态小部件是StatelessWidget的子类。

有状态小部件是动态的:例如,它可以响应用户交互触发的事件或接收数据时更改其外观。CheckBoxRadioSliderInkWellFormTextField是有状态小部件的示例。有状态的小部件是StatefulWidget的子类。

小部件的状态存储在State对象中,将小部件的状态与其外观分开。状态由可以更改的值组成,例如滑块的当前值或复选框是否被选中。当小部件的状态发生变化时,状态对象调用setState(),告诉框架重绘小部件。

创建有状态的小部件

重点是什么?
· 有状态小部件由两个类实现:一个子类`StatefulWidget`和一个子类`State`。
· 状态类包含小部件的可变状态和小部件的`build()`方法。
· 当小部件的状态发生变化时,状态对象调用`setState()`,告诉框架重绘小部件。

您将创建一个自定义有状态小部件。您将用一个自定义的有状态小部件替换两个无状态小部件(实心红星和旁边的数字计数),该小部件管理包含两个子小部件的行:一个IconButtonText

实现自定义有状态小部件需要创建两个类:

  • 定义小部件的StatefulWidget的子类。
  • State的子类,包含该小部件的状态并定义小部件的build()方法。

第0步:做好准备

如果您已经在# Flutter之构建布局中构建了该应用程序,请调制下一节。

  1. 确保您已经设置好您的环境
  2. 创建一个新的Flutter应用程序
  3. lib/main.dart文件替换为main.dart.
  4. pubspec.yaml文件替换为pubspec.yaml.
  5. 在你的项目中创建一个images目录,并添加 lake.jpg. 一旦您连接并启用了设备,或者启动了iOS模拟器或Android模拟器,您就可以开始了。

第1步:决定那个对象管理小部件的状态

可以通过多种方式管理小部件的状态,但在我们的示例中,小部件本身FavoriteWidget将管理自己的状态。在此实例中,切换星形是一个独立的操作,不会影响父小部件或UI的其余部分,因此小部件可以在内部处理其状态。

第2步:子类化StatefulWidget

该类FavoriteWidget管理自己的状态,因此它会覆盖createState()以创建一个State对象。当框架想要构建小部件时,它会调用createState()。为此示例中,createState()返回_FavoriteWidgetState的实例,您将在下一步中实现它。

class FavoriteWidget extends StatefulWidget {
    const FavoriteWidget(Key? key): super(key: key);
    
    @override
    State<FavoriteWidget> createState() => _FavoriteWidgetState();
}
注意:以下划线(_)开头的成员或类是私有的。

第3步:子类状态

该类_FavoriteWidgetState存储可以在小部件的生命周期内发生变化的可变数据。当应用程序首次启动时,用户界面会显示一颗实心的红星,表示该湖具有“收藏”状态,以及41个赞。这些值存储在_isFacorited_favoriteCount字段中:

Flutter应用程序添加交互性

该类还定义了一个`build()`方法,该方法创建一个包含红色`IconButton`和`Text`的行。您使用`IconButton`(而不是Icon)是因为它有一个`onPressed`属性,该属性定义了用于处理点击的回调函数(_toggleFavorite)。接下来您将定义回调函数。

Flutter应用程序添加交互性 提示:将Text放在SizedBox并设置其宽度可防止当文本在值40和41之间变化时出现显示的“跳跃”——否则还会发生跳跃,因为这些值具有不同宽度。

按下IconButton时调用_toggleFavorite()方法调用setState()很关键,因为这会告诉框架小部件的状态已更改并且应该重绘小部件。setState()的函数参数在这两种状态之间切换UI:

  • star图标和数字41
  • star_border图标和数字40
void _toggleFavorite() {
    setState(() {
        if (_isFavorited) {
            _favoriteCount -= 1;
            _isFavorited = false;
        } else {
            favoriteCount += 1;
            _isFavorited = true;
        }
    });
}

第4步:将有状态小部件插入小部件树

在应用程序的build()方法中将自定义有状态小部件添加到小部件。首先,找到创建图标和额文本的代码,并将其删除。在同一位置,创建有状态小部件:

Flutter应用程序添加交互性 就是这样!当您热重新加载应用程序时候,星形图标现在应该相应点击。

问题?

如果您的代码无法运行,请在您的 IDE 中查找可能的错误。 调试 Flutter 应用程序可能会有所帮助。如果仍然找不到问题,请对照 GitHub 上的交互式湖泊示例检查您的代码。

管理状态

重点是什么?
· 有不同的方法来管理状态
· 作为小部件设计者,您可以选择使用哪种方法
· 如果有疑问,请从管理父小部件中的状态开始。

谁管理有状态小部件的状态?小部件本身?父不见?两个都?另一个对象?答案是……这取决于。有几种有效的方法可以使您的小部件具有交互性。作为小部件设计者,您可以根据您希望如何使用小部件来做出决定。以下是管理状态的最常见方法:

  • 小部件管理自己的状态
  • 父级管理小部件的状态
  • 混合搭配方法

您如何决定使用哪种方法?以下原则可以帮助您做出决定:

  • 如果所讨论的状态是用户数据,例如复选框的选中或未选中模式,或滑块的位置,那么状态最好由父小部件管理。
  • 如果所讨论的状态是美学的,例如动画,那么状态最好由小部件本身管理。

如果有疑问,请从管理父小部件中的状态开始: 我们将通过创建三个简单示例来举例说明管理状态的不同方式:TapboxA、tapboxB和TapboxC。这些示例的工作方式都相似——每个示例都会创建一个容器,当点击该容器时,它会在绿色或灰色框之间切换。布尔_active值确定颜色:绿色表示活动,灰色表示不活动。

Flutter应用程序添加交互性 Flutter应用程序添加交互性

小部件管理自己的状态

有时,小部件在内部管理其状态最有意义。例如,ListView当其内容超出渲染框时自动滚动。大多是使用ListView的,开发者不想管理ListView的滚动行为,所以ListView自己管理它的滚动偏移量。 _TapboxAState类:

  • 管理TabboxA的状态。
  • 定义确定框当前颜色的_active布尔值。
  • 定义_handleTap()函数,该函数在点击框时更新_active并调用setState()函数更新UI。
  • 实现小部件的所有交互行为。
import 'package:flutter/material.dart';

// TapboxA manages its own state

//------------------------- TapboxA ----------------------------------

class TapboxA extends StatefulWidget {
    const TapboxA(Key? key) : super(key: key);
    
    @override
    State<TapboxA> createState() => _TapboxAState();
}

class _TapboxState extends State<TapboxA> {
    bool _active = false;
    
    void _handleTap() {
        setState(() {
            _active = !_active;
        });
    }
    
    @override
    Widget build(BuildContext context) {
        return GestureDetector(
            onTap: _handleTap,
            child: Container(
                width: 200.0,
                height: 200.0,
                decoration: BoxDecoration(
                    color: _active ? Colors.lightGreen[700] : Colors.grey[600],
                ),
                child: Center(
                    child: Text(
                        _active ? 'Active' : 'Inactive',
                        style: const TextStyle(fontSize: 32.0, color: Colors.white)
                    ),
                ),
            ),
        );
    }
}

//------------------------- MyApp ----------------------------------

class MyApp extends StatelessWidget {
    const MyApp(Key? key) : super(key: key);
    
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Flutter Demo',
            home: Scaffold(
                appBar: AppBar(
                    title: const Text('Flutter Demo'),
                ),
                body: const Center(
                    child: TapboxA(),
                ),
            ),
        );
    }
}

父小部件管理小部件状态

通常,父小部件管理状态不能告诉其子小部件何时更新是最有意义的。例如,IconButton允许您将图标视为可点击按钮。IconButton是一个无状态小哦不见,因为我们决定父小部件需要知道按钮是否已被点击,以便它可以采取适当的操作。

在以下示例中,TapboxB通过回调将其状态到处到其父级。因为TapboxB不管理任何状态,所以它是StatelessWidget的子类。

ParentWidgetState类:

  • 管理TapboxB_active状态。
  • 实现_handleTapboxChanged(),即点击框时调用的方法。
  • 当状态改变时,调用setState()来更新UI

TapboxB类:

  • 扩展StatelessWidget,因为所有状态都由其父级处理。
  • 当检测到点击时,它会通知父母。
import 'package:flutter/material.dart'

// ParentWidget manages the state for TapboxB.

//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
    const ParentWidget(Key? key) : super(key: key);
    
    @override
    State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<PrentWidget> {
    bool _active = false;
    
    void_handleTapboxChanged(bool newValue) {
        setState(() {
            _active = newValue;
        });
    }
    
    @override
    Widget build(BuildContext context) {
        return SizedBox(
            child: TapboxB(
                active: _active,
                onChanged: _handleTapboxChanged,
            ),
        );
    }
}

//------------------------- TapboxB ----------------------------------

class TapboxB extends StatelessWidget {
    const TapboxB({
        Key? key,
        this.active = active,
        required this.onChanged,
    }) : super(key: key);
    
    final bool active;
    final ValueChanged<bool> onChanged;
    
    void _handleTap() {
        onChanged(!active);
    }
    
    @override
    Widget build(BuildContext context) {
        return GestureDetector(
            onTap: _handleTap,
            child: Container(
                width: 200.0,
                height: 200.0,
                decoration: BoxDecoration(
                    color: active ? Colors.lightGreen[700] : Colors.grey[600],
                ),
                child: Center(
                    child: Text(
                        active ? 'Active' : 'Inactive',
                        style: const TextStyle(fontSize: 32.0, color: Colors.white),
                    ),
                ),
            ),
        );
    }
}

混合搭配方法

对于某些小部件,混合搭配方法最有意义。在这种情况下,有状态小部件管理部分状态,而父小部件管理状态的其他方面。

在TapboxC示例中,在点击时,框周围会出现深绿色边框。点击时,边框消失,框的颜色发生变化。TapboxC将其_active状态导出到其父级,但在内部管理其_highlight状态。此示例有两个State对象,_ParentWidgetState_TapboxCState

_ParentWidgetState对象:

  • 管理_active状态实现_handleTapboxChanged(),即点击框时调用的方法。
  • 当点击发生并且_active状态发生变化时,调用setState()来更新UII。

TapboxCState对象

  • 管理_highlight状态。
  • GestureDetector监听所有点击事件。当用户点击时,它会添加高亮显示(实现为深绿色边框)。当用户释放点击时,它会删除突出显示。
  • 调用setState()以在点击、点击或点击取消时更新UI,并且_highlight状态会更改。
  • 在点击事件中,将该状态更改传递给父小部件以使用小部件属性采取适当的操作。
import 'package:flutter/material.dart';

//---------------------------- ParentWidget ----------------------------
class ParentWidget extends StatefulWidget {
    const ParentWidget(Key? key) : super(key: key);
    
    @override
    State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
    bool _active = false;
    
    void _handleTapboxChanged(bool newValue) {
        setState(() {
            _active = newValue;
        });
    }
    
    @override
    Widget build(BuildContext context) {
        return SizedBox(
            child: TapboxC(
                active: _active,
                onChanged: _handleTapboxChanged,
            ),
        );
    }
}

//----------------------------- TapboxC ------------------------------
class TapboxC extend StatefulWidget {
    const TapboxC({
        Key? key,
        this.active = false,
        required this.onChanged,
    }) : super(key: key);
    
    final bool active;
    final ValueChanged<bool> onChanged;
    
    @override
    State<TapboxC> createState()  => _TapboxCState();
}

class _TapboxCState extends State<TapboxC> {
    bool _highlight = false;
    
    void _handleTapDown(TapDownDetails details) {
        setState(() {
            _highlight = true;
        });
    }
    
    void _handleTapUp(TapDownDetails details) {
        setState(() {
            _highlight = false;
        });
    }
    
    void _hhandleTapCancel() {
        setState(() {
            _hightlight = false;
        });
    }
    
    void _handleTap() {
        widget.onChanged(!widget.active);
    }
    
    @override
    Widget build(BuildContext context) {
        // This Example adds a greeen bordeer on tap down
        // On tap up, thisi square changes to the opposite state.
        return GestureDetector(
            onTapDown: _handleTapDown, // Handle the tap event in the ordeer that
            onTapUp: _handleTapUp, //they occur: down, up, tap, cancel
            onTap: _handleTap,
            onTapCancel: _handleTapCancel,
            child: Container(
                width: 200.0,
                height: 200.0,
                decoration: BoxDecoration(
                    color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
                    border: _highlight
                        ? Border.all(
                            color: Colors.teal[700]!,
                            width: 10.0,
                        ),
                        : null,
                ),
                child: Center(
                    child: Text(
                        widget.active ? 'Active' : 'Inactive',
                        style: const TextStyle(
                            fontSize: 32.0,
                            color: Colors.white,
                        )
                    ),
                ),
            ),
        );
    }
}

另一种实现可能会将高亮状态导出到父级,同时将活动状态保持在内部,但如果要求某人使用那个点击框,他们可能会抱怨它没有多大意义。开发人员关心该框是否处于好活动状态。开发人员可能不关心突出显示是如何管理,而更喜欢点击框处理这些细节。

其他交互式小部件

Flutter 提供了多种按钮和类似的交互式小部件。这些小部件中的大多数都实现了Material Design 指南,该指南定义了一组具有固定 UI 的组件。

如果您愿意,您可以使用GestureDetector将交互性构建到任何自定义小部件中。GestureDetector您可以在 管理状态中找到示例。在Handle tapsGestureDetector 中了解更多信息,这是Flutter cookbook中的一个食谱。

** 提示:  Flutter 还提供了一组名为 Cupertino.

当您需要交互性时,最简单的方法是使用其中一种预制小部件。这是部分清单:

标准小部件

材料成分

转载自:https://juejin.cn/post/7203180600818860091
评论
请登录