Est-il possible d'implémenter quelque chose de similaire à ce que RelativeLayout
fait sur Android?
En particulier, je cherche quelque chose de similaire à centerInParent
, layout_below:<layout_id>
, layout_above:<layout_id>
et alignParentLeft
Pour plus de référence sur RelativeLayout: https://developer.Android.com/reference/Android/widget/RelativeLayout.LayoutParams.html
EDIT: voici un exemple de mise en page utilisant RelativeLayout
Ainsi, dans l'image ci-dessus, en haut, le texte "Songs of tofu" est aligné comme suit: centerInParent
à l'intérieur d'un RelativeLayout
. Alors que les 2 autres sont alignParentLeft
et alignParentRight
Sur chaque cellule, où se trouve l'icône de feu, le nombre de goûts au bas de celle-ci est aligné autour du centre de l'icône de flamme. De plus, les titres du haut et du bas de chaque cellule sont alignés à droite, à droite et à gauche de l'avatar de l'image, respectivement.
Les mises en page flottantes sont généralement construites en utilisant un arbre de Column
, Row
et Stack
widgets. Ces widgets prennent des arguments de constructeur qui spécifient des règles pour la disposition des enfants par rapport au parent. Vous pouvez également influencer la présentation de chaque enfant en les encapsulant dans Expanded
, Flexible
, Positioned
, Align
, ou Center
widgets.
Il est également possible de construire des mises en page complexes en utilisant CustomMultiChildLayout
. Voici comment Scaffold
est implémenté en interne, et un exemple d'utilisation de cette application dans une application apparaît dans le Démo Shrine . Vous pouvez également utiliser LayoutBuilder
ou CustomPaint
, ou descendre un calque et étendre RenderObject
comme indiqué dans le exemple sectoriel . Faire vos mises en page manuellement de cette façon représente plus de travail et crée plus de risque d'erreur dans les virages, alors j'essaierais de me débrouiller avec les primitives de disposition de haut niveau si vous le pouvez.
Pour répondre à vos questions spécifiques:
leading
et trailing
de AppBar
pour positionner les éléments de la barre d’application. Si vous souhaitez utiliser un Row
à la place, utilisez un mainAxisAlignment
de MainAxisAlignment.spaceBetween
.Row
avec un crossAxisAlignment
de CrossAxisAlignment.center
pour positionner l’icône et le numéro de tir en dessous.Column
avec un mainAxisAlignment
de MainAxisAlignment.spaceBetween
pour positionner votre titre supérieur et inférieur. (Vous devriez envisager d'utiliser ListTile
pour disposer les tuiles de la liste, mais vous perdrez le contrôle du positionnement exact si vous le faites.)Voici un extrait de code qui implémente la conception que vous avez fournie. Dans cet exemple, j’ai utilisé un IntrinsicHeight
pour déterminer la hauteur des mosaïques de morceaux, mais vous pouvez améliorer les performances en les codant en dur à une hauteur fixe.
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(),
),
),
],
);
}
}
Note finale: Dans cet exemple, j’utilisais un AppBar
normal, mais vous pouvez aussi utiliser un CustomScrollView
avec un _ épinglé SliverAppBar
qui a un elevation
de 0.0 . Cela rendrait le contenu visible alors qu'il défilerait derrière la barre d'applications. Il est délicat de faire en sorte que cela fonctionne bien avec PageView
, car on s’attend à ce qu’une zone de taille fixe s’installe.
Vous pouvez utiliser Stack
et avoir ses enfants sous le nom de Positioned
ou Align
.
Exemple n ° 1 (Utilisation de Positioned
dans 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")),
],
)
Exemple n ° 2 (Utilisation de Align
dans 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),),),
],
);
Capture d'écran:
Voici un autre exemple pour montrer comment Stack
ainsi que Positioned
peuvent être utilisés pour le faire fonctionner comme 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(),
],
),
),
],
),
);
}