Trzepotanie układy są zwykle budowane za pomocą drzewa Column
, Row
i Stack
widgety. Te wzory przyjąć argumenty konstruktora, które określają zasady jak dzieci są określone w stosunku do rodziców, a także można wpływać na rozmieszczenie poszczególnych dzieci przez owijanie ich w Expanded
, Flexible
, Positioned
, Align
, lub Center
widgetów.
Możliwe jest również budowanie złożonych układów przy użyciu CustomMultiChildLayout
. Tak jest Scaffold
zaimplementowany wewnętrznie, a przykład użycia go w aplikacji pojawia się w wersji demonstracyjnej Shrine . Możesz także użyć LayoutBuilder
lub CustomPaint
, lub zejść w dół warstwy i rozszerzyć, RenderObject
jak pokazano na przykładzie sektora . Wykonywanie układów ręcznie, jak to, wymaga więcej pracy i stwarza więcej potencjalnych błędów w przypadkach narożnych, więc jeśli możesz, spróbuję poradzić sobie z prymitywami układu wysokiego poziomu.
Aby odpowiedzieć na Twoje pytania:
- Użyj argumentów
leading
i, aby ustawić elementy paska aplikacji. Jeśli chcesz użyć zamiast tego, użyj z .trailing
AppBar
Row
mainAxisAlignment
MainAxisAlignment.spaceBetween
- Użyj z
Row
z crossAxisAlignment
lub, CrossAxisAlignment.center
aby umieścić ikonę ognia i liczbę pod spodem.
- Użyj z
Column
z mainAxisAlignment
lub, MainAxisAlignment.spaceBetween
aby ustawić tytuł na górze i na dole. (Powinieneś rozważyć użycie ListTile
do rozłożenia kafelków listy, ale stracisz kontrolę nad dokładnym pozycjonowaniem, jeśli to zrobisz.)
Oto fragment kodu, który implementuje dostarczony przez Ciebie projekt. W tym przykładzie użyłem an IntrinsicHeight
do określenia wysokości kafelków utworu, ale możesz poprawić wydajność, zakodując je na stałe do stałej wysokości.
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(),
),
),
],
);
}
}
Uwaga końcowa: w tym przykładzie użyłem zwykłego AppBar
, ale możesz też użyć CustomScrollView
z przypiętą, SliverAppBar
która ma elevation
0,0. Dzięki temu treść byłaby widoczna, gdy przewija się za paskiem aplikacji. Trudno jest sprawić, by gra była przyjemna PageView
, ponieważ oczekuje się, że obszar o stałym rozmiarze będzie się układał.
Możesz używać
Stack
i mieć swoje elementy podrzędne jakoPositioned
lubAlign
.Przykład nr 1 (używanie
Positioned
wStack
)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")), ], )
Przykład nr 2 (używanie
Align
wStack
)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),),), ], );
Zrzut ekranu:
źródło
Oto kolejny przykład pokazujący, jak
Stack
wraz zPositioned
można wykorzystać, aby działał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(), ], ), ), ], ), ); }
źródło
Podobny do Androida
RelativeLayout
(a właściwie mocniejszy) jestAlignPositioned
widget zalign_positioned
pakietu:https://pub.dev/packages/align_positioned
Z jego dokumentów:
Jednak konkretny przykład w pytaniu jest dość prosty, i tak użyłbym po prostu
Row
s,Column
s itp. Uwaga: jestem autorem tego pakietu.źródło