有没有办法实现类似于 Android RelativeLayout上的功能?
Android RelativeLayout
特别是我在寻找类似的东西来centerInParent,layout_below:<layout_id>,layout_above:<layout_id>,和alignParentLeft
centerInParent,layout_below:<layout_id>
layout_above:<layout_id>
alignParentLeft
有关RelativeLayout的更多参考:https ://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html
编辑:这是一个依赖于布局的示例 RelativeLayout
RelativeLayout
屏幕截图
因此,在上图中的顶部,“豆腐的歌曲”文本与centerInParent内对齐RelativeLayout。而其他2个是alignParentLeft和alignParentRight
centerInParent
alignParentRight
在每个带有火图标的单元格上,其底部的点赞次数围绕着火图标的中心对齐。同样,每个单元格的顶部和底部标题分别与图像头像的右侧和顶部和底部对齐。
Flutter 正在使用的树通常建立 Column, Row and Stack widgets. 这些小部件采取了怎样指定规则构造函数参数 的儿童相对于父摆出来,你也可以影响 通过包裹它们成为个体儿童的布局 Expanded, Flexible, Positioned, Align, or Center widgets.
Column
Row
Stack
Expanded
Flexible
Positioned
Align
Center
It is also possible to build complex layouts using 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. Doing your layouts manually like this is more work and creates more potential for errors in corner cases, so I would try to get by with the high-level layout primitives if you can.
CustomMultiChildLayout
Scaffold
LayoutBuilder
CustomPaint
RenderObject
To answer your specific questions:
leading
trailing
AppBar
mainAxisAlignment
MainAxisAlignment.spaceBetween
crossAxisAlignment
CrossAxisAlignment.center
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有一个elevation的0.0。这将使内容在滚动到应用程序栏后面时可见。这是棘手得到这个与发挥很好PageView,因为它期待一个固定大小的区域,本身奠定了进去。
CustomScrollView
SliverAppBar
elevation