Lao Mengのガイド:この記事では、Flutterレイアウトシステムの動作原理について詳しく説明しています。
翻訳元:https : //itnext.io/flutter-layout-system-overview-c70bbe9ba909?source=bookmarks---------17---------------- -
最近、私はFlutterの基本に焦点を合わせることにしました。今回は、「レイアウトシステムのしくみ」について理解を深め、次の質問に答えました。
- ウィジェットのサイズが正しくありません。どうしたのですか?
- ウィジェットを特定の場所に配置したいのですが、それを制御するプロパティがありません。
- BoxConstraints、RenderBox、Sizeなどの用語が頻繁に表示されます。それらの間の関係は何ですか?
- レイアウトシステムがどのように機能するかについて一般的な理解がありますか?
この記事は、上記のすべての詳細で詳細な説明を提供することを意図していません。ただし、最も重要なコンテンツの概要を示し、すべてを視覚化するように努めます。
「2ステージ」レイアウトシステムと制約
まず、ウィジェットはFlutter SDKのビルディングブロックですが、ウィジェットは画面に自分自身を描画する責任はありません。各ウィジェットは、この操作を担当するRenderBoxオブジェクトに関連付けられています。これらのボックスは2D直交座標系であり、そのサイズは原点からのオフセットとして表されます。各RenderBoxは、最大|最小幅と最大|最小高の4つの値を含むBoxConstraintsオブジェクトにも関連付けられます。RenderBoxは必要な任意のサイズを選択できますが、これらの値/制約に従う必要があります。ウィジェットのサイズ/位置は、これらのRenderBoxのプロパティに完全に依存します。
原文:ウィジェットが3つのウィジェットを作成するのと同じ方法で、RenderBoxは3つのレンダリングを作成します。
3つは間違っているかもしれません。ツリー、翻訳のはずです。ウィジェットがコンポーネントツリーを生成するのと同じように、RenderBoxはレンダーツリーを生成します。
Flutterのレイアウトシステムは2段階のシステムと考えることができます。最初の段階では、フレームワークはBoxConstraintsをRender Treeに沿って子コンポーネントに再帰的に渡します。親コンポーネントが子コンポーネントのサイズを調整/拡張し、必要に応じてこれらの制限を更新する方法を提供します。言い換えれば、これは制約情報を広め、すべての人にその最大値/最小値を知らせる責任がある段階です。
完了後、第2フェーズが始まります。今回は、各RenderBoxは選択されたサイズを親オブジェクトに戻します。親はすべての子のサイズを収集し、この幾何情報を使用して、各子を独自のデカルトシステムに正しく配置します。この段階では、サイズと場所を決定します。この段階で、親コンポーネントは各子コンポーネントのサイズとその場所を認識します。
これはどういう意味ですか?
これは、親コンポーネントが、子コンポーネントのサイズを定義/制限/制約し、その座標系に対して相対的に配置することを担当することを意味します。つまり、ウィジェットはそのサイズを選択できますが、常にその親から受け取った制約に従う必要があります。また、ウィジェットは画面上の位置を認識していませんが、親は認識しています。
ウィジェットのサイズや場所について質問がある場合は、その親コンポーネントを表示(更新)してみてください。
例
さて、すべてを視覚化し、例を通して何が起こっているのかを理解してみましょう。しかしその前に、制約をデバッグするときに役立つ可能性があるいくつかの用語を以下に示します。
次の用語は、翻訳よりも理解されているため、翻訳されていません。
- もしMAX(W | H)=分(W | H)\、であるしっかり\拘束されました。
- もし分(W | H)= 0 \、我々は緩い\制約を。
- もしMAX(W | H)!=無限\、制約がさ有界\。
- もしMAX(W | H)=無限\、制約がある無制限\。
- もし分(W | H)=無限\、ちょうどであると言われて無限\
最初のアプリケーションテンプレートの修正バージョンを使用します。通常、RenderBoxウィジェットとその属性は、次の2つの簡単な方法で確認できます。
コードの実行を通じて:LayoutBuilderを使用して、レイアウトシステムの最初のステージでBoxConstraintsの伝達をインターセプトし、制約をチェックできます。次に、第2段階が完了したら、キーを使用してウィジェットのRenderBoxを取得し、サイズ、位置を確認できます。
または、DevToolsウィジェットインスペクターを使用する
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<HomePage> {
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),
);
},
),
);
});
}
}
何が起こったかを段階的に見てみましょう(ここではLayoutBuilderは無視します)。
この例で最初に発生するのは、runApp(..)を実行することです。この関数は、画面の現在のサイズ(この例では392:759)をチェックし、最初のウィジェット(MyApp)に送信される制約を含むBoxConstraintsオブジェクトを作成します。max | minの幅と高さは等しいことに注意してください。したがって、runAppは厳密な制約を使用します。これを行うと、MyAppは、画面上の使用可能なスペースを除いて、サイズを選択する選択肢がなくなります。
次に、制約をウィジェットツリーに伝搬します。MyApp、MaterialApp、HomePage、Scaffoldはすべて同じ厳密な制約を受けます。したがって、誰もが画面全体を埋める必要があります。各ウィジェットは、子にさまざまなBoxConstraintを通知する機会があります(まだ受信した子を尊重しています)。しかし、この場合、彼らはそうしないことを選びました。
今、物事はますます面白くなっています。ScaffoldはAppBarに使用する必要があるBoxConstraintsを通知しますが、今回は緩い制約を使用します(最小h = 0)。AppBarに必要な高さを選択する機会を与えますが、width = 390を使用する必要があります。
AppBarは、PreferredSizeWidgetと呼ばれる特別なウィジェットです。このタイプのウィジェットは、その子に制約を課しません。Titleの制約を取得するためにLayoutBuilderを使用しようとすると、エラーが発生します。代わりに、AppBarは推奨/デフォルトのサイズでScaffoldに応答します:高さ= 80、幅= 392(受け取った制約に従う)
AppBarのサイズを取得した後、Scaffoldは次のサブアイテムであるCenterに進みます。
さて、ここで多くのことが起こりました。理解してみましょう:
- 足場はセンターにその制約を伝え、0 <幅<392と0 <高さ<697の間で選択させます。最大の高さは759(画面の最大の高さ)から80(AppBarで選択された高さ)を引いたものであることに注意してください。
- センターはそのサブコンポーネント「テキスト」に移動し、同じ制約を転送します。
- テキストは、データを表示するのに十分なサイズ(279:16)を選択してから、中央に戻ります。
- 手元の幾何情報(サイズ)を利用して、Centerはテキストをデカルトシステムに正しく配置できます。親として、Centerは子コンポーネントの場所を選択する権利を持ちます。その場合、Centerはコンポーネントを中央に配置することを決定します。
プロセスは続きます:
- 次に、Centerは、「十分な」サイズ(「テキスト」など)を選択するのではなく、それ自体のサイズを選択しましたが、できるだけ大きくすることを決定したため、制限がありました。
- ScaffoldはCenterが必要とするサイズを受け取り、プロセスは最後の子に進みます:FAB
- FABは制約を受け取り、その適切なサイズをScaffoldに返します(56:56)
- 最後に、足場には、各子をデカルトシステムに配置するために必要なすべての幾何学的情報も含まれています。
最後に、Scaffoldの上のすべてのウィジェットに対してプロセスを繰り返します。
- サイズ情報は、Render Treeに沿って伝播し続けます。
- 各ウィジェットはこの情報を使用して、各子をデカルトシステムに配置します。
- ScaffoldはHomePageに返信し、HomePageはMaterialAppに返信し、MaterialAppはMyAppに返信します。最終的に再びメインに到達するまで。
- メインはこの「最終」ウィジェットを取得し、最後にそれを画面にバインドします。
RenderBoxツリーが最終的に画面にバインドされます。実行中のアプリケーションがあります。
覚えておくと面白いこと
- ウィジェットは画面上のその位置を認識せず、その親コンポーネントのみがウィジェットを認識します。
- ウィジェットは必要なサイズを選択できますが、その親によって制限される必要があります。
- 制約は下方向に伝搬し、サイズは上方向に伝搬します。
- 制約を理解してみてください。後で役立つ場合があります。
これらのすべてが、Flutterレイアウトシステムの動作をよりよく理解するのに役立つことを願っています。
と通信する
Laomeng Flutterブログアドレス(330コントロールの使用):http ://laomengit.com
Flutter交換グループ(WeChat:laomengit)へようこそ。パブリックアカウント[Lao Meng Flutter]に従ってください。