[譯]Flutter for Android Developers - Views

先說兩句

關於Flutter就很少介紹了,它一樣是一個致力於開發跨平臺移動應用的SDK,使用Dart語言。Dart是Google家的語言,雖然比較低調,但也確實不算難用,學起來也不難。Flutter的實現參考了很多RN的思路。更多關於Flutter可參閱官方網站android

前幾天Google發佈了Dart2了,而且據說Google內部部分應用早已經轉用Flutter實現。我這琢磨着Flutter是否是得開始把玩一下了。因而開始一頓瞎操做。過程當中在官網發現了這篇結合Android對比Flutter的文檔,對以前作Android的同窗挺有幫助的,因而決定譯之並從新整理爲一個系列,也算是一個小結。這系列的文章更適合對Dart和Flutter已經有所瞭解的Android Developers。canvas

華麗分割線後,這系列文章正式開始網絡


View在Flutter中等價於什麼

  • in Android
    1. View是屏幕上可見的基礎元素,咱們屏幕上的Button,Toolbars等等,每一個東西都是一個View。
    2. 系統能夠修改整個View的層級結構中的任意一個View。
    3. 一個View被畫完以後它不會重繪,除非invalidate方法被調用。
  • in Flutter
    1. View等價於Widget。這是Flutter中的一個概念。
    2. Widget是不可修改的,以致於Widget變得很是的輕量。
    3. Widget只維持一幀,每一幀Flutter框架都會從新建立一個由Widget實例組成的樹。

怎麼更新Widgets

  • in Androidapp

    • 咱們能夠直接修改View的屬性來更新它們。
  • in Flutter框架

    • Widget是不可修改的,咱們無法直接去更新它們,取而代之咱們能夠用Widget的State來實現更新。

在Flutter中Widget分爲兩個類型:less

1.StatelessWidget 一個StatelessWidget沒有任何狀態信息。當你正在描述的界面元素不依賴於任何除了自身對象內的配置外的其餘東西時,StatelessWidgets就恰好派上用場。 好比在Android中咱們將logo用一個ImageView來展現。這個logo在運行的過程當中將不會再改變,所以放到Flutter中的話,咱們將用一個StatelessWidget來實現它。ide

2.StatefulWidget 若是你想在運行的過程當中動態的改變界面,好比在想網絡請求了數據以後,或者應用與用戶發生了一系列交互以後。這個時候就必需要使用帶有狀態信息的StatefulWidget,它能夠告知Flutter框架Widget的狀態已經更新進而促使Flutter框架去更新Widget。函數

Note: StatelessWidget和StatefulWidget的核心邏輯是相同的,就是他們在每一幀都被rebuild,不一樣的是StatefulWidget有一個State對象來保存狀態信息,而後在幀與幀之間它能夠經過這個State對象來恢復以前保存的狀態信息。佈局

若是你還比較疑惑,那麼能夠簡單記下這個規則:若是用戶會與一個Widget交互,那麼這個Widget就用StatefulWidget。若是一個Widget響應了一個交互事件,可是隻要包含它的Parent Widget沒有響應這個交互事件的話,它的Parent Widget依然是StatelessWidget。動畫

接下來咱們看下怎樣使用StatelessWidget。一個最多見的StatelessWidget就是Text Widget。若是你去看Text Widget的實現的話你會發現他是StatelessWidget的一個子類。

new Text(
  'I like Flutter!',
  style: new TextStyle(fontWeight: FontWeight.bold),
);
複製代碼

就像你看到的同樣,Text Widget沒有與它關聯的State信息,它只是簡單的渲染經過構造函數傳遞給它的信息。 可是若是咱們想使**"I like Flutter!"**動態的改變,好比經過點擊一個FloatingActionButton,該怎麼辦呢? 其實很簡單,咱們能夠經過將Text Widget包裹在一個StatefulWidget裏面來實現,當FloatingActionButton點擊的時候更新StatefulWidget中的狀態信息。 代碼以下:

import 'package:flutter/material.dart';

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  // Default placeholder text
  String textToShow = "I Like Flutter";

  void _updateText() {
    setState(() {
      // update the text
      textToShow = "Flutter is Awesome!";
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new Center(child: new Text(textToShow)),
      floatingActionButton: new FloatingActionButton(
        onPressed: _updateText,
        tooltip: 'Update Text',
        child: new Icon(Icons.update),
      ),
    );
  }
}
複製代碼

上面的代碼中能夠看到咱們將展現**"I Like Flutter"**的Text Widget在了一個繼承於State的_SampleAppPageState類中渲染。咱們自定義了一個繼承於StatefulWidget的類SampleAppPage,它是一個StatefulWidget,與它關聯的State就是_SampleAppPageState。當FloatingActionButton被點擊時會回調_updateText方法,_updateText方法經過調用State類的setState方法來修改Text Widget的內容。setState因爲修改了狀態信息會觸發Flutter框架對Widget的更新。

小結: 在Flutter中Widgets Tree是不可變的,而且每一幀Widget都會rebuild。沒法直接更新Widget。因此須要使用StatefulWidget來記錄State,記錄的State能夠在幀與幀之間共享(在下一幀時恢復上一幀保存的State信息)。經過改變State來觸發Flutter更新Widget。

怎樣對Widgets佈局,xml佈局文件在哪裏

  • in Android

    • 咱們通常經過xml來寫佈局。
  • in Flutter

    • 咱們經過Widget Tree來寫咱們的佈局。

這有一個例子描述了怎樣在屏幕上展現一個Widget,而且給他添加一些padding。

override
Widget build(BuildContext context) {
  return new Scaffold(
    appBar: new AppBar(
      title: new Text("Sample App"),
    ),
    body: new Center(
      child: new MaterialButton(
        onPressed: () {},
        child: new Text('Hello'),
        padding: new EdgeInsets.only(left: 10.0, right: 10.0),
      ),
    ),
  );
}
複製代碼

這裏看到其實在Flutter中是沒有xml佈局文件的存在了,取而代之的是直接在override的build方法中去佈局,這其實有點相似RN,這裏的build方法就相似RN中的render方法,只不過RN經過JSX使得render方法中經過xml語法來完成佈局,而Flutter則是徹底經過Dart語法來完成佈局。可讀性上我我的仍是更喜歡Flutter,xml與js混寫仍是以爲有點彆扭。 這裏列出了Flutter提供的全部的佈局。

小結: 在Flutter中不存在xml的佈局形式,Widget的佈局在build方法中直接構建。

怎麼從佈局中添加或者刪除一個組件

  • in Android

    • 咱們能夠調用addChild或者removeChild方法去動態的添加或者刪除一個ViewGroup中的View。
  • in Flutter

    • 由於widget是不可變的因此不能直接的addChild或者removeChild。可是能夠傳遞一個返回Widget的方法給它的Parent,而後經過一個boolean值在該方法中控制要返回的Widget。

下面的代碼展現瞭如何經過點擊FloatingActionButton來觸發在兩個Widget之間切換:

import 'package:flutter/material.dart';

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  // Default value for toggle
  bool toggle = true;
  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }

  _getToggleChild() {
    if (toggle) {
      return new Text('Toggle One');
    } else {
      return new MaterialButton(onPressed: () {}, child: new Text('Toggle Two'));
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new Center(
        child: _getToggleChild(),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _toggle,
        tooltip: 'Update Text',
        child: new Icon(Icons.update),
      ),
    );
  }
}
複製代碼

代碼也比較簡單,關鍵在於_SampleAppPageState的build方法中Center的構造方法中的child參數傳的是一個_getToggleChild方法。該方法經過一個toggle變量來決定返回給Center的是一個怎樣的Widget。而toggle的賦值一樣是由點擊FloatingActionButton後調用setState來改變的。也就是說將toggle做爲SampleAppPage的狀態保存下來,在展現的時候由toggle的值來動態決定要展現的是什麼Widget。

小結: 在Flutter中不能直接動態的去添加或者刪除一個Widget到Widgets Tree中,由於Flutter中的Widget是不可變的。但咱們能夠依賴StatefulWidget根據State的不一樣來靈活的構建不一樣的Widget。

怎樣對一個Widget作動畫

  • in Android
    • 咱們能夠經過經過xml文件或者調用View.animate()方法建立一個動畫。
  • in Flutter
    • 咱們將須要作動畫的Widget包裹到一個Transition中來實現。

像Android同樣,在Flutter中咱們也有AnimationController和Interpolator,Interpolator經過繼承Animation類實現,好比下面例子中用到的CurvedAnimation。咱們傳遞AnimationController和Animation到一個Widget中,而後經過AnimationController來啓動動畫。 下面的例子展現了使用FadeTransition來實現當按下按鈕時將展現Logo的FlutterLogo Widget淡出的效果:

import 'package:flutter/material.dart';

void main() {
  runApp(new FadeAppTest());
}

class FadeAppTest extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Fade Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyFadeTest(title: 'Fade Demo'),
    );
  }
}

class MyFadeTest extends StatefulWidget {
  MyFadeTest({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyFadeTest createState() => new _MyFadeTest();
}

class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
  AnimationController controller;
  CurvedAnimation curve;

  @override
  void initState() {
    controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
    curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
          child: new Container(
              child: new FadeTransition(
                  opacity: curve,
                  child: new FlutterLogo(
                    size: 100.0,
                  )))),
      floatingActionButton: new FloatingActionButton(
        tooltip: 'Fade',
        child: new Icon(Icons.brush),
        onPressed: () {
          controller.forward();
        },
      ),
    );
  }
}
複製代碼

整個Widget Tree仍是跟以前相似,MaterialApp的構造函數中home參數傳入的依然是咱們自定義的一個繼承自StatefulWidget的MyFadeTest,_MyFadeTest是其對應的State,其中定義了AnimationController和CurvedAnimation,AnimationController用於控制動畫,CurvedAnimation是一個插值器實現。接着在build方法中經過將咱們須要動畫的FlutterLogo Widget包裹在一個FadeTransition中來讓FlutterLogo Widget產生動畫,最後在按下FloatingActionButton的回調中使用AnimationController.forward()方法來觸發動畫。 這裏或者那裏查看更多關於動畫的具體細節。

小結: 在Flutter中也有AnimationController和插值器,經過AnimationController來控制動畫的播放,插值器改變更畫播放的加速度。 使用時先構造AnimationController,而後將構造好的AnimationController做爲參數構造插值器,最後將構造好的插值器做爲參數構造Transition。以後就能夠經過AnimationController來控制Transition中包含的Widget的動畫執行。

怎樣使用Canvas去畫內容

  • in Android
    • 咱們能夠用Canvas去畫一些自定義的圖形在屏幕上。
  • in Flutter
    • CustomPaint和CustomPainter這兩個類能夠幫助咱們在Canvas上做畫。

下面的代碼實現一個可自由簽名的Widget:

import 'package:flutter/material.dart';
class SignaturePainter extends CustomPainter {
  SignaturePainter(this.points);
  final List<Offset> points;
  void paint(Canvas canvas, Size size) {
    Paint paint = new Paint()
      ..color = Colors.black
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 5.0;
    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null)
        canvas.drawLine(points[i], points[i + 1], paint);
    }
  }
  bool shouldRepaint(SignaturePainter other) => other.points != points;
}
class Signature extends StatefulWidget {
  SignatureState createState() => new SignatureState();
}
class SignatureState extends State<Signature> {
  List<Offset> _points = <Offset>[];
  Widget build(BuildContext context) {
    return new GestureDetector(
      onPanUpdate: (DragUpdateDetails details) {
        setState(() {
          RenderBox referenceBox = context.findRenderObject();
          Offset localPosition =
          referenceBox.globalToLocal(details.globalPosition);
          _points = new List.from(_points)..add(localPosition);
        });
      },
      onPanEnd: (DragEndDetails details) => _points.add(null),
      child: new CustomPaint(painter: new SignaturePainter(_points)),
    );
  }
}
class DemoApp extends StatelessWidget {
  Widget build(BuildContext context) => new Scaffold(body: new Signature());
}
void main() => runApp(new MaterialApp(home: new DemoApp()));
複製代碼

能夠看到CustomPaint和CustomPainter搭配使用來實現了向Canvas上繪製自定義內容的目的。首先自定義SignaturePainter繼承於CustomPainter,並重寫paint方法,在本例中paint方法首先採用鏈式寫法構造一個Paint實例paint,接着遍歷points列表的內容來畫線。points列表是構造SignaturePainter時傳入的,裏面保存了觸摸屏幕的事件點的信息。其餘部分其實與以前的結構都差很少。關鍵在SignatureState的build方法中返回的是一個GestureDetector,這是一個可以幫助咱們捕獲手勢信息的Widget,在它的構造函數中child參數傳入的是一個CustomPaint,CustomPaint的構造函數又傳入了一個咱們自定義的SignaturePainter,而且將手勢捕獲事件時捕獲到的_points列表在這個時候傳遞給SignaturePainter。因而CustomPaint就和CustomPainter產生化學反應,相互配合完成在Canvas上做畫的效果。

效果以下:

小結: 在Flutter中實如今Canvas上做畫須要CustomPaint和CustomPainter相互配合,首先繼承CustomPainter自定義一個Painter並重寫paint方法實現繪製邏輯。而後將自定義的CustomPainter傳遞給CustomPaint的構造方法,CustomPaint做爲Widget Tree中的一個Widget使用自定義的CustomPainter完成繪製。

怎樣構建自定義Widget

  • in Android
    • 自定義View通常經過繼承View或者已經存在的其餘組件並重寫一些關鍵方法來實現。
  • in Flutter
    • 自定義一個Widget不是經過繼承而是經過組合其餘widgets。

讓咱們來看一個栗子:

class CustomButton extends StatelessWidget {
  final String label;
  CustomButton(this.label);

  @override
  Widget build(BuildContext context) {
    return new RaisedButton(onPressed: () {}, child: new Text(label));
  }
}
複製代碼

代碼很簡單,看到CustomButton同樣仍是繼承於StatelessWidget。構造函數接受一個字符串參數並保存在內部成員變量label中。在build方法中返回的是一個RaisedButton(一個Flutter提供的Widget),巧妙的地方是在RaisedButton的構造函數中傳入了一個Text(一個Flutter提供的Widget)做爲其child參數。這個Text Widget顯示的就是CustomButton的label成員中的內容。

在使用CustomButton的時候能夠像使用其餘Widget同樣直接使用:

override
  Widget build(BuildContext context) {
    return new Center(
      child: new CustomButton("Hello"),
    );
  }
}
複製代碼

小結: 在Flutter中自定義Widget是經過組合不一樣的Widget來實現的,自定義的Widget只繼承於StatelessWidget或者StatefulWidget,經過build方法中組合其餘的Widget來實現自定義Widget。

英文原版傳送

相關文章
相關標籤/搜索