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

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%')
      )
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 作者: Mike Bluestein | 译:孙印凤 原文地址: [https://www.smashingmag...
    格老子阅读 3,455评论 0 6
  • 国庆后面两天在家学习整理了一波flutter,基本把能撸过能看到的代码都过了一遍,此文篇幅较长,建议保存(star...
    Nealyang阅读 4,337评论 1 17
  • 本文是在GitHub上一个flutter项目的资料中看到的,由于原文过于太长,因此对其进行了章节拆分方便阅读,此篇...
    韩明泽阅读 3,610评论 0 1
  • 文 张钰开学近两个月以来,班级问题层出不穷,在诸如“晚修纪律差”、“铁柜上杂物”等旧疾未根除的同时,“忘带拉力带...
    五温西东阅读 266评论 1 2
  • 图片发自简书App 冰 封住了/我的青春/ 西伯利亚的寒风/再也带不走/我 似花一样的年华/ 愤怒的年轻人啊/我们...
    徐州麒麟埙苑阅读 184评论 0 0