前语

这是笔者作为一个Android工程师入门Flutter的学习笔记,笔者不想经过一种循规蹈矩的方式来学习:先学Dart语言,然后学习Flutter的基本运用,再到实践运用这样的步骤。这样的方式有点无趣且效率较低。

笔者觉得对于已经有Android根底的来说,经过类比Android的方式来学习Flutter,掌握核心根底概念后,直接开发实践运用,在这个进程中去学习其间的知识比如Dart语法、深化的知识点。这是笔者的一次学习尝试,并将其记录下来:

给Android工程师的Flutter入门手册(一)

给Android工程师的Flutter入门手册(二)

本篇是该系列的第三篇,主要内容是:

(1)布局:Android常用的布局对应Flutter的完成

(2)列表视图和适配器:Flutter中怎么完成列表展示和适配

(3)主题:主题的运用

布局

LinearLayout

Android 中,LinearLayout 用于线性布局 widget 的——水平或许垂直。在 Flutter 中,运用 Row 或许 Column Widget来完成相同的作用。

Widget getRowWidget() {
  return Row(  
    mainAxisAlignment: MainAxisAlignment.center,  
    children: const <Widget>[  
      Text('Row One'),  
      Text('Row Two'),  
      Text('Row Three'),  
      Text('Row Four'),  
    ],  
  );  
}  
Widget getColumnWidget() {  
  return Column(  
    mainAxisAlignment: MainAxisAlignment.center,  
    children: const <Widget>[  
      Text('Column One'),  
      Text('Column Two'),  
      Text('Column Three'),  
      Text('Column Four'),  
    ],  
  );  
}

仔细看上面代码,会发现除了RowColumnwidget 以外是一模一样的。它们的子级是一样的,这个特性能够被充分利用来开发包括有相同的子级,可是会随时刻改动的杂乱布局。

RelativeLayout

Android中RelativeLayout表明相对布局,经过 Widget 的相互方位对它们进行布局。 在 Flutter 中,能够经过组合运用 ColumnRow Stack Widget 完成 RelativeLayout 的作用。

层叠布局 Stack Web 中的绝对定位Android 中的 Frame 布局是相似的,子组件能够依据距父容器四个角的方位来确认本身的方位。层叠布局答应子组件依照代码中声明的次序堆叠起来。

Flutter中运用StackPositioned这两个组件来配合完成绝对定位。Stack答应子组件堆叠,而Positioned用于依据Stack的四个角来确认子组件的方位。

Widget getRelativeLayoutWidget() {
 // ConstrainedBox来保证Stack占满屏幕  
 return ConstrainedBox(  
    constraints: const BoxConstraints.expand(),  
    child: Stack(  
      alignment:Alignment.center , //指定未定位或部分定位widget的对齐方式  
      children: <Widget>[  
        Container(  
          color: Colors.red,  
          child: const Text("Hello Flutter",style: TextStyle(color: Colors.white)),  
        ),  
        const Positioned(  
          left: 18.0,  
          child: Text("Text1 On Left"),  
        ),  
        const Positioned(  
          top: 18.0,  
          child: Text("Text2 On Top"),  
        ),  
        const Positioned(  
          right: 18.0,  
          child: Text("Text3 On Right"),  
        ),  
        const Positioned(  
          bottom: 18.0,  
          child: Text("Text3 On Right"),  
        )  
      ],  
    ),  
  );  
}

经过StackPositioned,能够指定一个或多个子元素相对于父元素各个边的精确偏移,而且能够重叠。

给Android工程师的Flutter入门手册(三)

但假如我们只想简略的调整一个子元素在父元素中的方位的话,运用Align组件会更简略一些。

Align组件能够调整子组件的方位:

Align({
  Key key,
  this.alignment = Alignment.center,  
  this.widthFactor,
  this.heightFactor,
  Widget child,
})
  • alignment: 需求一个AlignmentGeometry类型的值,表明子组件在父组件中的起始位
  • widthFactorheightFactor是用于确认Align组件本身宽高的属性;它们是两个缩放因子,会分别乘以子元素的宽、高,终究的结果就是Align组件的宽高。假如值为null,则组件的宽高将会占用尽可能多的空间。

ScrollView

Android 中,运用 ScrollView 布局 `widget,假如用户的设备屏幕比运用的内容区域小,用户能够滑动内容。

Flutter 中,完成这个功用的最简略的办法是运用 ListView widget, Flutter 中 ListView widget 既能够说是Android ScrollView,也是 ListView

最简略的用法就是这样:

Widget getScrollListView() {
  return SizedBox(  
      width: 200,  
      height: 100,  
      child: Align(alignment: Alignment.center,  
      child: ListView(  
      scrollDirection: Axis.vertical,  
      children: const <Widget>[  
        Text('ListView One', ),  
        Text('ListView Two', ),  
        Text('ListView Three', ),  
        Text('ListView Four', ),  
        Text('ListView Five', ),  
        Text('ListView Six', ),  
          ],  
        ),  
      ));  
}

可是只用List是没有翻滚条,怎么快速加上呢:

Scrollbar是一个Material风格的翻滚条,假如要给可翻滚组件增加翻滚条,只需将Scrollbar作为可翻滚组件的恣意一个父级组件即可:

Widget getScrollListView() {
  return Scrollbar(  
      child: SizedBox(  
          width: 200,  
          height: 100,  
          child: Align(  
            alignment: Alignment.center,  
            child: ListView(  
              scrollDirection: Axis.vertical,  
              children: const <Widget>[  
                Text('ListView One', ),  
                Text('ListView Two', ),  
                Text('ListView Three', ),  
                Text('ListView Four', ),  
                Text('ListView Five', ),  
                Text('ListView Six', ),  
          ],  
        ),  
      )));  
}

给Android工程师的Flutter入门手册(三)

列表视图和适配器

运用 AndroidListView 时,创立一个 adapter 并将其传给 ListView ListView 烘托 adapter 返回的每一行内容。然后,你需求保证回收了每一行视图,否则,你会遇到各种古怪的界面和内存问题。

由于 Flutter widget 不可变的特点,你需求向 ListView 传入一组 widgetFlutter会保证滑动的快速顺畅。

增加分割线和点击事件

完成一个带分割线,而且每个Item能够呼应点击的ListView:

Widget getListView() {
  return ListView.separated(  
      itemBuilder: (BuildContext context, int index) {  
        return GestureDetector(  
          onTap: () {  
            debugPrint('item tapped $index');  
          },  
          child: ListTile(title: Text("ITEM $index")),  
        );  
      },  
      separatorBuilder: (BuildContext context, int index) {  
        return const Divider(color: Colors.blue);  
      },  
      itemCount: 100);  
}

给Android工程师的Flutter入门手册(三)

怎么动态更新 ListView?

Android 中,经过adapter调用notifyDataSetChanged完成列表刷新。

Flutter 中,假如你预备在setState()里更新一组 widget,你很快会发现你的数据并没有更新到界面上。这是由于当setState()被调用的时候, Flutter 烘托引擎会检查Widget树是否有任何更改。当引擎检查到ListView,他会履行==检查,并判断两个ListView是一样的。没有任何更改,所以也就不需求更新。

所以,更新ListView的一个简略办法是,在setState()里创立一个新的List,并将数据从旧列表拷贝到新列表。尽管这个办法很简略,可是不推荐在大数据集的时候运用。

推荐的高效且有用的创立一个列表的办法是运用 ListView.Builder。这个办法非常适用于动态列表或许具有很多数据的列表。能够理解它就是Android里的 RecyclerView,会为你自动回收列表项:

对上末节代码做个改造,完整代码如下:

class _SampleAppPageState extends State<SampleAppPage> {
  @override  
  Widget build(BuildContext context) {  
    return Scaffold(  
        appBar: AppBar(  
          title: const Text('ListView Demo'),  
        ),  
        body: getListView());  
  }  
  List<Widget> widgets = [];  
  @override  
  void initState() {  
    super.initState();  
    for (int i = 0; i < 100; i++) {  
      widgets.add(getItemView(i));  
    }  
  }  
  Widget getListView() {  
    return ListView.separated(  
        itemBuilder: (BuildContext context, int index) {  
          return getItemView(index);  
        },  
        separatorBuilder: (BuildContext context, int index) {  
          return const Divider(color: Colors.blue);  
        },  
        itemCount: widgets.length);  
  }  
  Widget getItemView(int index) {  
    return GestureDetector(  
      onTap: () {  
        debugPrint('item tapped $index');  
        setState(() {  
          debugPrint('item setState $index');  
          widgets.add(getItemView(index + 1)); // tap后增加一个新数据  
        });  
      },  
      child: ListTile(title: Text("ITEM $index")),  
    );  
  }  
}

其间ItemBuilder 办法和 Android adapter 里的getView办法相似;它经过方位返回你希望在这个方位烘托的列表项。

最重要的一条是,onTap()办法不重建列表项,而是对widget调集履行元素增加的操作,增加后就会自动动态更新ListView的数据显现了

主题

Android 中你在 XML 文件中定义主题并在 AndroidManifest.xml 中将其赋值给你的运用。

Flutter 中是在顶层 Widget 上声明主题。为了在运用中利用好 Material 组件,能够在运用中声明一个顶层 WidgetMaterialApp作为进口。

怎么定义主题

Flutter 供给开箱即用的美丽的 Material Design 完成,能够满足你通常需求的各种款式和主题的需求。MaterialApp 是一个包装了一系列 Widget 的为你给予便当的 Widget,而这些 Widget 通常是完成 Material Design 的运用所必须的。它根据 WidgetsApp 并增加了Material相关的功用。

当然能够运用WidgetApp作为运用的 Widget,它会供给一些相同的功用,可是不如MaterialApp供给的功用丰厚。

假如要自定义恣意子组件的色彩或许款式,给MaterialApp这个Widget 传入一个ThemeData对象即可。

例如,鄙人面的代码中,主色调设置为蓝色,定义一些文本主题:

Widget build(BuildContext context) {
  const appName = 'Custom Themes';  
  return MaterialApp(  
    title: appName,  
    theme: ThemeData(  
      primarySwatch: Colors.blue,  // 主色调设置为蓝色  
      // 文本主题      
      textTheme: const TextTheme(  
        displayLarge: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),  
        titleLarge: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic),  
        bodyMedium: TextStyle(fontSize: 14.0, fontFamily: 'Hind'),  
      ),  
    ),  
    home: const MyHomePage(  
      title: appName,  
    ),  
  );  
}

运用文本主题

定义好文本主题后,就能够运用到Text `中:

body: Center(
  child: Container(  
    color: Theme.of(context).colorScheme.secondary,  
    child: Text(  
      'This is a theme text',  
      style: Theme.of(context).textTheme.titleLarge,  
    ),  
  ),  
)

给Android工程师的Flutter入门手册(三)

参考

LinearLayout部分

How to design LinearLayout in Flutter.

BoxDecoration

Flutter编程之BoxDecoration用法详解

主题部分

运用 Themes 统一色彩和字体风格