面向React Native开发者的Flutter——Flutter widgets

354 阅读10分钟

Flutter widgets

在Flutter中,您可以使用小部件构建UI,这些小部件描述了它们的视图在给定当前配置和状态的情况下应该是什么样子。

小部件通常由许多小的、单一用途的小部件组成,这些小部件嵌套以产生强大的效果。例如,Container小部件由几个负责布局、绘制和、定位和调整大小的小部件组成。具体来说,Container小部件包括LimitedBoxConstrainedBoxAlignPaddingDecoratedBoxTransform小部件。您可以通过新的独特防水组合这些和其他简单的小部件,而不是通过哦子类Container来产生自定义效果。

Center小部件是您如何控制布局的另一个示例。要使小部件居中,请将其包装在Center小部件中,然后使用布局小部件进行alignmengRowColumngrids。这些布局小部件没有自己的可视化表示。相反,它们的唯一目的是空哦哦那个纸另一个小部件布局的某些方面。要理解为什么一个小部件以某种房事呈现,检查相邻的小部件通常很有帮助。

有关详细信息,请参阅Flutter 技术概述

有关Widgets包中核心小部件的更多信息,请参阅Flutter Basic WidgetsFlutter Widget CatalogFlutter Widget Index

一、Views

1.1、View容器相当于什么?

在React Native中,View是一个之词汇布局Flexboxstyletouch handlingaccessibility controls

在Flutter中,您可以使用Widgets库中的核心布局小部件,例如ContainerColumnRowCenter。有关详细信息,请参阅布局小部件目录。

1.2、什么相当于FlatListSectionList

List是垂直排列的可滚动组件列表。

在React Native中,FlatListSectionList用于呈现简单的或分段的列表。

// Native Native
<FlatList
    data={[ ... ]}
    renderItem={({ item }) => <Text>{item.key}</Text>}>

ListView是Flutter最常用的滚动小部件。默认构造函数采用显示字列表。 ListView最适合少量小部件。对于大型或无限列表,请使用ListView.builder,它按需构建其子项并且仅构建哪些可见的子项。

var data = [
    'Hello',
    'World',
];
return ListView.builder(
    itemCount: data.length,
    itemBuilder: (context, index) {
        return Text(data[index]);
    },
);
  • Android

image.png

  • iOS

image.png

要了解如何实现无限滚动列表,请参阅官方 infinite_list示例。

1.3、如何使用Canvas进行绘图和绘画?

在React Native中,画布组件不存在,因此使用第三方库react-native-canvas

// React Native
handleCanvas = canvas => {
    const ctx = canvas.getContext('2d');
    ctx.fillStyle = 'skyblue';
    ctx.beginPath();
    ctx.arc(75, 75, 0, 2 * Math.PI);
    ctx.fillRect(150, 100, 300, 300);
    ctx.stroke();
};

render() {
    return (
        <View>
            <Canvas ref={this.handleCanvas} />
        </View>
    );
}

在Flutter中,您可以使用CustomPaintCustomPainter类来绘制到画布上。

以下示例显示了如何使用小部件在绘制阶段进行绘制CustomPaint。她实现抽象类,CustomPainter并将其传递给CustomPaint的paint属性。CustomPaint子类必须实现paint()shouldRepaint()方法。

class MyCanvasPainter extends CustomPainter {
    const MyCanvasPainter();
    
    @override
    void paind(Canvas canvas, Size size) {
        final Paint paint = Paint()..color = Colors.amber;
        canvas.drawCircle(const Offset(100.0, 200.0), 40.0, paint);
        final Paint paintRect = Paint()..color = Colors.lightBlue;
        final Rect rect = Rect.fromPointer(
            const Offset(150.0, 300.0),
            const Offset(300.0, 400.0),
        );
        canvas.drawRect(rect, paintRect);
    }
    
    @override
    bool shouldRepaint(MyCanvasPainter oldDelegate) => false;
}

class MyCanvasWidget extends StatelessWidget {
    const MyCanvasWidget({Key? key}) : super(key: key);
    
    @override
    Widget build(BuildContext context) {
        return const Scaffold(
            body: CustomPaint(painter: MyCanvasPainter()),
        );
    }
}
  • Android

image.png

  • iOS

image.png

二、 Layouts

2.1、如何使用小部件定义布局属性?

在React Native中,大部分布局都可以通过传递给特定组件的props来完成。例如,您可以在View组件上使用style属性来指定flexbox属性.要将您的组件排列在一列中,您需要指定一个道具,例如:flexDirection:'column'

// React Native
<View
    style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'space-between',
        alignItems: 'center'
    }}>

在Flutter中,布局主要由专门设计用于提供布局的小部件定义,并结合空间小部件及其样式属性。

例如,ColumnRow小部件采用子数组并分别垂直和水平对其它们。小Container不见采用布局和样式属性的结合,小部件将Center其子小部件居中。

@override
Widget build(BuildContext context) {
    return Center(
        child: Column(
            children: <Widget>[
                Container(
                    color: Colors.red,
                    width: 100.0,
                    height: 100.0,
                ),
                Container(
                    color: Colors.bluem
                    widget: 100.0,
                    height: 100.0,
                ),
                Container(
                    color: Colors.green,
                    width: 100.0,
                    height: 100.0,
                ),
            ],
        ),
    );
}

Flutter在其核心widget库中提供了多种布局widget。例如,PaddingAlignStack

有关完整列表,请参阅布局小部件

  • Android

image.png

  • iOS

image.png

2.2、如何分层小部件?

在React Native中,组件可以使用absolute定义进行分层。

Flutter使用Stack widget分层排列子widget。小部件可以完全或部分重叠基本小部件。

Stack小部件相对于其边缘定位其子项。如果您只是想重叠几个子部件,这个雷很有用。

@override
Widget build(BuildContext context) {
    return Stack(
        alignment: const Alignment(0.6, 0.6),
        children: <Widget>[
            const CircleAvatar(
                backgroundImage: NetworkImage(
                    'https://avatars3.githubusercontent.com/u/14101776?v=4'
                ),
            ),
            Conttainer(
                color: Colors.black45,
                child: const Text('Flutter'),
            ),
        ],
    );
}

前面的示例用于StackText一个CircleAvatarAlignmentStack 使用对齐属性和坐标偏移文本。

  • Android

image.png

  • iOS

image.png

有关详细信息,请参阅Stack类文档。

三、Styling

3.1、我如何设计我的组件?

在React Native中,内联样式stylesheets.create用于设置组件样式。

// React Native
<View style={styles.container}>
    <Text style={{ fontSize: 32, color: 'cyan', fontWeight: '600'}}>
        This is a sample Text
    </Text>
</View>

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColoe: '#fff',
        alignItems: 'center',
        justifyContent: 'center'
    }
});

在Flutter中,一个Text小部件可以TextStyle为其样式属性采用一个类。如果您想在多个地方使用相同的文本样式,您可以创建一个TextStyle类并将其用于多个Text小部件。

const TextStyle textStyle = TextStyle(
    color: Colors.cyan,
    fontSize: 32.0,
    fontWeight: FontWeight.w600,
);

return Center(
    child: Column(
        children: const <Widget>[
            Text('Sample text', style: TextStyle),
            Padding(
                padding: EdgeInsets.all(20.0),
                child: Icon(
                    Icons.lightbulb_outlint,
                    size: 48.0,
                    color: Colors.redAccent,
                ),
            ),
        ],
    ),
);
  • Android

image.png

  • iOS

image.png

3.2、我如何使用Icons和Colors?

React Native不包含对图标的支持,因此需要使用第三方库。

在Flutter中,导入Material库还会引入丰富的Material 图标颜色集。

return const Icon(Icons.lightbulb_outline, color: Colors.redAccent);

使用Icons该类时,请务必uses-material-design: true在项目pubspec.yaml文件中进行设置。这可确保MaterialIcons显示图标的字体包含在您的应用程序中。一般来说,如果你打算使用材料库,你应该包括这一行。

name: my_awesome_application
flutter:
  uses-material-design: true

Flutter的Cupertino(iOS风格)包含当前的iOS设计语言提供了高保真小部件。要使用该字体,请在项目的CupertinoIcons添加一个依赖项。 cupertino_iconspubspec.yaml

name: my_awesome_application
dependencies:
  cupertino_icons: ^0.1.0

要全局自定义组件的颜色和样式,请使用ThemeData为主题的各个方面制定默认颜色。将主题属性设置MaterialAppThemeData对象中。该类Colors提供来自Material Design调色板的颜色。

以下示例将主样本设置为blue,将文本选择设置red.

class SampleApp extends StatelessWidget {
    const SampleApp({Key? key}) : super(key: key);
    
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Sample App',
            theme: ThemeData(
                primarySwatch: Colors.blue,
                textSelectionTheme: const TextSelectionThemeData(selectionColor: Colors.red)
            ),
            home: const SampleAppPage(),
        );
    }
}

3.3、如何添加样式主题?

在React Native中,通用主题是在样式表中为组件定义的,然后在组件中使用。

ThemeData在Flutter中,通过在类中定义样式并将其传递给小部件中的theme属性,为几乎所有内容创建统一的样式MaterialApp

ThemeData在Flutter中,通过在类中定义样式并将其传递给小部件theme属性,为几乎所有内容创建统一的样式MaterialApp.

@override
Widget build(BuildContext context) {
    return MaterialApp(
        theme: ThemeData(
            primaryColor: Colors.cyan,
            brightness: Brightness.dark,
        ),
        home: const. StylingPage(),
    );
}

即使不使用MaterialApp小部件也可以应用主题。Theme小部件在其数据参数中采用ThemeData并将ThemeData应用于其所有子小部件。

@override
Widget build(BuildContext context) {
    return Theme(
        data: ThemeData(
            primaryColor: Colors.cyan,
            brightness: brightness,
        ),
        child: Scaffold(
            backggroundColor: Theme.of(context).primaryColor,
            // ...
        ),
    );
}

四、State management

状态是结构小部件时可以同步读取的信息,或者是在小部件的生命周期中可能发生变化的信息。要在Flutter中管理应用程序状态,请将StatefulWidget与State对象配对使用。

有关在 Flutter 中管理状态的方法的更多信息,请参阅状态管理

4.1、StatelessWidget

Flutter中的StatelessWidget是一个不需要状态更改的小部件——它没有要管理的内部状态。

当您描述的用户界面部分不依赖于对象本身的配置信息和扩展小部件配置信息以外的任何内容时,无状态小部件很有用BuildContext

AboutDialogCircleAvatarText是子类化的无状态小部件的示例StatelessWidget

import 'package:flutter/material.dart';

void main() => runApp(
    const MyStatelessWidget(
        text: 'StatelessWidget Example to show immutable data',
    ),
);

class MyStatelessWidget extends StatelessWidget {
    const MyStatelessWidget({Key? key, required this.text,});
    
    final String text;
    
    @override
    Widget build(BuildContext context) {
        return Center(
            child: Text(
                text,
                textDirection: TextDirection.ltr,
            ),
        );
    }
}

前面的示例使用MyStatelessWidget类的构造函数传递text,标记为final。此类扩展StatelessWidget——它包含不可变数据。

无状态小部件的build方法通常只在三中情况下本调用:

  • 当小部件被插入到树中时。
  • 当 小部件的父级更更改其配置时
  • InheritedWidget它依赖时,改变。

4.2、StatefulWidget

StatefulWidget是一个改变状态的小部件。使用该setState方法来管理的状态更改StatefulWidget。调用setState()告诉Flutter框架某个状态发生了变化,这会导致应用程序重新运行该build()方法,以便应用程序可以反应更改。

State是构建小部件时可以同步读取的信息,并且可能在小部件的生命周期内发生变化。小部件实现者有责任确保在状态个更改时及时通知状态对香港。StatefulWidget当小部件可以动态更改时使用。例如,通过在表单中键入内容或移动滑块来更改小部件的状态。或者,它可以随着时间的推移而改变——也许数据会你更新UI。

CheckboxRadioSliderInkWellFormTextField是子类化的有状态小部件的示例StatefulWidget

以下示例声明了一个StatefulWidget需要createState()方法的。此方法创建管理小部件状态的状态对象,_MyStatefulWidgetState

class MyStatefulWidget extends StatefulWidget {
    const MyStatefulWidget({Key? key, required this.title}) : super(key: key);
    
    final String title;
    
    @override
    State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

以下状态类_MyStatefulWidgetState实现build()了小部件的方法。当状态改变时,例如,当用户切换按钮时,setState()使用新的切换值调用。这会导致框架在UI重建此小部件。

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
    bool showText = true;
    bool toggleSstate = true;
    Timer? t2;
    
    void toggleBlinkState() {
        setState(() {
            toggleState = !toggleState;
        });
    }
    if (!toggleState) {
        t2 = Timer.periodic(const Duration(milliseconds: 1000), (t) {
            toggleShowText();
        });
    } else {
        t2?.cancel();
    }
}

void toggleShowText() {
    setState(() {
        showText = !showText;
    });
}

@override
Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
            child: Column(
                children: <Widget>[
                    if (showText) 
                        const Text(
                            'This execution will be done before you can blink.',
                        ),
                    Padding(
                        padding: const EdgeInsets.only(top: 70.0),
                        child: ElevatedButton(
                            onPressed: toggleBlinkState,
                            child: toggleState
                                ? const Text('Blink')
                                : const Text('Stop Blinking')
                        ),
                    ),
                ],
            ),
        ),
    );
}

4.3、StatefulWidget和StatelessWidget的最佳实践是什么?

以下是设计小部件时需要考虑的一些事项。

  1. 确定小部件应该是StatefulWidget还是StatelessWidget。 在Flutter中,小部件要么是有状态的,要么时候无状态的——取决于它们是否以来于状态变化。
    • 如果小部件发生变化——用户与其交互数据馈送中断了UI,那么它就是StatefulWidget
    • 如果一个小部件是最终的或不可变的,那么它就是StatelessWidget 2.确定哪个对象管理小部件的状态 在Flutter中,主要有三种管理状态的方法:(StatefulWidget
      • 小部件管理自己的状态
      • 父小部件管理小部件的状态
      • 混合搭配方法 在决定使用哪种方法时,请考虑以下原则:
      • 如果所讨论的状态是用户数据,例如复选框的选中或未选中模式,或者滑块的位置,那么状态最好由父小部件管理。
      • 如果所讨论的状态是审美的,例如动画,那么小部件本身最好地管理状态。
      • 如有疑问,让父小部件管理子小部件的状态。 3.子类化StatefulWidget和State 该类MyStatefulWidget管理它自己的状态——它扩展StatefulWidget,它覆盖createState()创建State对象的方法,以及框架调用createState()来构建小部件。在这个例子中,createState()创建了一个实例_MyStatefulWidgetState,在下一个最佳实践中实现。
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({
    super.key,
    required this.title,
  });

  final String title;
  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  @override
  Widget build(BuildContext context) {
    //...
  }
}
  1. 将 StatefulWidget 添加到小部件树中。

在应用程序的构建方法中将您的自定义添加StatefulWidget到小部件树。

class MyStatelessWidget extends StatelessWidget {
  // This widget is the root of your application.
  const MyStatelessWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: MyStatefulWidget(title: 'State Change Demo'),
    );
  }
}
  • Android

image.png

  • iOS

image.png