天天看點

Flutter入門基礎 - sea的部落格

Flutter入門基礎

Flutter是Google開發的一套全新的跨平台、開源UI架構(本質上就是sdk)。 支援iOS、Android系統,并且是Fuchsia系統的預設開發套件。桌面和web上的支援也都在實驗中。

Flutter特點:跨平台(Flutter是Fuchsia的開發架構,同時支援Android、IOS),媲美原生性能,熱重載(目前不支援熱更新,但已加入2019工作計劃)。

其官方程式設計語言為Dart,熟悉Dart語言。

入門網站:Flutter中文網 Flutter官網(英文)

1、工程基礎簡介

1.1 Dart導包規則

(1)導包dart庫裡面的包

import \'dart:html\';
           

(2)導入pubspec.yaml 的dependencies依賴的包

import \'package:test/test.dart\';
           

(3)導入路徑包,base為flutter根目錄

import \'package:base/components/swiper.dart\';
           

(4)隻導入foo

import \'package:lib1/lib1.dart\' show foo;
           

(5)Im除了foo都導入

import \'package:lib2/lib2.dart\' hide foo;
           

(6)包裡面存在辨別符沖突

import \'package:lib1/lib1.dart\';
import \'package:lib2/lib2.dart\' as lib2;
           

(7)延遲加載(懶加載)允許應用程式在需要時加載庫。以下是一些您可能使用延遲加載的情況:

   減少應用程式的初始啟動時間。

   例如,執行A / B測試 - 試用算法的其他實作。

   加載很少使用的功能,例如可選的對話框。

import \'package:greetings/hello.dart\' deferred as hello;
           

1.2 工程配置檔案:

Flutter項目中的pubspec.yaml檔案相當于Android項目中的gradle檔案,項目的資訊以及依賴在此檔案中聲明。依賴包由pub包倉庫管理:https://pub.dartlang.org/ ,未釋出在pub包倉庫的插件可以使用本地檔案路徑,甚至可以直接使用git項目位址。 參考:https://flutterchina.club/using-packages/

依賴沖突:用any來解決,會找到最合适的不沖突版本,再到 pubspec.lock中找到版本号替換,最終不要直接用any,是個風險。

#name很重要,如果修改了name所有的dart的檔案的import前引用的本地的檔案啊的包名都需要修改
name: flutterdemo
description: A new Flutter application.
 
dependencies:
  flutter:
    sdk: flutter
 
 #添加依賴packages 
  cupertino_icons: ^0.1.2
  english_words: ^3.1.0
 # image_picker: ^0.4.8
 
dev_dependencies:
  flutter_test:
    sdk: flutter
 
  #啟用國際化
  flutter_localizations:
    sdk: flutter
 
flutter:
 
  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true
  # To add assets to your application, add an assets section, like this:
  #添加資源,不單單是圖檔,images是個和pubspec.yaml配置檔案同級的目錄,如果不同級,需要添加..
  assets:
        - images/park.jpg
        - images/lake.jpg
        - images/touxiang.jpg
  #  - images/a_dot_burr.jpeg
  #  - images/a_dot_ham.jpeg
   #字型設定
   fonts:
     - family: Schyler
       fonts:
       - asset: fonts/Schyler-Regular.ttf
        - asset: fonts/Schyler-Italic.ttf
           style: italic
     - family: Trajan Pro
       fonts:
      - asset: fonts/TrajanPro.ttf
      - asset: fonts/TrajanPro_Bold.ttf
        weight: 700
           
2、關于MaterialApp和Scaffold:

Flutter提供了兩套不同風格的UI控件,分别是類Android風格的MaterialApp和類IOS風格的CupertinoApp。兩種風格下面的widget不能完全通用,且Material風格的widget數量要多一些。

~ MaterialApp是Flutter提供給Android的一個基礎widget,采用了材料設計風格。

經過實踐,MaterialApp全局最好隻有一個,作為主界面,app的主題、首頁等全局設定可以在此定義。

最初按照官網教程每個page我都傳回的MaterialApp,顯示是沒什麼問題,因為都是widget,但是會出現卡頓和其他界面上的問題,大家可以自己試一下。

~ 子頁面直接傳回Scaffold,Scaffold是MaterialApp的布局實作,提供了appbar,floatingActionButton,drawer,bottomNavigationBar等MD風格的控件api。

Flutter預設會在debug模式下在右上角顯示水印,去除方式:

debugShowCheckedModeBanner: false
           
3、自定義控件。

Flutter架構給我們提供了StatelessWidget和StatefulWidget兩個抽象類,用于自定義控件。

(1)StatelessWidget是‘‘無狀态控件’’,不可變狀态控件,通過建構其他控件來描述使用者界面的一部分。必須實作build方法,傳回一個widget對象。 Icon、 IconButton, 和Text等都是無狀态widget, 他們都是 StatelessWidget的子類。

(2)StatefulWidget 是動态的. 使用者可以和其互動 (例如輸入一個表單、 或者移動一個slider滑塊),或者可以随時間改變 (也許是資料改變導緻的UI更新).Checkbox, Radio, Slider, Form, 和TextField 都是 stateful widgets, 他們都是 StatefulWidget的子類。

(3)自定義Widget:繼承StatefulWidget,并重寫createState()方法,傳回一個State對象。

自定義無狀态的widget:

class RedBoard extends StatelessWidget {
  const RedBoard({ Key key }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: Colors.red
    );
  }
}
           

自定義可變狀态的widget:

class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}

class RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random();
    return (new Text(wordPair.asPascalCase));
  }
}
           

(4)繼承自CustomPaint畫控件

Flutter也可以像Android中繼承View的方式來繪制控件,通過繼承CustomPaint類來實作,具體用法此處略。

4、TextField樣式
decoration: new InputDecoration(
                  hintText: \'input name to search\',
                  border: InputBorder.none
              )
           

去掉下邊框。

外面套上Container,加上裝飾實作四面邊框效果

decoration: BoxDecoration(
                color: Colors.white,
                border: Border.all(color: Colors.green, width: 1.0),
                borderRadius: BorderRadius.circular(4)),
           

注意:

在decoration中加了color屬性,在Container中就不能加color屬性,否則會出錯。

TextField坑:

軟鍵盤resize視窗,解決方式:

In your `Scaffold`, set `resizeToAvoidBottomPadding` property to `false`.
           

preprefixIcon和suffixIcon如果使用系統提供的svg資源,需要指定顔色,不然在擷取焦點時會變成不可見狀态。

5、ListView

ListView的使用可以參考此文:https://blog.csdn.net/hao_m582/article/details/84112278

如果與其他widget放在同一個Column中,ListView外加Expanded才能正常顯示。

可以用ListView作為滾動塊,相當于android中的ScrollView效果,但是子view不是寫在widget中,而是直接寫在ListView的children屬性中,如:

//...
body: new ListView(
  children: [
    new Image.asset(
      \'images/lake.jpg\',
      width: 600.0,
      height: 240.0,
      fit: BoxFit.cover,
    ),
    titleSection,
    buttonSection,
    textSection,
  ],
),
//..
           
6、加載圖檔與控件縮放

需要在pubspec.xml中配置圖檔路徑,可以看上段代碼片段。

assets:
        - images/park.jpg
        - images/lake.jpg
        - images/touxiang.jpg
           

其中images檔案夾放在工程的根目錄。加載圖檔可以直接使用AssertImage類,也可以使用Image.asset方法。

經過查找,flutter不完全支援svg,xml格式的VectorDrawable在flutter上無法直接加載

類似Android中ImageView的scaleType屬性,flutter的Image控件也有其屬性Boxfit,而且這個屬性不僅僅适用于Image相關的Widget,FittedBox也具有此屬性。

FittedBox會在自己的尺寸範圍内縮放并且調整child位置,使得child适合其尺寸。

示例代碼:

new Container(
  color: Colors.amberAccent,
  width: 300.0,
  height: 300.0,
  child: new FittedBox(
    fit: BoxFit.contain,
    alignment: Alignment.topLeft,
    child: new Container(
      color: Colors.red,
      child: new Text("FittedBox"),
    ),
  ),
)
           

看一下幾種縮放方式的差別:

Flutter入門基礎 - sea的部落格

image.png

7、Flutter建構布局執行個體

Flutter布局機制的核心是Widget。在Flutter中,幾乎所有東西都是Widget - 甚至布局模型都是Widget。你在Flutter應用中看到的圖像、圖示和文本都是widget。 甚至你看不到的東西也是widget,例如行(row)、列(column)以及用來排列、限制和對齊這些可見widget的網格(grid)。

檢視Flutter中文網的教程:在Flutter中建構布局

常用Widget:

(1)Column和Row相對于Android中的LinearLayout,Column相對于Orientation.Vertical;Row相當于Orientation.Horizontal。

(2)ListView ,GridView與Android中的同名控件效果等同

ListTitle是Flutter封裝好的在清單中顯示的item控件,他有固定的顯示格式,如下:

Flutter入門基礎 - sea的部落格

ListTitle.jpg

(3)Stack相當于Android中的FrameLayout,但是它又具有RelativeLayout的一些屬性。

(4)Card相當于Android中的CardView

(5)事件響應:Flutter并非為所有Widget都直接提供了點選,長按等事件響應,這時我們需要用 GestureDetector這個widget包裹需要響應事件的widget來實作功能。

GestureDetector提供了如下手勢:

  Tap

  onTapDown 指針已經在特定位置與螢幕接觸

  onTapUp 指針停止在特定位置與螢幕接觸

  onTap tap事件觸發

  onTapCancel 先前指針觸發的onTapDown不會在觸發tap事件

  輕按兩下

  onDoubleTap 使用者快速連續兩次在同一位置輕敲螢幕.

  長按

  onLongPress 指針在相同位置長時間保持與螢幕接觸

  垂直拖動

  onVerticalDragStart 指針已經與螢幕接觸并可能開始垂直移動

  onVerticalDragUpdate 指針與螢幕接觸并已沿垂直方向移動.

  onVerticalDragEnd 先前與螢幕接觸并垂直移動的指針不再與螢幕接觸,并且在停止接觸螢幕時以特定速度移動

  水準拖動

  onHorizontalDragStart 指針已經接觸到螢幕并可能開始水準移動

  onHorizontalDragUpdate 指針與螢幕接觸并已沿水準方向移動

  onHorizontalDragEnd 先前與螢幕接觸并水準移動的指針不再與螢幕接觸,并在停止接觸螢幕時以特定速度移動。

(6)Button:Flutter提供了幾種樣式的按鈕,分别為:

  FlatButton:扁平的,沒有陰影效果的。

  RaisedButton:有陰影效果的。

  FloatingActionButton:懸浮按鈕,類似Android上同名的控件。

  OutlineButton:線框按鈕,帶外邊框的按鈕。

(7)Expanded、Flexible:Expanded 這是個用來讓子項具有伸縮能力的widget,繼承自Flexible。它們兩個的預設靈活系數是一樣的,但是fit參數不同,Expanded是預設要占滿配置設定的空間的,而Flexible則預設不需要。

(8)Ripple效果:Flutter中文網将InkWell翻譯成“墨水飛濺”效果,其實看到效果後,我們馬上就能聯想到Android中的ripple效果。

Flutter入門基礎 - sea的部落格

inkwell.gif

用法也是用InkWell套起想要達到效果的widget,具體屬性檢視源碼注釋。

下面用一個具體的例子介紹布局和其他可能用到的Widget:

Flutter入門基礎 - sea的部落格

聊天.png

上圖是常見的聊天清單樣式,首先我們能想到的是整個界面是一個ListView,根據類型有左邊和右邊兩種樣式。由于flutter沒有xml布局,所有界面都是通過widget搭積木一樣,一層一層套起來的。

左側的顯示:最外層應該是一個Row,Row中包含了一個CircleAvatar(沒錯,這個Widget官方直接提供了)和一個Text。

怎樣控制Text的背景樣式:

首先想到的就是外層套一個可以設定樣式的Container,通過給Container加一個decoration屬性,一般使用BoxDecoration,可以為Container設定背景顔色,前景顔色,邊框,圓角,圖檔等能滿足大部分場景的樣式。

問題出現了

Flutter入門基礎 - sea的部落格

image.jpg

Text本身是支援文字自動換行的,Container本身如果沒有父控件限制也是包裹的,但因為外面放了一個Row,就會出現溢出螢幕的問題。經過多番查找,我找到了一個Widget可以解決問題——Flexible,在Container外面套一個Flexible就能解決問題,此時需要注意的是,Flexible,Expanded等可以自适應的繼承了Flex的控件,其父控件也必須是同類型。

接下來我們要控制文字的最長顯示寬度,Container有一個屬性是constraints,它的類型是BoxConstraints,這個Widget可以設定最小最大寬高,不限制的話就用double.infinity(無限)。在經過限制後,我們發現Flexible已經不需要了,因為寬度已經限制住了=.=|||。

//擷取螢幕寬度的方法
double width = MediaQuery.of(context).size.width;
           

接下來我們按照Android中的了解,顯示右側頭像的消息,就在Row中先加入一個Text,再加入一個CircleAvatar。沒錯,但是怎樣居右顯示呢?經過查詢資料發現,Row通過textDirection屬性可以設定方向,我們将屬性設定成TextDirection.rtl,也就是rightToLeft,發現咦?怎麼頭像跑到前面去了?那我們再把頭像代碼移到前面,竟然對了。。也就說明,Row的繪制流程都是根據children中最先加入的子widget來繪制的。

輸入框實作:

輸入框首先要保持在界面底部,怎麼實作呢?了解到官方提供了一個BottomAppBar,将其設定給Scaffold中的bottomNavigationBar屬性。BottomAppBar的child給到一個Row控件,排列語音按鈕IconButton,輸入框TextField,更多按鈕IconButton。TextField外部要嵌套一個Container修飾樣式。運作後發現整個界面都無法顯示,而注釋掉TextField就可以顯示,由此想到應該是TextField的寬度不正常導緻的,使用萬能控件Flexible套在TextField的父級Container外後顯示正常。

接下來試試輸入文字,又出現坑了!BottomAppBar在輸入法彈出時無法自動上移,确定了resizeToAvoidBottomPadding設定為true的情況依然無法解決問題後,隻好找其他方式。在stackoverflow上找到了另一種方案:将最下面的輸入布局連同ListView都放入Scaffold的body中,ListView外加上Expanded伸縮,最外層一個Container包裹,運作完美,代碼如下:

@override
  Widget build(BuildContext context) {
    _getConversations();
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(mIsGroup
            ? mConversation.groupBean.groupName
            : mConversation.contactBean.nickName != null
                ? mConversation.contactBean.nickName
                : mConversation.contactBean.pin),
        elevation: 0.5,
        actions: <Widget>[
          new IconButton(
              icon: new Icon(
                mIsGroup ? Icons.group : Icons.person,
                size: 24,
                color: Colors.black54,
              ),
              onPressed: _goContactInfo),
        ],
      ),
      body: new Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          new Expanded(
            child: _buildConversations(),
          ),
          Row(
            mainAxisSize: MainAxisSize.max,
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              IconButton(icon: Icon(Icons.keyboard_voice), onPressed: null),
              Flexible(
                child: Container(
                    height: 40,
                    margin: EdgeInsets.fromLTRB(10, 6, 10, 6),
                    padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
                    decoration: BoxDecoration(
                      color: Colors.white,
                      border: Border.all(
                        color: Colors.black12,
                        width: 0.5,
                        style: BorderStyle.solid,
                      ),
                      borderRadius: BorderRadius.circular(4),
                    ),
                    child: TextField(
                      decoration: InputDecoration(
                          hintText: \'輸入内容\', border: InputBorder.none),
                    )),
              ),
              Container(
                margin: EdgeInsets.fromLTRB(0, 0, 10, 0),
                child: IconButton(icon: Icon(Icons.add_circle_outline),onPressed: null,),
              )
            ],
          ),
        ],
      ),
    );
  }
           

到此,就實作了圖中所示效果。

下面是item的布局代碼:

Widget _msgHolder(MessageBean message, BuildContext context) {
    double width = MediaQuery.of(context).size.width;
    double _maxWidth = width * 0.65;
    return Row(
      textDirection: message.from.pin == myInfo.pin?TextDirection.rtl:TextDirection.ltr,
      children: <Widget>[
        CircleAvatar(
          backgroundImage: AssetImage(message.from.avatarUrl == null
              ? "assets/drawable/ava_group.png"
              : message.from.avatarUrl),
        ),
        GestureDetector(
            onLongPress: () {
              _showList(options);
            },
            child: Container(
              constraints: BoxConstraints(
                  minWidth: 0,
                  maxWidth: _maxWidth,
                  minHeight: 0,
                  maxHeight: double.infinity),
              margin: EdgeInsets.fromLTRB(10, 5, 5, 10),
              padding: EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: Colors.green,
                border: Border.all(
                  color: Colors.green,
                  width: 0.5,
                  style: BorderStyle.solid,
                ),
                borderRadius: BorderRadius.circular(4),
              ),
              child: Text(
                _getMessage(message),
                style: TextStyle(color: Colors.white, fontSize: 16),
              ),
            )),
      ],
    );
  }
           

Widget的顯示與隐藏

Flutter知識點: Widget隐藏與顯示

8、Toast和Dialog

在聊天中,常見操作是長按消息,彈出一個具有選項的Dialog,那麼在Flutter中如何顯示常見的Toast和Dialog呢?

由于Flutter提供給安卓的大部分Widget都是基于Material設計的,是以Flutter并沒有提供Toast控件,而是提供了SnackBar。

//SnackBar的顯示
Scaffold.of(context).showSnackBar(SnackBar(
     content: Text("長按消息"),
));
           

SnackBar不止能夠顯示Text,還可以任意加入widget。

此處有坑:

關于上段代碼中的context(BuildContext),你在任何方法裡都可以取到context,但是運作起來很可能會遇到context為null的情況,那麼就需要傳入一個經過了執行個體化的BuildContext。比如Scaffold中或者ListView.builder中的context,傳給SnackBar即可。

Dialog:

Flutter為Android提供了Dialog,AlertDialog,SimpleDialog三種常用對話框。想要實作我們的需求用SimpleDialog剛合适,效果如下:

Flutter入門基礎 - sea的部落格

彈窗.jpg

showDialog<int>(
        context: context,
        builder: (BuildContext context) {
          return new SimpleDialog(
            children: options.map((value) {
              return new SimpleDialogOption(
                onPressed: () {
                  Navigator.pop(
                      context,
                      options.indexOf(
                          value)); //here passing the index to be return on item selection
                },
                child: new Text(
                  value,
                  style: TextStyle(
                    fontSize: 16,
                  ),
                ), //item value
              );
            }).toList(),
          );
        })
           
9、資料存儲

Flutter知識點:資料存儲之SharedPreferences

Flutter知識點:資料存儲之File

Flutter知識點:資料存儲之sqflite

官網sqflite頁面

下面的字段不能用于表的屬性名稱

"add","all","alter","and","as","autoincrement","between","case","check","collate","commit","constraint","create","default","deferrable","delete","distinct","drop","else","escape","except","exists","foreign","from","group","having","if","in","index","insert","intersect","into","is","isnull","join","limit","not","notnull","null","on","or","order","primary","references","select","set","table","then","to","transaction","union","unique","update","using","values","when","where"

           
10、Flutter常用插件
audio_recorder: any #錄音、播放
  flutter_sound: ^1.1.5#錄音
  dropdown_menu: ^1.1.0#下拉菜單
  simple_permissions:#權限擷取
  easy_alert:#彈框
  amap_location: any #高德地圖
  location: any #gogle位置擷取
  barcode_scan 0.0.8#二維碼識别qr_mobile_vision: ^0.1.0 #二維碼識别 這個不好用
  flutter_screenutil: ^0.3.0#螢幕适配工具類  
  flutter_spinkit: ^2.1.0#加載等待框
  lpinyin: ^1.0.6#漢字轉拼音
  shimmer: ^0.0.4#微光效果控件
  qr_flutter: ^1.1.3#二維碼生成
  url_launcher: any#啟動URL的Flutter插件。支援網絡,電話,短信和電子郵件
  datetime_picker_formfield: ^0.1.3#時間選擇控件
  flutter_picker: \'^1.0.0\'#選擇器
  common_utils: \'^1.0.1\'#工具類 時間、日期、日志等
  flutter_html: \'^0.8.2\'#靜态html标記呈現為Flutter小部件
  fluwx: \'^0.3.0\'#微信支付、分享、登入
  tobias: \'^ 0.0.6#支付寶
  cupertino_icons: \'^0.1.2\'#小圖示控件
  http: \'^0.11.3+16\'#網絡請求
  html: \'^0.13.3\'#html解析
  image_picker: \'^0.4.5\'#圖檔選擇(相冊或拍照)
  flutter_webview_plugin: any#webview展示
  fluttertoast: any#toast提示框
  shared_preferences: \'^0.4.2\'#shared_preferences存儲
  transparent_image: \'^0.1.0\'#透明圖檔控件
  flutter_swiper : \'^1.0.2\'#輪播圖
  charts_flutter: \'^0.4.0\'#統計圖表
  path_provider: \'^0.4.1\'#擷取系統檔案
  cached_network_image: \'0.4.1\'#加載網絡圖檔并本地緩存
  sqflite: \'^0.11.0+1\'#sqllite資料庫操作
  pull_to_refresh: \'^1.1.5\'#下拉重新整理上拉加載更多
  video_player: \'0.6.1\'#視訊播放
  collection: \'1.14.11\'#集合操作工具類
  device_info: \'0.2.1\'#擷取手機資訊
  flutter_svg: \'^0.3.2\'#展示svg圖示控件
  intl: any#國際化工具類
  connectivity: \'0.3.1\'#連結網絡
  flutter_staggered_grid_view:#瀑布流展示控件
  flutter_file_manager:#檔案管理
  loader_search_bar:#導航欄搜尋控件
  flutter_image_compress : any#圖檔壓縮
  ota_update : any#App下載下傳更新
  flutter_slidable:#item側滑控件
           

發表于

2019-12-15 15:27 

sea的部落格 

閱讀(247) 

評論(0) 

編輯 

收藏 

舉報

Flutter入門基礎 - sea的部落格