[Flutter] Flutter之Android开发者教程(一)(自译)

文档地址:https://flutter.io/docs/get-started/flutter-for/android-devs

译者:Haocxx

 

    这篇文章是为了让Android开发工程师利用已有的Android知识快速理解基于Flutter的APP开发。如果你对Android framework有一定的理解,你可以把这篇文档作为一个迈向Flutter开发大门的跳板。

    你已有的Android开发知识非常有益于学习开发Flutter工程,因为Flutter依赖于许多移动操作系统的原理和架构。Flutter是一种新的移动开发框架,同时也提供了与Android/IOS通信的插件系统。如果你精通Android,你可以迅速上手Flutter开发。

    这篇文档适用于刚转向Flutter的开发者,你可以把它作为一个字典,用于查找刚上手Flutter最有可能遇到的问题及困惑。

视图

Android之中的View在Flutter中相当于什么?

    声明式UI的介绍:https://flutter.io/docs/get-started/flutter-for/declarative

    在Android中,View时显示在屏幕上所有控件图像的基础。按钮,工具栏,各种输入控件都UI都是一个View。在Flutter中,与View最相似的是Widget。Widget并不完全等于Android的View,但是刚开始的时候你也可以把它们当作是一个声明和构建UI的组件。

    但是,Widget还是跟Android中的View有很多区别的。首先,Widget有不同的生命周期:一个Widget是不可变的,当它们的状态被改变,Flutter framework会重新创建Widget的实例树。而在Android上,一个View只会绘制一次,除非调用invalidate。

    Flutter的Widget是不可变的轻量级的组件。因为Widget并不是视图本身,也不直接进行绘制,它更像是对真正视图对象的描述和定义。

    Flutter包含了MaterialDesign的库,MaterialDesign是一个面向所有平台(包括IOS)的灵活设计系统。

    Flutter具有良好的灵活性和设计表现力。比如在IOS上,你可以使用Cupertino组件来实现一个类似IOS原生的界面。

如何更新Widget?

    在Android中,你可以直接修改一个View。但在Flutter中,Widget是不能直接改变,而是需要通过改变Widget的状态来实现。

    这就是有状态Widget(StatefulWidget)和无状态Widget(StatelessWidget)概念的由来。无状态Widget正如名字定义的那样,是一个没有状态信息的Widget。

    无状态Widget在描述对非本Widget配置数据无依赖的控件时是很好用的。

    比如,在Android中,我们习惯把一个Logo图片放在一个ImageView中。这个图片在运行过程中不会发生改变,所以在Flutter中会使用无状态Widget来描述它。

   如果你想要根据网络接口拉取的数据或者相应用户来动态地改变UI,那就必须使用有状态Widget,并将Widget的改变通知Flutter的framework。

    需要注意的是,有状态Widget和无状态WIdget其实本质是一样的,他们每一帧都会重新创建。不同的是,有状态WIdget持有一个状态State对象来存储帧之间的信息。

    如果还是不太理解的话,只要记住一条规则就行了:如果一个Widget会因响应用户操作等而改变,它就是有状态Widget。另外,一个Widget要改变的话,包含它的父Widget如果自身不变的话仍然可以是无状态Widget。

    下面的例子告诉我们如何去定义一个无状态Widget。常见的一个无状态Widget是Text。如果查看Text的实现关系的话就会发现它是StatelessWidget的子类。

Text(
  'I like Flutter!',
  style: TextStyle(fontWeight: FontWeight.bold),
);

    正如你所看到的,Text没有任何与之相关联的状态信息,它指根据传入其构造方法的参数来渲染,没有其他的操作。

    但是如果你想让“I Like Flutter”动态地变化,比如响应按钮的点击该怎么实现呢?

    想要实现这一点,需要把Text包装进StatefulWidget并在用户点击按钮的时候更新它。

    比如:

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 {
  // Default placeholder text
  String textToShow = "I Like Flutter";

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

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

如何写出我的布局?XML布局文件在哪?

    在Android中,你需要把布局写在XML文件中。但在Flutter中你需要把它写在视图树(Widget Tree)中。

    下面的例子告诉你如何用padding属性来展示一个简单的视图:

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

     更多的布局属性可以参照官方文档:https://flutter.io/docs/development/ui/widgets/layout

如何从布局中添加或者删除一个元素?

    在Android中,你可以在父View上调用addChild或者removeChild来动态添加删除VIew。在Flutter中,因为Widget是不可变的,所以没有像Android一样直接添加删除的方法。相对应地,你可以传入一个返回Widget对象的方法,通过boolean值来控制子视图的创建。

    比如,下面的代码实现了点击按钮切换两个Widget:

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 {
  // Default value for toggle
  bool toggle = true;
  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }

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

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

如何为Widget实现动画效果?

    在Android中,你可以使用XML或者调用animate()方法来为一个View添加动画。在Flutter中,Widget的动画是通过将自身包装到animation库中的动画Widget来实现的。

    在Flutter中,使用AnimationController(继承自Animation)来控制动画的播放状态。它需要一个Ticker实例来发送每一帧绘制的回调,并为每一帧产生一个0到1之间的线性插值。你可以为一个AnimationController添加多个动画。

    比如,你可以使用CurvedAnimation来实现一个插值曲线动画效果。此时,AnimationController就是整个动画的主资源,CurvedAnimation会根据插值计算出一条曲线来替代控制器默认的线性运动。就好像Widget在演剧本一样。

    在Widget树build的时候可以为一个Widget指定一个动画属性,比如指定FadeTransition的不透明度,并将Widget包装到里面,然后通知Controller开始动画。

    下面的例子演示了如何写一个点击按钮,点击出现淡出Logo的效果:

import 'package:flutter/material.dart';

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

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

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

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

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

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

    关于动画的详细内容有三篇官方文档:

    Animation & Motion widgets:https://flutter.io/docs/development/ui/widgets/animation

    Animations tutorial:https://flutter.io/docs/development/ui/animations/tutorial

    Animation overview:https://flutter.io/docs/development/ui/animations

如何使用Canvas来绘制?

    在Android中,你可以使用Canvas和Drawable来绘制图像。Flutter也有一个类似的Canvas API,因为它是同样是基于Skia渲染引擎的,所以Android开发者在使用的时候会感到得心应手。

    Flutter有两个类来实现Canvas:CustomPaint和CustomPainter,你可以使用它们来实现自己的绘制算法。

    Collin在StackOverFlow中的回答演示了在Flutter中如何实现实现一个签名Painter:

    https://stackoverflow.com/questions/46241071/create-signature-area-for-mobile-app-in-dart-flutter

import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(home: DemoApp()));

class DemoApp extends StatelessWidget {
  Widget build(BuildContext context) => Scaffold(body: Signature());
}

class Signature extends StatefulWidget {
  SignatureState createState() => SignatureState();
}

class SignatureState extends State {
  List _points = [];
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (DragUpdateDetails details) {
        setState(() {
          RenderBox referenceBox = context.findRenderObject();
          Offset localPosition =
          referenceBox.globalToLocal(details.globalPosition);
          _points = List.from(_points)..add(localPosition);
        });
      },
      onPanEnd: (DragEndDetails details) => _points.add(null),
      child: CustomPaint(painter: SignaturePainter(_points), size: Size.infinite),
    );
  }
}

class SignaturePainter extends CustomPainter {
  SignaturePainter(this.points);
  final List points;
  void paint(Canvas canvas, Size size) {
    var paint = 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;
}

如何创建自定义Widget?

    在Android中,你通常会继承一个View,或使用已有的View并复写它的方法来实现自己想要的效果。

    在Flutter中,自定义View是通过对已有View的组合来实现的,而不是继承它们。这有点像Android中实现一个子元素全都是已有View的自定义ViewGroup。

    比如,怎样实现一个带有标签的自定义按钮?只需要写一个CustomButton Widget类,内部包含带有标签的RaisedButton即可。而不必继承RaisedButton。

class CustomButton extends StatelessWidget {
  final String label;

  CustomButton(this.label);

  @override
  Widget build(BuildContext context) {
    return RaisedButton(onPressed: () {}, child: Text(label));
  }
}

    你可以像使用其他Widget一样使用它:

@override
Widget build(BuildContext context) {
  return Center(
    child: CustomButton("Hello"),
  );
}

 

你可能感兴趣的:(Flutter,Flutter学习)