Equivalente di RelativeLayout in Flutter


86

C'è un modo per implementare qualcosa di simile a quello che RelativeLayoutfa su Android?

In particolare, sto cercando qualcosa di simile a centerInParent, layout_below:<layout_id>, layout_above:<layout_id>, ealignParentLeft

Per ulteriori riferimenti su RelativeLayout: https://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html

EDIT: ecco un esempio di layout basato su RelativeLayout

Immagine dello schermo

Quindi nell'immagine sopra, in alto, il testo "Tofu's Songs" è allineato come centerInParentall'interno di un file RelativeLayout. Mentre gli altri 2 sono alignParentLeftealignParentRight

In ogni cella, dove si trova l'icona del fuoco, il numero di Mi piace nella parte inferiore di essa è allineato attorno al centro dell'icona della fiamma. Inoltre, i titoli in alto e in basso su ciascuna cella sono allineati rispettivamente a destra e in alto e in basso nell'avatar dell'immagine.

Risposte:


214

Layout Flutter sono di solito costruiti utilizzando un albero di Column, Rowe Stackwidget. Questi widget prendono argomenti del costruttore che specificano le regole per come i bambini sono disposti rispetto al genitore, e si può anche influenzare il layout dei singoli bambini avvolgendoli in Expanded, Flexible, Positioned, Align, o Centerwidget.

È anche possibile costruire layout complessi utilizzando CustomMultiChildLayout. Questo è il modo in cui Scaffoldviene implementato internamente e un esempio di come usarlo in un'app appare nella demo di Shrine . Puoi anche usare LayoutBuildero CustomPaint, o scendere di un livello ed estenderlo RenderObjectcome mostrato nell'esempio del settore . Fare i layout manualmente in questo modo è più lavoro e crea più possibilità di errori nei casi d'angolo, quindi proverei a cavarmela con le primitive di layout di alto livello se puoi.

Per rispondere alle tue domande specifiche:

  • Usa gli argomenti leadinge per posizionare gli elementi della barra dell'app. Se invece vuoi usare a , usa a of .trailingAppBarRowmainAxisAlignmentMainAxisAlignment.spaceBetween
  • Usa un Rowcon un crossAxisAlignmentdi CrossAxisAlignment.centerper posizionare l'icona del fuoco e il numero sotto.
  • Usa un Columncon un mainAxisAlignmentdi MainAxisAlignment.spaceBetweenper posizionare il titolo in alto e in basso. (Dovresti considerare l'utilizzo ListTileper disporre le tessere dell'elenco, ma perderai il controllo sul posizionamento esatto se lo fai.)

Ecco uno snippet di codice che implementa il design che hai fornito. In questo esempio ho usato un IntrinsicHeightper determinare l'altezza delle tessere della canzone, ma puoi migliorare le prestazioni codificandole a un'altezza fissa.

immagine dello schermo

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

Nota finale: in questo esempio, ho usato un normale AppBar, ma potresti anche usare un CustomScrollViewcon un appuntato SliverAppBarche ha elevation0,0. Ciò renderebbe il contenuto visibile mentre scorre dietro la barra dell'app. È difficile farlo funzionare bene PageView, perché ci si aspetta un'area di dimensioni fisse in cui disporsi.


Non consiglierei di omettere IntrinsicHeight poiché la dimensione del carattere può essere modificata dall'utente e il layout può facilmente interrompersi.
Lukasz Ciastko,

25

Puoi usare Stacke puoi avere i suoi figli come Positionedo Align.

Esempio # 1 (utilizzoPositionedinStack)

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")),
  ],
)

Esempio # 2 (utilizzoAligninStack)

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

Immagine dello schermo:

inserisci qui la descrizione dell'immagine


1
Davvero utile, penso che quello che la maggior parte degli sviluppatori di Android stia cercando sia un layout come Constraint Layout. C'è qualcosa del genere in svolazzare?
user3833732

1
@ user3833732 Puoi praticamente ottenere qualsiasi cosa usando il widget integrato di Flutter. Se hai un layout e pensi di non essere in grado di implementarlo utilizzando Flutter, pubblicalo come domanda e inviami un messaggio, cercherò di rispondere.
CopsOnRoad

3

Ecco un altro esempio per mostrare come Stackinsieme a Positionedpuò essere utilizzato per farlo funzionare come RelativeLayout.

inserisci qui la descrizione dell'immagine

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

2

Simile a quello di Android RelativeLayout(e in effetti più potente) è il AlignPositionedwidget del align_positionedpacchetto:

https://pub.dev/packages/align_positioned

Dai suoi documenti:

Quando il layout desiderato sembra troppo complesso per colonne e righe, AlignPositioned è un vero risparmio di vita. Flutter è molto componibile, il che è buono, ma a volte è inutilmente complesso tradurre alcuni requisiti di layout in una composizione di widget più semplici.

AlignPositioned allinea, posiziona, dimensiona, ruota e trasforma il suo bambino in relazione sia al contenitore che al bambino stesso. In altre parole, ti consente di definire facilmente e direttamente dove e come deve apparire un widget in relazione a un altro.

Ad esempio, puoi dirgli di posizionare l'angolo in alto a sinistra del suo bambino a 15 pixel a sinistra dell'angolo in alto a sinistra del contenitore, oltre a spostarlo di due terzi dell'altezza del bambino verso il basso più 10 pixel, e poi ruotare di 15 gradi. Sai anche come iniziare a farlo componendo widget Flutter di base? Forse, ma con AlignPositioned è molto più semplice e richiede un singolo widget.

Tuttavia, l'esempio specifico nella domanda è abbastanza semplice, userei comunque Rows, Columns ecc. Nota: sono l'autore di questo pacchetto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.