美文网首页Android开发经验谈Flutter从入门到精通
快速上手Flutter开发系列教程」之布局与列表开发指南

快速上手Flutter开发系列教程」之布局与列表开发指南

作者: CrazyCodeBoy | 来源:发表于2019-03-22 00:02 被阅读8次

    快速上手Flutter开发系列教程

    本文参考了《Flutter从入门到进阶-实战携程网App》课程的部分讲解,更多关于Flutter 布局与列表的实战技巧可在《Flutter从入门到进阶-实战携程网App》中查看。

    说道列表大家可能都不陌生,那么在Flutter中列表是由什么实现的呢?它和Android上的LinearLayout与RelativeLayout又有什么异同呢,以及如何使用Flutter的列表以及动态更新列表等相关技巧和经验又有那些呢?另外,如何设置Flutter的布局样式呢?在这篇文章中将向大家揭晓这些答案。

    LinearLayout 在Flutter中等价于什么(Android)?

    在Android中,使用LinearLayout来使你的控件呈水平或垂直排列。在Flutter中,你可以使用Row或Co​​lumn widget来实现相同的结果:

    @override
    Widget build(BuildContext context) {
      return Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('Row One'),
          Text('Row Two'),
          Text('Row Three'),
          Text('Row Four'),
        ],
      );
    }
    
    @override
    Widget build(BuildContext context) {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('Column One'),
          Text('Column Two'),
          Text('Column Three'),
          Text('Column Four'),
        ],
      );
    }
    

    以上代码片段的完整部分可以在课程源码中查找。

    要了解有关构建线性布局的更多信息,可参考区贡献的媒体文章Flutter For Android Developers : How to design LinearLayout in Flutter?

    RelativeLayout 在Flutter中等价于什么(Android)?

    RelativeLayout用于使widget相对于彼此位置排列。在Flutter中,有几种方法可以实现相同的结果

    您可以通过使用ColumnRowStack的组合来实现RelativeLayout的效果。您可以为widget构造函数指定相对于父组件的布局规则。

    推荐参考在StackOverflow上的一个在Flutter中构建RelativeLayout的例子。

    如何使用widget定义布局属性?

    在React Native中,大多数布局都可以使用传递给特定组件的props来完成。 例如,您可以使用View组件上的样式prop来指定flexbox属性。 要在列中排列组件,您可以指定一个prop,例如:flexDirection:“column”

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

    以上代码片段的完整部分可以在课程源码中查找。

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

    例如, widgets 控制一个数组中的条目 并且 分别垂直和水平对齐它们。 Container widget 控制一个布局的样式和属性, 并且 Center widget 负责居中它的子widget。

    // Flutter
    Center(
      child: Column(
        children: <Widget>[
          Container(
            color: Colors.red,
            width: 100.0,
            height: 100.0,
          ),
          Container(
            color: Colors.blue,
            width: 100.0,
            height: 100.0,
          ),
          Container(
            color: Colors.green,
            width: 100.0,
            height: 100.0,
          ),
        ],
      ),
    )
    

    以上代码片段的完整部分可以在课程源码中查找。

    Flutter在其核心widget库中提供了各种布局小部件。 例如, Padding, Align, 和 Stack

    更多布局widget可参考 Layout Widgets

    basic-layout-android
    basic-layout-ios

    如何分层布局?

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

    Flutter 使用Stack widget 控制子widget在一层。 子widgets可以完全或者部分覆盖基础widgets。

    Stack控件将其子项相对于其框的边缘定位。如果您只想重叠多个子窗口小部件,这个类很有用。

    // Flutter
    Stack(
      alignment: const Alignment(0.6, 0.6),
      children: <Widget>[
        CircleAvatar(
          backgroundImage: NetworkImage(
            "https://avatars3.githubusercontent.com/u/14101776?v=4"),
        ),
        Container(
          decoration: BoxDecoration(
              color: Colors.black45,
          ),
          child: Text('Flutter'),
        ),
      ],
    )
    

    以上代码片段的完整部分可以在课程源码中查找。

    上一个示例使用 Stack 覆盖容器 (显示其“Text”在半透明的黑色背景上) 在’CircleAvatar之上.Stack偏移文本 使用alignment属性和Alignment`定位。

    Stack.png

    如何设置布局样式?

    在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,
        backgroundColor: "#fff",
        alignItems: "center",
        justifyContent: "center"
      }
    });
    

    以上代码片段的完整部分可以在课程源码中查找。

    Flutter没有内联样式和stylesheets.create的概念,它有一套独特的布局系统,PaddingCenterColumnRow、等都是widget,另外组件也通常接受用于布局样式的构造参数:比如Textwidget可以使用TextStyle属性。如果要在多个位置使用相同的文本样式, 你可以创建一个 TextStyle 类并将其应用于各个 Text widgets。

    // Flutter
    var textStyle = TextStyle(fontSize: 32.0, color: Colors.cyan, fontWeight:
       FontWeight.w600);
        ...
    Center(
      child: Column(
        children: <Widget>[
          Text(
            'Sample text',
            style: textStyle,
          ),
          Padding(
            padding: EdgeInsets.all(20.0),
            child: Icon(Icons.lightbulb_outline,
              size: 48.0, color: Colors.redAccent)
          ),
        ],
      ),
    )
    

    以上代码片段的完整部分可以在课程源码中查找。

    flutterstyling-ios
    flutterstyling-ios

    从Flutter实战教程中获取Flutter布局的更多经验技巧

    ScrollView在Flutter中等价于什么?

    在Android中,ScrollView允许您包含一个子控件,以便在用户设备的屏幕比控件内容小的情况下,使它们可以滚动。在Flutter中,最简单的方法是使用ListView。但在Flutter中,一个ListView既是一个ScrollView,也是一个Android ListView。

    在 iOS 中,你给 view 包裹上 ScrollView 来允许用户在需要时滚动你的内容。在 Flutter 中,最简单的方法是使用 ListView widget。它表现得既和 iOS 中的 ScrollView 一致,也能和 TableView 一致,因为你可以给它的 widget 做垂直排布:

    @override
    Widget build(BuildContext context) {
      return ListView(
        children: <Widget>[
          Text('Row One'),
          Text('Row Two'),
          Text('Row Three'),
          Text('Row Four'),
        ],
      );
    }
    

    以上代码片段的完整部分可以在课程源码中查找。

    更多关于在 Flutter 中如何排布 widget 的文档,请参阅 layout tutorial

    谁是Flutter的列表组件?

    • 在 iOS 中,通常用 UITableViewUICollectionView 来展示一个列表;
    • 在 Android 中,通常用 ListViewRecyclerView 来展示一个列表;
    • 在 RN 中,通常用 FlatListSectionList 来展示一个列表;

    在 Flutter 中,你可以用 ListView 来达到相似的实现:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(SampleApp());
    }
    
    class SampleApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Sample App',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: SampleAppPage(),
        );
      }
    }
    
    class SampleAppPage extends StatefulWidget {
      SampleAppPage({Key key}) : super(key: key);
    
      @override
      _SampleAppPageState createState() => _SampleAppPageState();
    }
    
    class _SampleAppPageState extends State<SampleAppPage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Sample App"),
          ),
          body: ListView(children: _getListData()),
        );
      }
    
      _getListData() {
        List<Widget> widgets = [];
        for (int i = 0; i < 100; i++) {
          widgets.add(Padding(padding: EdgeInsets.all(10.0), child: Text("Row $i")));
        }
        return widgets;
      }
    }
    

    以上代码片段的完整部分可以在课程源码中查找。

    在Android ListView中,您可以创建一个适配器,然后您可以将它传递给ListView,该适配器将使用适配器返回的内容来展示每一行,从上面代码中不难看出,在Flutter中没有adapter的等价物,我们唯一要做的就是控制这个list中要展示的数据。

    如何知道点击了列表中哪个item?

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(SampleApp());
    }
    
    class SampleApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Sample App',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: SampleAppPage(),
        );
      }
    }
    
    class SampleAppPage extends StatefulWidget {
      SampleAppPage({Key key}) : super(key: key);
    
      @override
      _SampleAppPageState createState() => _SampleAppPageState();
    }
    
    class _SampleAppPageState extends State<SampleAppPage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Sample App"),
          ),
          body: ListView(children: _getListData()),
        );
      }
    
      _getListData() {
        List<Widget> widgets = [];
        for (int i = 0; i < 100; i++) {
          widgets.add(GestureDetector(
            child: Padding(
              padding: EdgeInsets.all(10.0),
              child: Text("Row $i"),
            ),
            onTap: () {
              print('row tapped');
            },
          ));
        }
        return widgets;
      }
    }
    

    以上代码片段的完整部分可以在课程源码中查找。

    在上述代码中我们通过GestureDetector来监听item的点击事件。

    从Flutter实战教程中获取关于Flutter列表的更多玩法

    如何动态更新ListView?

    • 在 iOS 中,你改变列表的数据,并通过 reloadData() 方法来通知 table 或是 collection view;
    • 在 Android 中,改变列表数据后通过notifyDataSetChanged来更新列表;

    在 Flutter 中,如果你想通过 setState() 方法来更新 widget 列表,你会很快发现你的数据展示并没有变化。这是因为当 setState() 被调用时,Flutter 渲染引擎会去检查 widget 树来查看是否有什么地方被改变了。当它得到你的 ListView 时,它会使用一个==判断,并且发现两个 ListView 是相同的。没有什么东西是变了的,因此更新不是必须的。

    一个更新 ListView 的简单方法是,在setState() 中创建一个新的 List,并把旧 List 的数据拷贝给新的 list。虽然这样很简单,但当数据集很大时,并不推荐这样做,来一起看个demo:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(SampleApp());
    }
    
    class SampleApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Sample App',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: SampleAppPage(),
        );
      }
    }
    
    class SampleAppPage extends StatefulWidget {
      SampleAppPage({Key key}) : super(key: key);
    
      @override
      _SampleAppPageState createState() => _SampleAppPageState();
    }
    
    class _SampleAppPageState extends State<SampleAppPage> {
      List widgets = [];
    
      @override
      void initState() {
        super.initState();
        for (int i = 0; i < 100; i++) {
          widgets.add(getRow(i));
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Sample App"),
          ),
          body: ListView(children: widgets),
        );
      }
    
      Widget getRow(int i) {
        return GestureDetector(
          child: Padding(
            padding: EdgeInsets.all(10.0),
            child: Text("Row $i"),
          ),
          onTap: () {
            setState(() {
              widgets = List.from(widgets);
              widgets.add(getRow(widgets.length + 1));
              print('row $i');
            });
          },
        );
      }
    }
    

    以上代码片段的完整部分可以在课程源码中查找。

    一个推荐的、高效的且有效的做法是,使用 ListView.Builder 来构建列表。这个方法在你想要构建动态列表,或是列表拥有大量数据时会非常好用:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(SampleApp());
    }
    
    class SampleApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Sample App',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: SampleAppPage(),
        );
      }
    }
    
    class SampleAppPage extends StatefulWidget {
      SampleAppPage({Key key}) : super(key: key);
    
      @override
      _SampleAppPageState createState() => _SampleAppPageState();
    }
    
    class _SampleAppPageState extends State<SampleAppPage> {
      List widgets = [];
    
      @override
      void initState() {
        super.initState();
        for (int i = 0; i < 100; i++) {
          widgets.add(getRow(i));
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Sample App"),
          ),
          body: ListView.builder(
            itemCount: widgets.length,
            itemBuilder: (BuildContext context, int position) {
              return getRow(position);
            },
          ),
        );
      }
    
      Widget getRow(int i) {
        return GestureDetector(
          child: Padding(
            padding: EdgeInsets.all(10.0),
            child: Text("Row $i"),
          ),
          onTap: () {
            setState(() {
              widgets.add(getRow(widgets.length + 1));
              print('row $i');
            });
          },
        );
      }
    }
    

    以上代码片段的完整部分可以在课程源码中查找。

    从Flutter实战教程中获取Flutter关于列表效率优化的更多经验技巧

    与创建一个 “ListView” 不同,创建一个 ListView.builder 接受两个主要参数:列表的初始长度,和一个 ItemBuilder 方法。

    ItemBuilder 方法和 iOS的cellForItemAt 代理方法非常类似,它接受一个位置,并且返回在这个位置上你希望渲染的 cell。

    最后,也是最重要的,注意 onTap() 函数里并没有重新创建一个 List,而是 add 了一个 widget。

    未完待续

    • Flutter入门基础知识
    • Flutter主题和文字处理
    • Flutter什么是声明式UI
    • Flutter布局与列表
    • Flutter手势检测及触摸事件处理
    • Flutter状态管理d
    • Flutter线程和异步UI
    • Flutter表单输入与富文本
    • Flutter认识视图(Views)md
    • Flutter调用硬件、第三方服务以及平台交互、通知
    • Flutter路由与导航
    • Flutter项目结构、资源、依赖和本地化

    参考

    相关文章

      网友评论

        本文标题:快速上手Flutter开发系列教程」之布局与列表开发指南

        本文链接:https://www.haomeiwen.com/subject/etrkvqtx.html