給Android開發者Flutter上手指南

CrazyCodeBoy發表於2021-02-27

目錄

  • LinearLayout 在Flutter中等價於什麼(Android)?
  • RelativeLayout 在Flutter中等價於什麼(Android)?
  • 如何使用widget定義佈局屬性?
  • 如何分層佈局?
  • 如何設定佈局樣式?
  • ScrollView在Flutter中等價於什麼?
  • 誰是Flutter的列表元件?
  • 如何知道點選了列表中哪個item?
  • 如何動態更新ListView?

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定義佈局屬性?

在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

如何分層佈局?

在Android中,我們可以使用FrameLayout佈局進行分層。

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

如何設定佈局樣式?

Flutter有一套獨特的佈局系統,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

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 中,通常用 ListView RecyclerView 來展示一個列表;
  • 在 RN 中,通常用 FlatList SectionList 來展示一個列表;

在 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的點選事件。

如何動態更新ListView?

  • 在 Android 中,改變列表資料後通過notifyDataSetChanged來更新列表;
  • 在 iOS 中,你改變列表的資料,並通過 reloadData() 方法來通知 table 或是 collection view;

在 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');
        });
      },
    );
  }
}
複製程式碼

與建立一個 “ListView” 不同,建立一個 ListView.builder 接受兩個主要引數:列表的初始長度,和一個 ItemBuilder 方法。

ItemBuilder 方法和 iOS的cellForItemAt 代理方法非常類似,它接受一個位置,並且返回在這個位置上你希望渲染的 cell。

最後,也是最重要的,注意 onTap() 函式裡並沒有重新建立一個 List,而是 add 了一個 widget。

相關文章