Флаттер макеты, как правило , построены с использованием дерева Column
, Row
и Stack
виджетов. Эти виджеты принимают аргументы конструкторы , которые определяют правила для того, как дети раскладывают по отношению к родителю, и вы можете также влиять на расположение отдельных детей, окружив их Expanded
, Flexible
, Positioned
, Align
или Center
виджеты.
Также возможно создание сложных макетов с использованием CustomMultiChildLayout
. Вот как Scaffold
это реализовано внутри, и пример того, как использовать это в приложении, представлен в демонстрации Shrine . Вы также можете использовать LayoutBuilder
или CustomPaint
, или перейти на уровень ниже и расширить, RenderObject
как показано в примере сектора . Выполнение ваших макетов вручную, как это, требует больше работы и создает больше возможностей для ошибок в крайних случаях, поэтому я бы попытался обойтись примитивами макета высокого уровня, если вы можете.
Чтобы ответить на ваши конкретные вопросы:
- Используйте
leading
и trailing
аргументы , AppBar
чтобы расположить штриховые элементы приложения. Если вы хотите использовать Row
вместо этого, используйте a mainAxisAlignment
of MainAxisAlignment.spaceBetween
.
- Используйте a
Row
с a crossAxisAlignment
или CrossAxisAlignment.center
для размещения значка огня и числа под ним.
- Используйте a
Column
с a mainAxisAlignment
или, MainAxisAlignment.spaceBetween
чтобы разместить верхний и нижний заголовок. (Вам следует подумать об использовании 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: () {
},
),
),
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: () {
},
),
),
],
),
),
);
}
}
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: () {
},
),
],
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
, но вы также можете использовать a CustomScrollView
с закрепленным SliverAppBar
, имеющим elevation
0,0. Это сделает контент видимым при прокрутке за панелью вашего приложения. Сложно заставить это хорошо играть PageView
, потому что ожидается, что область фиксированного размера будет располагаться в ней.
Вы можете использовать
Stack
и иметь его дочерние элементы какPositioned
илиAlign
.Пример # 1 (Использование
Positioned
inStack
)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
inStack
)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
(и на самом деле более мощный)AlignPositioned
виджет изalign_positioned
пакета:https://pub.dev/packages/align_positioned
Из его документов:
Однако конкретный пример в вопросе довольно прост, я бы все равно использовал
Row
s,Column
s и т. Д. Примечание: я являюсь автором этого пакета.источник