美文网首页Flutter从入门到进阶 实战携程网App
5. 【文档讲解】认识视图(**Views**)

5. 【文档讲解】认识视图(**Views**)

作者: 全栈小土堆堆堆 | 来源:发表于2019-04-10 17:35 被阅读0次

    3-7 【文档讲解】认识视图(Views)

    声明:Flutter专栏文档均来自慕课网
    https://coding.imooc.com/class/321.html

    认识视图(Views)

    • 谁是Flutter中View?
    • 如何更新Widgets?
    • 如何布局?
    • 如何在布局中添加或删除组件?
    • 如何对 Widget 做动画?
    • 如何绘图(Canvas draw/paint)?
    • 如何构建自定义Widgets?
    • 如何设置Widget的透明度?

    谁是FlutterView

    • 在Android中,View是屏幕上显示的所有内容的基础, 按钮、工具栏、输入框等一切都是View。
    • 在 iOS 中,构建 UI 的过程中将大量使用 view 对象。这些对象都是 UIView 的实例。它们可以用作容器来承载其他的 UIView,最终构成你的界面布局。
    • 在React Native中,View是一个支持Flexbox布局的容器,样式,触摸处理和辅助控制。

    Android中的View与iOS中的UIView在下文中统称为:View,React Native统称为RN。

    那么,在Flutter中我们可以将Widget当做是Android、iOS、RN中的View,但他们并不完全等价,但当我们试图去理解 Flutter 是如何工作的时候,我们可以认为它是“声明和构建 UI 的方法”。

    但是,Widget与View有一些区别。 首先,Widget具有不同的生命周期:它们是不可变的,它们会存在于状态被改变之前。 每当Widget或其状态发生变化时,Flutter的框架都会创建一个新的Widget实例树。 相比之下,Android/iOS视图被绘制一次,并且在调用invalidate/setNeedsDisplay之前不会重绘。

    此外,与View不同,Flutter的Widget很轻巧,部分原因在于它的不变性。 因为它本身不是视图,并且不是直接绘制任何东西,而是对UI及其语义的描述。

    Flutter 包含了 Material 组件库。这些 widgets 遵循了 Material 设计规范。材料设计是一个灵活的设计系统,并且为包括 iOS 在内的所有系统进行了优化。

    但是用 Flutter 实现任何的设计语言都非常的灵活和富有表现力。在 iOS 平台,你可以使用 Cupertino widgets 来构建遵循了 Apple’s iOS design language 的界面。

    在Flutter中,您可以使用Widgets库中的核心布局小部件 如 Container, Column, Row, 和 Center,关于widget的更多内容可参考:Layout Widgets目录。

    如何更新Widgets?

    在Android/iOS中要更新视图,我们可以直接通过对应的方法来操作更改。 在Flutter中,Widget是不可变的,不会直接更新。 相反,我们可以通过操纵Widget的状态来更新它们。

    这就是有状态和无状态Widget的概念来源。 StatelessWidget听起来就像是一个没有状态信息的Widget。

    StatelessWidgets适用于当我们描述的用户界面不依赖于对象中的配置信息时。

    例如,在Android/iOS中,我们需要用ImageView/UIImageView来显示logo。 logo在运行时不会改变,因此在Flutter中使用StatelessWidget是最好不过了。

    如果要根据HTTP网络请求或用户交互后收到的数据动态更改UI,则必须使用StatefulWidget并告诉Flutter框架Widget的状态已更新,以便更新该Widget。

    无状态Widget和有状态Widget之间的重要区别在于StatefulWidgets具有一个State对象,该对象存储状态数据并将其传递到树重建中,因此状态不会丢失。

    请记住以下规则:如果Widget在build之外更改(例如,由于运行时用户交互),则它是有状态的。 如果Widget永远不会改变,一旦构建,它就是无状态的。 但是,即使Widget是有状态的,如果包含它的父窗口小部件本身不对这些更改(或其他输入)做出反应,父Widget仍然可以是无状态的。

    接下来,我们来看看你如何使用一个StatelessWidget。Text就是一个常见的StatelessWidget。如果你查看Text Widget的实现,你会发现它是一个StatelessWidget的子类:

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

    正如你所看到的,Text 没有与之关联的状态信息,它呈现了构造函数中传递的内容,仅此而已。

    但是,如果你想让“I Like Flutter”动态变化,例如点击一个FloatingActionButton?可以通过将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<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 Scaffold(
          appBar: AppBar(
            title: Text("Sample App"),
          ),
          body: Center(child: Text(textToShow)),
          floatingActionButton: FloatingActionButton(
            onPressed: _updateText,
            tooltip: 'Update Text',
            child: Icon(Icons.update),
          ),
        );
      }
    }
    
    

    如何布局?

    • 在Android中,我们通过XML编写布局;
    • 在iOS 中,我们会用 Storyboard 文件来组织 views,并对它们设置约束,或在 view controller 中使用代码来设置约束;

    在 Flutter中,我们通过编写一个 widget 树来声明布局。

    下面这个例子展示了如何展示一个带有 padding 的简单 widget:

    @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),
          ),
        ),
      );
    }
    

    另外推荐大家在widget catalog中查看 Flutter提供的布局。

    如何在布局中添加或删除组件?

    • 在Android中,我们可以调用父级控件的addChild或removeChild方法以动态添加或删除View。
    • 在 iOS 中,我们可以调用父view的addSubview() 或在子view的removeFromSuperview()来动态地添加或移除子 view。

    在Flutter中,因为Widget是不可变的,所以没有类似的方法。相反,我们可以传入一个函数或表达式,该函数或表达式返回一个Widget给父项,并通过布尔值控制该Widget的创建。

    例如,当点击一个FloatingActionButton时,如何在两个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<SampleAppPage> {
      // 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创建动画或调用view.animate()。
    • 在 iOS 中,你通过调用 animate(withDuration:animations:) 方法来给一个 view 创建动画。

    在 Flutter 中,使用动画库来包裹 widgets,而不是创建一个动画 widget。

    在 Flutter 中,使用 AnimationController,这是一个可以暂停、寻找、停止、反转动画的 Animation 类型。它需要一个 Ticker 当 vsync 发生时来发送信号,并且在每帧运行时创建一个介于 0 和 1 之间的线性插值(interpolation)。我们可以创建一个或多个的 Animation 并附加给一个 controller。

    例如,我们可能会用 CurvedAnimation 来实现一个 interpolated 曲线。在这个场景中,controller 是动画过程的“主人”,而 CurvedAnimation 计算曲线,并替代 controller 默认的线性模式。

    当构建 widget 树时,你会把 Animation 指定给一个 widget 的动画属性,比如 FadeTransition 的 opacity,并告诉控制器开始动画。

    下面这个例子展示了在点击 FloatingActionButton 之后,如何使用 FadeTransition 来让 widget 淡出到 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<MyFadeTest> with TickerProviderStateMixin {
      AnimationController controller;
      CurvedAnimation curve;
    
      @override
      void initState() {
        super.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, Animations tutorial, Animations overview查看更多信息。

    如何绘图(Canvas draw/paint)?

    • 在Android中,可以使用CanvasDrawable 在屏幕上绘制出自定义形状和图片;
    • 在 iOS 上,可以通过 CoreGraphics 来在屏幕上绘制线条和形状;
    • 在RN中我们通常是由react-native-canvas插件来进行绘图;

    Flutter也有类似的Canvas API,因为它基于相同的底层渲染引擎Skia。 因此,对于Android开发人员来说,在Flutter中绘制到画布是一项非常熟悉的任务。Flutter有两个类可以帮助我们绘制画布,CustomPaint和CustomPainter,它们实现您的算法以绘制到画布。

    要了解如何在Flutter中实现签名Painter,可参阅Collin在StackOverflow上的答案。

    image

    绘制圆形和方形

    在Flutter中,你可以使用 CustomPaint 和 CustomPainter 类去绘制到画布。

    以下示例显示如何使用CustomPaintwidget在绘制阶段绘制。 它实现了抽象类CustomPainter,并将其传递给CustomPaint的painter属性。 CustomPaint子类必须实现paintshouldRepaint方法:

    image
    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<Signature> {
      List<Offset> _points = <Offset>[];
      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<Offset> 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;
    }
    
    import 'package:flutter/material.dart';
    import 'package:flutter_app/navigator/tab_navigator.dart';
    
    void main() => runApp(new MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Flutter bottomNavigationBar',
          theme: new ThemeData.fallback(),
          home: _MyCanvas(),
        );
      }
    }
    
    // Flutter
    class MyCanvasPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        Paint paint = Paint();
        paint.color = Colors.amber;
        canvas.drawCircle(Offset(100.0, 200.0), 40.0, paint);
        Paint paintRect = Paint();
        paintRect.color = Colors.lightBlue;
        Rect rect = Rect.fromPoints(Offset(150.0, 300.0), Offset(300.0, 400.0));
        canvas.drawRect(rect, paintRect);
      }
      bool shouldRepaint(MyCanvasPainter oldDelegate) => false;
      bool shouldRebuildSemantics(MyCanvasPainter oldDelegate) => false;
    }
    
    class _MyCanvas extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: CustomPaint(
            painter: MyCanvasPainter(),
          ),
        );
      }
    }
    

    如何构建自定义Widgets?

    • 在Android中,可以通过继承View或已经存在的某个控件,然后覆盖其绘制方法来实现自定义View;
    • 在iOS中,可以通过编写 UIView 的子类,或使用已经存在的 view 来重载并实现方法,以达到特定的功能;

    在 Flutter 中,推荐组合多个小的 widgets 来构建一个自定义的 widget(而不是扩展它)。

    举个例子,如果你要构建一个 CustomButton ,并在构造器中传入它的 label?那就组合 RaisedButton 和 label,而不是扩展 RaisedButton。

    class CustomButton extends StatelessWidget {
      final String label;
      CustomButton(this.label);
    
      @override
      Widget build(BuildContext context) {
        return new RaisedButton(onPressed: () {}, child: new Text(label));
      }
    }
    //使用自定义组件
    
    @override
      Widget build(BuildContext context) {
        return new Center(
          child: new CustomButton("Hello"),
        );
      }
    }
    

    如何设置Widget的透明度?

    • 在 iOS 中,什么东西都会有一个 .opacity 或是 .alpha 的属性;
    • 在Android中View有setAlpha方法;

    在 Flutter中如果要改变透明度,我们可以给widget 包裹一个 Opacity widget 来做到这一点。

    Opacity(
          opacity: 0.5,
          child: Text('透明度50%')
          )
    

    相关文章

      网友评论

        本文标题:5. 【文档讲解】认识视图(**Views**)

        本文链接:https://www.haomeiwen.com/subject/rmyoiqtx.html