相当于 Flutter 中的 RelativeLayout
Equivalent of RelativeLayout in Flutter
有没有办法实现类似于 RelativeLayout
在 Android 上所做的事情?
特别是我正在寻找类似于 centerInParent
、layout_below:<layout_id>
、layout_above:<layout_id>
和 alignParentLeft
的内容
有关 RelativeLayout 的更多参考:https://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html
编辑:这是一个依赖于 RelativeLayout
的布局示例
所以在上图中,在顶部,"tofu's Songs" 文本在 RelativeLayout
内对齐为 centerInParent
。而其他 2 个是 alignParentLeft
和 alignParentRight
在火焰图标所在的每个单元格上,其底部的赞数围绕火焰图标的中心对齐。此外,每个单元格的顶部和底部标题分别与图像头像的右侧和顶部和底部对齐。
Flutter 布局通常使用 Column
, Row
and Stack
widgets. These widgets take constructor arguments that specify rules for how the children are laid out relative to the parent, and you can also influence layout of individual children by wrapping them in Expanded
, Flexible
, Positioned
, Align
, or Center
小部件树构建。
也可以使用 CustomMultiChildLayout
. This is how Scaffold
is implemented internally, and an example of how to use it in an app appears in the Shrine demo. You can also use LayoutBuilder
or CustomPaint
, or go down a layer and extend RenderObject
as shown in the sector example 构建复杂的布局。像这样手动进行布局需要更多工作,并且在边角情况下更容易出错,所以如果可以的话,我会尝试使用高级布局原语。
回答您的具体问题:
- 使用
AppBar
的 leading
和 trailing
参数来定位您的应用栏元素。如果您想改用 Row
,请使用 mainAxisAlignment
of MainAxisAlignment.spaceBetween
.
- 使用
Row
和 CrossAxisAlignment.center
的 crossAxisAlignment
将火焰图标和数字放在下方。
- 使用
Column
with a mainAxisAlignment
of MainAxisAlignment.spaceBetween
to position your top and bottom title. (You should consider using ListTile
来布置列表图块,但如果这样做,您将失去对精确定位的控制。)
这是实现您提供的设计的代码片段。在此示例中,我使用 IntrinsicHeight
来确定歌曲块的高度,但您可以通过将它们硬编码为固定高度来提高性能。
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
brightness: Brightness.dark,
primaryColorBrightness: Brightness.dark,
),
home: new HomeScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class Song extends StatelessWidget {
const Song({ this.title, this.author, this.likes });
final String title;
final String author;
final int likes;
@override
Widget build(BuildContext context) {
TextTheme textTheme = Theme
.of(context)
.textTheme;
return new Container(
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
decoration: new BoxDecoration(
color: Colors.grey.shade200.withOpacity(0.3),
borderRadius: new BorderRadius.circular(5.0),
),
child: new IntrinsicHeight(
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Container(
margin: const EdgeInsets.only(top: 4.0, bottom: 4.0, right: 10.0),
child: new CircleAvatar(
backgroundImage: new NetworkImage(
'http://thecatapi.com/api/images/get?format=src'
'&size=small&type=jpg#${title.hashCode}'
),
radius: 20.0,
),
),
new Expanded(
child: new Container(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(title, style: textTheme.subhead),
new Text(author, style: textTheme.caption),
],
),
),
),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 5.0),
child: new InkWell(
child: new Icon(Icons.play_arrow, size: 40.0),
onTap: () {
// TODO(implement)
},
),
),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 5.0),
child: new InkWell(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Icon(Icons.favorite, size: 25.0),
new Text('${likes ?? ''}'),
],
),
onTap: () {
// TODO(implement)
},
),
),
],
),
),
);
}
}
class Feed extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new ListView(
children: [
new Song(title: 'Trapadelic lobo', author: 'lillobobeats', likes: 4),
new Song(title: 'Different', author: 'younglowkey', likes: 23),
new Song(title: 'Future', author: 'younglowkey', likes: 2),
new Song(title: 'ASAP', author: 'tha_producer808', likes: 13),
new Song(title: '', author: 'TraphousePeyton'),
new Song(title: 'Something sweet...', author: '6ryan'),
new Song(title: 'Sharpie', author: 'Fergie_6'),
],
);
}
}
class CustomTabBar extends AnimatedWidget implements PreferredSizeWidget {
CustomTabBar({ this.pageController, this.pageNames })
: super(listenable: pageController);
final PageController pageController;
final List<String> pageNames;
@override
final Size preferredSize = new Size(0.0, 40.0);
@override
Widget build(BuildContext context) {
TextTheme textTheme = Theme
.of(context)
.textTheme;
return new Container(
height: 40.0,
margin: const EdgeInsets.all(10.0),
padding: const EdgeInsets.symmetric(horizontal: 20.0),
decoration: new BoxDecoration(
color: Colors.grey.shade800.withOpacity(0.5),
borderRadius: new BorderRadius.circular(20.0),
),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: new List.generate(pageNames.length, (int index) {
return new InkWell(
child: new Text(
pageNames[index],
style: textTheme.subhead.copyWith(
color: Colors.white.withOpacity(
index == pageController.page ? 1.0 : 0.2,
),
)
),
onTap: () {
pageController.animateToPage(
index,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 300),
);
}
);
})
.toList(),
),
);
}
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => new _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
PageController _pageController = new PageController(initialPage: 2);
@override
build(BuildContext context) {
final Map<String, Widget> pages = <String, Widget>{
'My Music': new Center(
child: new Text('My Music not implemented'),
),
'Shared': new Center(
child: new Text('Shared not implemented'),
),
'Feed': new Feed(),
};
TextTheme textTheme = Theme
.of(context)
.textTheme;
return new Stack(
children: [
new Container(
decoration: new BoxDecoration(
gradient: new LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: [
const Color.fromARGB(255, 253, 72, 72),
const Color.fromARGB(255, 87, 97, 249),
],
stops: [0.0, 1.0],
)
),
child: new Align(
alignment: FractionalOffset.bottomCenter,
child: new Container(
padding: const EdgeInsets.all(10.0),
child: new Text(
'T I Z E',
style: textTheme.headline.copyWith(
color: Colors.grey.shade800.withOpacity(0.8),
fontWeight: FontWeight.bold,
),
),
)
)
),
new Scaffold(
backgroundColor: const Color(0x00000000),
appBar: new AppBar(
backgroundColor: const Color(0x00000000),
elevation: 0.0,
leading: new Center(
child: new ClipOval(
child: new Image.network(
'http://i.imgur.com/TtNPTe0.jpg',
),
),
),
actions: [
new IconButton(
icon: new Icon(Icons.add),
onPressed: () {
// TODO: implement
},
),
],
title: const Text('tofu\'s songs'),
bottom: new CustomTabBar(
pageController: _pageController,
pageNames: pages.keys.toList(),
),
),
body: new PageView(
controller: _pageController,
children: pages.values.toList(),
),
),
],
);
}
}
最后说明:在本示例中,我使用了常规 AppBar
,但您也可以使用 CustomScrollView
和 SliverAppBar
的固定 SliverAppBar
0.0。这将使内容在您的应用栏后面滚动时可见。很难让它与 PageView
一起很好地发挥作用,因为它需要一个固定大小的区域来放置自己。
您可以使用 Stack
并且可以将其子代设为 Positioned
或 Align
。
示例 #1(在 Stack
中使用 Positioned
)
Stack(
children: <Widget>[
Positioned(left: 0.0, child: Text("Top\nleft")),
Positioned(bottom: 0.0, child: Text("Bottom\nleft")),
Positioned(top: 0.0, right: 0.0, child: Text("Top\nright")),
Positioned(bottom: 0.0, right: 0.0, child: Text("Bottom\nright")),
Positioned(bottom: 0.0, right: 0.0, child: Text("Bottom\nright")),
Positioned(left: width / 2, top: height / 2, child: Text("Center")),
Positioned(top: height / 2, child: Text("Center\nleft")),
Positioned(top: height / 2, right: 0.0, child: Text("Center\nright")),
Positioned(left: width / 2, child: Text("Center\ntop")),
Positioned(left: width / 2, bottom: 0.0, child: Text("Center\nbottom")),
],
)
示例 #2(在 Stack
中使用 Align
)
Stack(
children: <Widget>[
Align(alignment: Alignment.center, child: Text("Center"),),
Align(alignment: Alignment.topRight, child: Text("Top\nRight"),),
Align(alignment: Alignment.centerRight, child: Text("Center\nRight"),),
Align(alignment: Alignment.bottomRight, child: Text("Bottom\nRight"),),
Align(alignment: Alignment.topLeft, child: Text("Top\nLeft"),),
Align(alignment: Alignment.centerLeft, child: Text("Center\nLeft"),),
Align(alignment: Alignment.bottomLeft, child: Text("Bottom\nLeft"),),
Align(alignment: Alignment.topCenter, child: Text("Top\nCenter"),),
Align(alignment: Alignment.bottomCenter, child: Text("Bottom\nCenter"),),
Align(alignment: Alignment(0.0, 0.5), child: Text("Custom\nPostition", style: TextStyle(color: Colors.red, fontSize: 20.0, fontWeight: FontWeight.w800),),),
],
);
截图:
这是另一个示例,展示了如何使用 Stack
和 Positioned
使其像 RelativeLayout
一样工作。
double _containerHeight = 120, _imageHeight = 80, _iconTop = 44, _iconLeft = 12, _marginLeft = 110;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: <Widget>[
Positioned(
left: 0,
right: 0,
height: _containerHeight,
child: Container(color: Colors.blue),
),
Positioned(
left: _iconLeft,
top: _iconTop,
child: Icon(Icons.settings, color: Colors.white),
),
Positioned(
right: _iconLeft,
top: _iconTop,
child: Icon(Icons.bubble_chart, color: Colors.white),
),
Positioned(
left: _iconLeft,
top: _containerHeight - _imageHeight / 2,
child: ClipOval(child: Image.asset("assets/images/profile.jpg", fit: BoxFit.cover, height: _imageHeight, width: _imageHeight)),
),
Positioned(
left: _marginLeft,
top: _containerHeight - (_imageHeight / 2.5),
child: Text("CopsOnRoad", style: TextStyle(color: Colors.white, fontWeight: FontWeight.w500, fontSize: 18)),
),
Positioned.fill(
left: _marginLeft,
top: _containerHeight + (_imageHeight / 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
children: <Widget>[
Text("2", style: TextStyle(fontWeight: FontWeight.bold)),
Text("Gold", style: TextStyle(color: Colors.grey)),
],
),
Column(
children: <Widget>[
Text("22", style: TextStyle(fontWeight: FontWeight.bold)),
Text("Silver", style: TextStyle(color: Colors.grey)),
],
),
Column(
children: <Widget>[
Text("28", style: TextStyle(fontWeight: FontWeight.bold)),
Text("Bronze", style: TextStyle(color: Colors.grey)),
],
),
Container(),
],
),
),
],
),
);
}
类似于 Android 的 RelativeLayout
(实际上更强大)是 align_positioned
包中的 AlignPositioned
小部件:
https://pub.dev/packages/align_positioned
来自其文档:
When your desired layout feels too complex for Columns and Rows,
AlignPositioned is a real life saver. Flutter is very composable,
which is good, but sometimes it's unnecessarily complex to translate
some layout requirement into a composition of simpler widgets.
The AlignPositioned aligns, positions, sizes, rotates and transforms
its child in relation to both the container and the child itself. In
other words, it lets you easily and directly define where and how a
widget should appear in relation to another.
For example, you can tell it to position the top-left of its child at
15 pixels to the left of the top-left corner of the container, plus
move it two thirds of the child's height to the bottom plus 10 pixels,
and then rotate 15 degrees. Do you even know how to start doing this
by composing basic Flutter widgets? Maybe, but with AlignPositioned
it's much easier, and it takes a single widget.
不过,问题中的具体例子很简单,反正我就用Row
s,Column
s等。 注意:我是这个包的作者。
有没有办法实现类似于 RelativeLayout
在 Android 上所做的事情?
特别是我正在寻找类似于 centerInParent
、layout_below:<layout_id>
、layout_above:<layout_id>
和 alignParentLeft
有关 RelativeLayout 的更多参考:https://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html
编辑:这是一个依赖于 RelativeLayout
所以在上图中,在顶部,"tofu's Songs" 文本在 RelativeLayout
内对齐为 centerInParent
。而其他 2 个是 alignParentLeft
和 alignParentRight
在火焰图标所在的每个单元格上,其底部的赞数围绕火焰图标的中心对齐。此外,每个单元格的顶部和底部标题分别与图像头像的右侧和顶部和底部对齐。
Flutter 布局通常使用 Column
, Row
and Stack
widgets. These widgets take constructor arguments that specify rules for how the children are laid out relative to the parent, and you can also influence layout of individual children by wrapping them in Expanded
, Flexible
, Positioned
, Align
, or Center
小部件树构建。
也可以使用 CustomMultiChildLayout
. This is how Scaffold
is implemented internally, and an example of how to use it in an app appears in the Shrine demo. You can also use LayoutBuilder
or CustomPaint
, or go down a layer and extend RenderObject
as shown in the sector example 构建复杂的布局。像这样手动进行布局需要更多工作,并且在边角情况下更容易出错,所以如果可以的话,我会尝试使用高级布局原语。
回答您的具体问题:
- 使用
AppBar
的leading
和trailing
参数来定位您的应用栏元素。如果您想改用Row
,请使用mainAxisAlignment
ofMainAxisAlignment.spaceBetween
. - 使用
Row
和CrossAxisAlignment.center
的crossAxisAlignment
将火焰图标和数字放在下方。 - 使用
Column
with amainAxisAlignment
ofMainAxisAlignment.spaceBetween
to position your top and bottom title. (You should consider usingListTile
来布置列表图块,但如果这样做,您将失去对精确定位的控制。)
这是实现您提供的设计的代码片段。在此示例中,我使用 IntrinsicHeight
来确定歌曲块的高度,但您可以通过将它们硬编码为固定高度来提高性能。
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
brightness: Brightness.dark,
primaryColorBrightness: Brightness.dark,
),
home: new HomeScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class Song extends StatelessWidget {
const Song({ this.title, this.author, this.likes });
final String title;
final String author;
final int likes;
@override
Widget build(BuildContext context) {
TextTheme textTheme = Theme
.of(context)
.textTheme;
return new Container(
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
decoration: new BoxDecoration(
color: Colors.grey.shade200.withOpacity(0.3),
borderRadius: new BorderRadius.circular(5.0),
),
child: new IntrinsicHeight(
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Container(
margin: const EdgeInsets.only(top: 4.0, bottom: 4.0, right: 10.0),
child: new CircleAvatar(
backgroundImage: new NetworkImage(
'http://thecatapi.com/api/images/get?format=src'
'&size=small&type=jpg#${title.hashCode}'
),
radius: 20.0,
),
),
new Expanded(
child: new Container(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(title, style: textTheme.subhead),
new Text(author, style: textTheme.caption),
],
),
),
),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 5.0),
child: new InkWell(
child: new Icon(Icons.play_arrow, size: 40.0),
onTap: () {
// TODO(implement)
},
),
),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 5.0),
child: new InkWell(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Icon(Icons.favorite, size: 25.0),
new Text('${likes ?? ''}'),
],
),
onTap: () {
// TODO(implement)
},
),
),
],
),
),
);
}
}
class Feed extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new ListView(
children: [
new Song(title: 'Trapadelic lobo', author: 'lillobobeats', likes: 4),
new Song(title: 'Different', author: 'younglowkey', likes: 23),
new Song(title: 'Future', author: 'younglowkey', likes: 2),
new Song(title: 'ASAP', author: 'tha_producer808', likes: 13),
new Song(title: '', author: 'TraphousePeyton'),
new Song(title: 'Something sweet...', author: '6ryan'),
new Song(title: 'Sharpie', author: 'Fergie_6'),
],
);
}
}
class CustomTabBar extends AnimatedWidget implements PreferredSizeWidget {
CustomTabBar({ this.pageController, this.pageNames })
: super(listenable: pageController);
final PageController pageController;
final List<String> pageNames;
@override
final Size preferredSize = new Size(0.0, 40.0);
@override
Widget build(BuildContext context) {
TextTheme textTheme = Theme
.of(context)
.textTheme;
return new Container(
height: 40.0,
margin: const EdgeInsets.all(10.0),
padding: const EdgeInsets.symmetric(horizontal: 20.0),
decoration: new BoxDecoration(
color: Colors.grey.shade800.withOpacity(0.5),
borderRadius: new BorderRadius.circular(20.0),
),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: new List.generate(pageNames.length, (int index) {
return new InkWell(
child: new Text(
pageNames[index],
style: textTheme.subhead.copyWith(
color: Colors.white.withOpacity(
index == pageController.page ? 1.0 : 0.2,
),
)
),
onTap: () {
pageController.animateToPage(
index,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 300),
);
}
);
})
.toList(),
),
);
}
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => new _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
PageController _pageController = new PageController(initialPage: 2);
@override
build(BuildContext context) {
final Map<String, Widget> pages = <String, Widget>{
'My Music': new Center(
child: new Text('My Music not implemented'),
),
'Shared': new Center(
child: new Text('Shared not implemented'),
),
'Feed': new Feed(),
};
TextTheme textTheme = Theme
.of(context)
.textTheme;
return new Stack(
children: [
new Container(
decoration: new BoxDecoration(
gradient: new LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: [
const Color.fromARGB(255, 253, 72, 72),
const Color.fromARGB(255, 87, 97, 249),
],
stops: [0.0, 1.0],
)
),
child: new Align(
alignment: FractionalOffset.bottomCenter,
child: new Container(
padding: const EdgeInsets.all(10.0),
child: new Text(
'T I Z E',
style: textTheme.headline.copyWith(
color: Colors.grey.shade800.withOpacity(0.8),
fontWeight: FontWeight.bold,
),
),
)
)
),
new Scaffold(
backgroundColor: const Color(0x00000000),
appBar: new AppBar(
backgroundColor: const Color(0x00000000),
elevation: 0.0,
leading: new Center(
child: new ClipOval(
child: new Image.network(
'http://i.imgur.com/TtNPTe0.jpg',
),
),
),
actions: [
new IconButton(
icon: new Icon(Icons.add),
onPressed: () {
// TODO: implement
},
),
],
title: const Text('tofu\'s songs'),
bottom: new CustomTabBar(
pageController: _pageController,
pageNames: pages.keys.toList(),
),
),
body: new PageView(
controller: _pageController,
children: pages.values.toList(),
),
),
],
);
}
}
最后说明:在本示例中,我使用了常规 AppBar
,但您也可以使用 CustomScrollView
和 SliverAppBar
的固定 SliverAppBar
0.0。这将使内容在您的应用栏后面滚动时可见。很难让它与 PageView
一起很好地发挥作用,因为它需要一个固定大小的区域来放置自己。
您可以使用 Stack
并且可以将其子代设为 Positioned
或 Align
。
示例 #1(在 Stack
中使用 Positioned
)
Stack(
children: <Widget>[
Positioned(left: 0.0, child: Text("Top\nleft")),
Positioned(bottom: 0.0, child: Text("Bottom\nleft")),
Positioned(top: 0.0, right: 0.0, child: Text("Top\nright")),
Positioned(bottom: 0.0, right: 0.0, child: Text("Bottom\nright")),
Positioned(bottom: 0.0, right: 0.0, child: Text("Bottom\nright")),
Positioned(left: width / 2, top: height / 2, child: Text("Center")),
Positioned(top: height / 2, child: Text("Center\nleft")),
Positioned(top: height / 2, right: 0.0, child: Text("Center\nright")),
Positioned(left: width / 2, child: Text("Center\ntop")),
Positioned(left: width / 2, bottom: 0.0, child: Text("Center\nbottom")),
],
)
示例 #2(在 Stack
中使用 Align
)
Stack(
children: <Widget>[
Align(alignment: Alignment.center, child: Text("Center"),),
Align(alignment: Alignment.topRight, child: Text("Top\nRight"),),
Align(alignment: Alignment.centerRight, child: Text("Center\nRight"),),
Align(alignment: Alignment.bottomRight, child: Text("Bottom\nRight"),),
Align(alignment: Alignment.topLeft, child: Text("Top\nLeft"),),
Align(alignment: Alignment.centerLeft, child: Text("Center\nLeft"),),
Align(alignment: Alignment.bottomLeft, child: Text("Bottom\nLeft"),),
Align(alignment: Alignment.topCenter, child: Text("Top\nCenter"),),
Align(alignment: Alignment.bottomCenter, child: Text("Bottom\nCenter"),),
Align(alignment: Alignment(0.0, 0.5), child: Text("Custom\nPostition", style: TextStyle(color: Colors.red, fontSize: 20.0, fontWeight: FontWeight.w800),),),
],
);
截图:
这是另一个示例,展示了如何使用 Stack
和 Positioned
使其像 RelativeLayout
一样工作。
double _containerHeight = 120, _imageHeight = 80, _iconTop = 44, _iconLeft = 12, _marginLeft = 110;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: <Widget>[
Positioned(
left: 0,
right: 0,
height: _containerHeight,
child: Container(color: Colors.blue),
),
Positioned(
left: _iconLeft,
top: _iconTop,
child: Icon(Icons.settings, color: Colors.white),
),
Positioned(
right: _iconLeft,
top: _iconTop,
child: Icon(Icons.bubble_chart, color: Colors.white),
),
Positioned(
left: _iconLeft,
top: _containerHeight - _imageHeight / 2,
child: ClipOval(child: Image.asset("assets/images/profile.jpg", fit: BoxFit.cover, height: _imageHeight, width: _imageHeight)),
),
Positioned(
left: _marginLeft,
top: _containerHeight - (_imageHeight / 2.5),
child: Text("CopsOnRoad", style: TextStyle(color: Colors.white, fontWeight: FontWeight.w500, fontSize: 18)),
),
Positioned.fill(
left: _marginLeft,
top: _containerHeight + (_imageHeight / 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
children: <Widget>[
Text("2", style: TextStyle(fontWeight: FontWeight.bold)),
Text("Gold", style: TextStyle(color: Colors.grey)),
],
),
Column(
children: <Widget>[
Text("22", style: TextStyle(fontWeight: FontWeight.bold)),
Text("Silver", style: TextStyle(color: Colors.grey)),
],
),
Column(
children: <Widget>[
Text("28", style: TextStyle(fontWeight: FontWeight.bold)),
Text("Bronze", style: TextStyle(color: Colors.grey)),
],
),
Container(),
],
),
),
],
),
);
}
类似于 Android 的 RelativeLayout
(实际上更强大)是 align_positioned
包中的 AlignPositioned
小部件:
https://pub.dev/packages/align_positioned
来自其文档:
When your desired layout feels too complex for Columns and Rows, AlignPositioned is a real life saver. Flutter is very composable, which is good, but sometimes it's unnecessarily complex to translate some layout requirement into a composition of simpler widgets.
The AlignPositioned aligns, positions, sizes, rotates and transforms its child in relation to both the container and the child itself. In other words, it lets you easily and directly define where and how a widget should appear in relation to another.
For example, you can tell it to position the top-left of its child at 15 pixels to the left of the top-left corner of the container, plus move it two thirds of the child's height to the bottom plus 10 pixels, and then rotate 15 degrees. Do you even know how to start doing this by composing basic Flutter widgets? Maybe, but with AlignPositioned it's much easier, and it takes a single widget.
不过,问题中的具体例子很简单,反正我就用Row
s,Column
s等。 注意:我是这个包的作者。