92 votes

Equivalent de RelativeLayout dans Flutter

Existe-t-il un moyen de mettre en œuvre 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érences sur RelativeLayout : https://developer.Android.com/reference/Android/widget/RelativeLayout.LayoutParams.html

EDIT : voici un exemple d'une mise en page qui s'appuie sur RelativeLayout

Screenshot

Ainsi, dans l'image ci-dessus, en haut, le texte "Les chansons du tofu" est aligné comme suit centerInParent à l'intérieur d'un RelativeLayout . Alors que les 2 autres sont alignParentLeft et alignParentRight

Sur chaque cellule, là où se trouve l'icône de feu, le nombre de likes en bas de celle-ci est aligné autour du centre de l'icône de feu. De même, le titre supérieur et inférieur de chaque cellule est aligné sur la droite et sur le haut et le bas de l'avatar de l'image, respectivement.

220voto

Collin Jackson Points 29995

Les mises en page de Flutter 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 les règles pour la disposition des enfants par rapport au parent, et vous pouvez également influencer la disposition des enfants individuels en les entourant de Expanded , Flexible , Positioned , Align ou Center les widgets.

Il est également possible de construire des mises en page complexes en utilisant CustomMultiChildLayout . C'est ainsi que Scaffold est implémenté en interne, et un exemple de son utilisation dans une application est présenté dans la page d'accueil du site Web de l'UE. Démonstration du sanctuaire . Vous pouvez également utiliser LayoutBuilder ou CustomPaint ou descendez d'une couche et étendez RenderObject comme indiqué dans le exemple de secteur . Réaliser vos mises en page manuellement de cette manière demande plus de travail et crée plus de risques d'erreurs dans les cas particuliers. J'essaierais donc d'utiliser les primitives de mise en page de haut niveau si vous le pouvez.

Pour répondre à vos questions spécifiques :

  • Utilisez le leading et trailing arguments pour AppBar pour positionner les éléments de votre barre d'application. Si vous souhaitez utiliser un Row à la place, utilisez un mainAxisAlignment de MainAxisAlignment.spaceBetween .
  • Utilisez un Row avec un crossAxisAlignment de CrossAxisAlignment.center pour positionner l'icône de feu et le numéro en dessous.
  • Utilisez un 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 faites cela).

Voici un extrait de code qui met en œuvre la conception que vous avez fournie. Dans cet exemple, j'ai utilisé un IntrinsicHeight pour déterminer la hauteur des tuiles de chansons, mais vous pouvez améliorer les performances en les codant en dur à une hauteur fixe.

screenshot

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 = Thee
      .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(),
          ),
        ),
      ],
    );
  }
}

Remarque finale : dans cet exemple, j'ai utilisé un fichier ordinaire AppBar mais vous pouvez aussi utiliser un CustomScrollView avec une épingle SliverAppBar qui a un elevation de 0.0. Cela rendrait le contenu visible lorsqu'il défile derrière votre barre d'application. Il est difficile de faire en sorte que cela se passe bien avec PageView parce qu'il s'attend à une zone de taille fixe où s'étendre.

0 votes

Je ne recommanderais pas d'omettre IntrinsicHeight car la taille de la police peut être modifiée par l'utilisateur et la mise en page peut facilement se briser.

29voto

CopsOnRoad Points 4705

Vous pouvez utiliser Stack et peut avoir ses enfants comme Positioned ou Align .

Exemple n° 1 (En utilisant Positioned sur 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 (En utilisant Align sur 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 :

enter image description here

2 votes

Vraiment utile, je pense que ce que la plupart des développeurs venant d'Android recherchent, c'est une mise en page comme Constraint Layout. Y a-t-il quelque chose comme ça dans Flutter ?

1 votes

@user3833732 Vous pouvez réaliser à peu près n'importe quoi en utilisant le widget intégré de Flutter. Si vous avez une mise en page et que vous pensez ne pas pouvoir l'implémenter en utilisant Flutter, posez-la comme question et envoyez-moi un message, j'essaierai d'y répondre.

3voto

CopsOnRoad Points 4705

Voici un autre exemple pour montrer comment Stack ainsi que Positioned peut être utilisé pour le faire fonctionner comme RelativeLayout .

enter image description here

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(),
            ],
          ),
        ),
      ],
    ),
  );
}

2voto

marcg Points 110

Semblable à celui d'Android RelativeLayout (et en fait plus puissant) est le AlignPositioned du widget align_positioned paquet :

https://pub.dev/packages/align_positioned

De ses docs :

Lorsque la mise en page souhaitée semble trop complexe pour Columns and Rows, AlignPositioned est une véritable bouée de sauvetage. Flutter est très composable, ce qui est bien, mais parfois il est inutilement complexe de traduire une exigence de mise en page en une composition de widgets plus simples.

L'AlignPositioned aligne, positionne, dimensionne, fait pivoter et transforme son enfant par rapport au conteneur et à l'enfant lui-même. En En d'autres termes, elle vous permet de définir facilement et directement où et comment un widget doit apparaître par rapport à un autre.

Par exemple, vous pouvez lui demander de positionner le haut gauche de son enfant à 15 pixels à la gauche du coin supérieur gauche du conteneur, plus de le déplacer de deux tiers de la hauteur de l'enfant vers le bas, plus 10 pixels, puis d'effectuer une rotation de 15 degrés. Savez-vous seulement comment commencer à faire cela en composant des widgets Flutter de base ? Peut-être, mais avec AlignPositioned c'est beaucoup plus facile, et cela ne prend qu'un seul widget.

Cependant, l'exemple spécifique de la question est assez simple, j'utiliserais simplement Row s, Column etc. de toute façon. Note : Je suis l'auteur de ce paquet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X