有没有办法实现类似于RelativeLayout
在Android上的功能?
特别是我正在寻找与centerInParent
,layout_below:<layout_id>
,layout_above:<layout_id>
和alignParentLeft
有关RelativeLayout的更多参考:https://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html
编辑:以下是依赖RelativeLayout
所以在上面的图片中,顶部是豆腐的歌曲&#34;文本在centerInParent
内对齐为RelativeLayout
。而另外2个是alignParentLeft
和alignParentRight
在火图标所在的每个单元格上,底部的喜欢数量围绕火焰图标的中心对齐。此外,每个单元格上的顶部和底部标题分别与图像化身的右侧和顶部和底部对齐。
答案 0 :(得分:164)
Flutter布局通常使用Column
,Row
和Stack
窗口小部件的树构建。这些小部件采用构造函数参数来指定子项相对于父项的布局规则,并且还可以通过将它们包含在Expanded
,Flexible
,Positioned
中来影响单个子项的布局。 ,Align
或Center
小部件。
也可以使用CustomMultiChildLayout
构建复杂的布局。这就是Scaffold
在内部实施的方式,并在Shrine demo中显示了如何在应用中使用它的示例。您也可以使用LayoutBuilder
或CustomPaint
,或向下查看图层并展开RenderObject
,如sector example所示。像这样手动进行布局是更多的工作,并且在极端情况下产生更多的错误可能性,所以如果可以的话,我会尝试使用高级布局基元。
回答您的具体问题:
AppBar
的leading
和trailing
参数来定位您的应用栏元素。如果您想使用Row
,请使用mainAxisAlignment
MainAxisAlignment.spaceBetween
。Row
crossAxisAlignment
CrossAxisAlignment.center
mainAxisAlignment
将火图标和数字放在下面。Column
MainAxisAlignment.spaceBetween
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(),
),
),
],
);
}
}
来定位您的顶部和底部标题。 (您应该考虑使用ListTile
来布置列表切片,但是如果您这样做,您将无法控制确切的定位。)以下是实现您提供的设计的代码段。在这个例子中,我使用IntrinsicHeight
来确定歌曲块的高度,但是你可以通过将它们硬编码到固定的高度来提高性能。
AppBar
最后注意事项:在此示例中,我使用了常规CustomScrollView
,但您也可以使用SliverAppBar
,其中elevation
的{{1}}为PageView
。当它滚动到您的应用栏后面时,这将使内容可见。让这个与dtype('<i8')
很好地配合是很棘手的,因为它期望一个固定大小的区域可以自己进入。
答案 1 :(得分:10)
您可以使用Stack
,并将其子级设置为Positioned
或Align
。
示例1 (在Positioned
中使用Stack
)
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 (在Align
中使用Stack
)
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),),),
],
);
屏幕截图:
答案 2 :(得分:2)
与RelativeLayout
包中的AlignPositioned
小部件类似,它类似于Android的align_positioned
(实际上更强大):
https://pub.dev/packages/align_positioned
来自其文档:
当所需的布局对于“列”和“行”而言过于复杂时, AlignPositioned是真正的救星。 Flutter非常容易组合, 很好,但是有时候翻译的过程不必要地复杂 一些布局要求组合成更简单的小部件。
AlignPositioned对齐,定位,调整大小,旋转和变换 它的子项与容器和子项本身有关。在 换句话说,它可以让您轻松直接地定义 小部件应该相对于另一个出现。
例如,您可以告诉它将其子项的左上角放在 容器左上角的左侧15像素,再加上 将孩子的高度的三分之二移到底部再加上10像素, 然后旋转15度你甚至不知道如何开始这样做 通过组成基本的Flutter小部件?也许可以,但是使用AlignPositioned 这要容易得多,并且只需要一个小部件。
但是,问题中的特定示例非常简单,无论如何我只会使用Row
,Column
等。 注意:我是这个软件包的作者。
答案 3 :(得分:1)
这里是另一个示例,说明如何将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(),
],
),
),
],
),
);
}