likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 15

前言

这是笔者作为一个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'),  
    ],  
  );  
}

仔细看上面代码,会发现除了 Row 和 Column widget 以外是一模一样的。它们的子级是一样的,这个特性可以被充分利用来开发包含有相同的子级,但是会随时间改变的复杂布局。

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 组件,可以在应用中声明一个顶层 Widget-MaterialApp 作为入口。

如何定义主题

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 统一颜色和字体风格