热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

相对布局怎么修改组件定位_Flutter—布局系统概述

老孟导读:此篇文章非常详细的讲解了Flutter布局系统的工作原理。翻译自:https:itnext.ioflutter-layout-system-o

3e52a4f89948276822d126882d9a32e9.png

老孟导读:此篇文章非常详细的讲解了 Flutter 布局系统的工作原理

翻译自:https://itnext.io/flutter-layout-system-overview-c70bbe9ba909?source=bookmarks---------17------------------

0163cc3e4376fe745c85e4fcf1901c27.png

最近,我决定专注于Flutter基础知识。这次,我试图更好地理解“布局系统的工作原理”,并回答以下问题:

  • 我的小部件的尺寸看起来不合适,怎么回事?
  • 我只想将Widget放置在特定位置,但是没有任何属性可以控制它,为什么呢?
  • 我一直看到诸如BoxConstraints,RenderBox和Size之类的术语。它们之间有什么关系?
  • 对布局系统如何工作有一个大概的了解?

本文并不意味着对以上所有内容进行深入而详细的描述。但是,我们将对最重要的内容进行很好的概述,力图将一切可视化。

“两个阶段” 布局系统和约束

首先,小部件是Flutter SDK的构建块,但它们不负责将其自身绘制到屏幕中。每个小部件都与负责此操作的RenderBox对象相关联。这些框是2D直角坐标系,其大小表示为距原点的偏移。每个RenderBox还将与一个BoxConstraints对象相关联,该对象包含四个值:最大|最小宽度和最大|最小高度。RenderBox可以选择具有所需的任何大小,但它必须遵守这些值/约束。小部件的大小/位置完全取决于这些RenderBox的属性。

原文:The same way Widgets build a Widget three, RenderBoxes make a render three.

我觉得three可能写错了,应该是tree,译文:以同样的方式小部件生成 组件树,RenderBoxes生成渲染树。

cc3e38a2e41b95051ef53c4400466d32.png

我们可以将Flutter的布局系统视为两阶段系统。在第一个阶段中,framework 以递归地方式沿着渲染树 把BoxConstraints传递给子组件。它为父组件提供了一种方式来调节/增强子组件的尺寸,并根据需要更新这些限制。换句话说,这是负责传播约束信息的阶段,让每个人知道其最大/最小值。

完成后,第二阶段开始。这次,每个RenderBox都将其选择的大小传递回其父对象。父级收集所有子级的大小,然后使用此几何信息将每个子级正确定位在自己的笛卡尔系统中。这个阶段负责确定大小和位置,在此阶段,父组件知道每个子组件的大小以及他们的位置。

那么,这到底意味着什么?

这意味着父组件有责任定义/限制/约束子组件的尺寸,并相对于其坐标系进行定位。换句话说,小部件可以选择其大小,但是它必须始终遵守从其父级收到的约束。此外,小部件不知道其在屏幕上的位置,但其父级知道。

如果您对小部件的大小或位置有疑问,请尝试查看(更新)其父组件。

Example

好的,让我们将所有内容可视化,尝试通过示例了解正在发生的事情。但是在此之前,以下是一些在调试约束时可能有用的术语,

下面的术语未翻译,因为这些术语本身比译文更好理解:

  • If *max(w|h) = min (w|h)*, that is *tightly* constrained.
  • If *min(w|h) = 0*, we have a *loose* constraint.
  • If *max(w|h) != infinite*, the constraint is *bounded.*
  • If *max(w|h) = infinite*, the constraint is *unbounded.*
  • If *min(w|h) = infinite*, is just said to be *infinite*
ce55c95e1631435861503083e76c623a.png

我们将使用的是初始应用模板的修改版本。通常,您可以通过两种简单的方法来检查窗口小部件RenderBox及其属性:

  1. 通过代码执行:我们可以使用LayoutBuilder在布局系统第一阶段拦截BoxConstraints传播,并检查约束。然后,在第二阶段完成后,我们使用键来获取小部件的RenderBox并能够检查Size,Position。

  2. 或使用DevTools窗口小部件检查器

    3e9c3d836235a466df867b1e82ae35f9.png

import 'package:flutter/material.dart';GlobalKey _keyMyApp = GlobalKey();GlobalKey _keyMaterialApp = GlobalKey();GlobalKey _keyHomePage = GlobalKey();GlobalKey _keyScaffold = GlobalKey();GlobalKey _keyAppbar = GlobalKey();GlobalKey _keyCenter = GlobalKey();GlobalKey _keyFAB = GlobalKey();GlobalKey _keyText = GlobalKey();void printConstraint(String name, BoxConstraints c) { print( 'CONSTRAINT of $name: min(w=${c.minWidth.toInt()},h=${c.minHeight.toInt()}) max(w=${c.maxWidth.toInt()},h=${c.maxHeight.toInt()})', );}void printSizes() { printSize('MyApp', _keyMyApp); printSize('MaterialApp', _keyMaterialApp); printSize('HomePage', _keyHomePage); printSize('Scaffold', _keyScaffold); printSize('Appbar', _keyAppbar); printSize('Center', _keyCenter); printSize('Text', _keyText); printSize('FAB', _keyFAB);}void printSize(String name, GlobalKey key) { final RenderBox renderBox = key.currentContext.findRenderObject(); final size = renderBox.size; print("SIZE of $name: w=${size.width.toInt()},h=${size.height.toInt()}");}void printPositions() { printPosition('MyApp', _keyMyApp); printPosition('MaterialApp', _keyMaterialApp); printPosition('HomePage', _keyHomePage); printPosition('Scaffold', _keyScaffold); printPosition('Appbar', _keyAppbar); printPosition('Center', _keyCenter); printPosition('Text', _keyText); printPosition('FAB', _keyFAB);}void printPosition(String name, GlobalKey key) { final RenderBox renderBox = key.currentContext.findRenderObject(); final position = renderBox.localToGlobal(Offset.zero); print("POSITION of $name: $position ");}void main() { runApp(LayoutBuilder( builder: (context, constraints) { printConstraint('MyApp', constraints); return MyApp(); }, ));}class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return LayoutBuilder( key: _keyMyApp, builder: (context, constraints) { printConstraint('MaterialApp', constraints); return MaterialApp( key: _keyMaterialApp, title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: LayoutBuilder( builder: (context, constraints) { printConstraint('HomePage', constraints); return HomePage( key: _keyHomePage, title: 'Flutter Demo Home Page', ); }, ), ); }, ); }}class HomePage extends StatefulWidget { HomePage({Key key, this.title}) : super(key: key); final String title; @override _HomePageState createState() => _HomePageState();}class _HomePageState extends State { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override void initState() { WidgetsBinding.instance.addPostFrameCallback(_afterLayout); super.initState(); } void _afterLayout(_) { printSizes(); printPositions(); } @override Widget build(BuildContext context) { return LayoutBuilder(builder: (context, constraints) { printConstraint('Scaffold', constraints); return Scaffold( backgroundColor: Colors.purple, key: _keyScaffold, appBar: AppBar( key: _keyAppbar, title: Text(widget.title), ), body: LayoutBuilder( builder: (context, constraints) { printConstraint('Center', constraints); return Center( key: _keyCenter, child: LayoutBuilder(builder: (context, constraints) { printConstraint('Text', constraints); return Text( 'You have pushed the button this many times:', key: _keyText, style: TextStyle(color: Colors.white), ); }), ); }, ), floatingActionButton: LayoutBuilder( builder: (context, constraints) { printConstraint('FAB', constraints); return FloatingActionButton( key: _keyFAB, onPressed: printSizes, tooltip: 'Increment', child: Icon(Icons.add), ); }, ), ); }); }}

f32e791d4d5b531ce7805066c337d283.png

让我们一步一步来看看发生了什么(在这里我们将忽略LayoutBuilders)。

fbf7912ec42d93438eddbdd07849df98.gif

在我们的示例中发生的第一件事是执行runApp(..)。此函数检查屏幕当前大小(在我们的示例中为392:759),然后创建一个BoxConstraints对象,其中包含将发送到我们的第一个小部件(MyApp)的约束。注意,max | min的宽度和高度都相等;因此,runApp使用了严格的约束-通过这样做,MyApp除了选择屏幕上的可用空间外,在选择其大小时将别无选择。

2b84e415d62e3b5a78a946fca210e3e6.gif

然后将约束向下传播到Widget树。MyApp,MaterialApp,HomePage和Scaffold都被告知相同的严格约束。因此,所有人将被迫填满整个屏幕。每个小部件都有机会向其子项通知不同的BoxConstraints(仍然尊重已收到的子项)。但是,在这种情况下,他们选择不这样做。

475092c43f83583104854606e3e25438.gif

现在事情开始变得越来越有趣。Scaffold告知AppBar有关必须使用的BoxConstraints的信息,但是,这一次,它使用了宽松的约束(min h = 0)。它使AppBar有机会选择所需的任何高度,但仍必须使用width = 390。

AppBar是一种特殊的小部件,称为PreferredSizeWidget。这种类型的小部件不会对其子级施加任何约束。如果尝试使用LayoutBuilder获取Title的约束,则会出现错误。而是,AppBar以首选/默认大小响应Scaffold:高度= 80,宽度= 392(受接收到的约束的约束)

获得AppBar的大小后,Scaffold继续下一个子项:Center

10dae827498d090a84de824eef677b4f.gif

好的,这里发生了很多事情。让我们尝试了解:

  1. Scaffold告知Center其约束,让其选择在 0
  2. Center转到其子组件“Text”,转发相同的约束。
  3. Text选择一个足以显示其数据的大小(279:16),然后回复Center。
  4. 借助手上的几何信息(大小),Center可以在其笛卡尔系统内正确定位文本。作为父母,Center有权选择其子组件位置,在这种情况下,它决定将其居中。

流程继续:

605e84e08b1f6bd3cd67acf2572b8b43.gif
  1. 然后,Center为自己选择一个大小,而不是仅选择一个“足够”的大小(如“Text”一样),而是决定尽可能大,因此受到了限制。
  2. Scaffold收到Center所需的尺寸,并且流程继续向其最后一个孩子:FAB
  3. FAB收到约束,然后将其首选大小返回给Scaffold(56:56)
  4. 最后,Scaffold还具有将每个孩子都放置在其笛卡尔系统内所需的所有几何信息。

最后,对Scaffold以上的所有小部件重复该过程:

26e3d23cfb7950ab6fbd39aff1f2e241.gif
  1. Size信息继续沿渲染树传播。
  2. 每个小部件都使用此信息将每个孩子放置在笛卡尔系统内。
  3. Scaffold回复HomePage,HomePage回复MaterialApp,MaterialApp回复MyApp。直到最后再次到达Main。
  4. Main获取此“最终”窗口小部件,并将其最终绑定到屏幕中。

RenderBox树最终绑定在屏幕上。我们有一个正在运行的应用程序。

有趣的事情要记住

  • 小部件不知道其在屏幕上的位置;它的父组件才知道。
  • 小部件可以选择想要的大小,但必须根据其父级的限制。
  • 约束向下传播,而大小向上传播。
  • 尝试了解约束条件,它们可能在以后有用。

我希望所有这些都可以帮助您更好地了解Flutter布局系统的工作方式。

8d594599a80ac0a16461d5d52a3f78dd.png你可能还喜欢
  • 【Flutter组件终结篇】332个组件 658页PDF

  • 【Flutter 实战】17篇动画系列文章带你走进自定义动画

  • 2020年20个Flutter最漂亮的UI库和项目

  • Flutter 中渐变的高级用法

22f473c1611938dc0f23908ae66bad37.png关注「老孟Flutter」让你每天进步一点点



推荐阅读
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • 今日份分享:Flutter自定义之旋转木马
    今日份分享:Flutter自定义之旋转木马-先上图,带你回到童年时光:效果分析子布局按照圆形顺序放置且平分角度子布局旋转、支持手势滑动旋转、快速滑动抬手继续旋转、自动旋转支持X轴旋 ... [详细]
  • python3 logging
    python3logginghttps:docs.python.org3.5librarylogging.html,先3.5是因为我当前的python版本是3.5之所 ... [详细]
  • 在一对一直播源码使用过程中,有时会出现软键盘切换闪屏问题,就是当切换表情的时候屏幕会跳动,因此要对一对一直播源码表情面板无缝切换进行优化。 ... [详细]
  • 使用Flutternewintegration_test进行示例集成测试?回答首先在dev下的p ... [详细]
  • 涉及的知识点-ViewGroup的测量与布局-View的测量与布局-滑动冲突的处理-VelocityTracker滑动速率跟踪-Scroller实现弹性滑动-屏幕宽高的获取等实现步 ... [详细]
  • Editedbymythouhttp:www.cnblogs.commythoupublicbooleancreateReflectedForAdapter(){finalintr ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了10分钟了解Android的事件分发相关的知识,希望对你有一定的参考价值。什么是事件分发?大家 ... [详细]
  • 当我在doWork方法中运行代码时,通过单击button1,进度条按预期工作.但是,当我从其他方法(即btn2,btn3)将列表传递给doWork方法时,进度条在启动后会跳转到10 ... [详细]
  • java unhandled,Eclipse编辑java文件报Unhandled event loop exception错误的解
    本人Eclipse版本是”eclipse-jee-kepler-SR2-win32-x86_64“昨天因为换电脑,所以重装了一下软件,装好eclipse ... [详细]
author-avatar
mobiledu2502899835
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有