Entspricht RelativeLayout in Flutter

81

Gibt es eine Möglichkeit, etwas Ähnliches RelativeLayoutwie auf Android zu implementieren ?

Insbesondere suche ich nach etwas ähnliches centerInParent, layout_below:<layout_id>, layout_above:<layout_id>, undalignParentLeft

Weitere Informationen zu RelativeLayout finden Sie unter https://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html

BEARBEITEN: Hier ist ein Beispiel für ein Layout, auf das Sie sich verlassen RelativeLayout

Bildschirmfoto

Im obigen Bild oben ist der Text "Tofus Songs" wie centerInParentin a ausgerichtet RelativeLayout. Während die anderen 2 sind alignParentLeftundalignParentRight

In jeder Zelle, in der sich das Feuersymbol befindet, ist die Anzahl der Likes unten in der Mitte des Flammensymbols ausgerichtet. Außerdem werden der obere und untere Titel jeder Zelle rechts und oben bzw. unten im Bildavatar ausgerichtet.

user3217522
quelle

Antworten:

209

Flutter - Layouts sind in der Regel gebaut , einen Baum zu verwenden Column, Rowund StackWidgets. Diese Widgets nehmen Konstruktor Argumente , die Regeln für die angeben , wie die Kinder angelegt relativ zum übergeordneten, und Sie können auch das Layout der einzelnen Kinder beeinflussen , indem sie in Einwickeln Expanded, Flexible, Positioned, Align, oder CenterWidgets.

Es ist auch möglich, komplexe Layouts mit zu erstellen CustomMultiChildLayout. So wird Scaffoldes intern implementiert, und ein Beispiel für die Verwendung in einer App finden Sie in der Shrine-Demo . Sie können auch LayoutBuilderoder verwenden CustomPaintoder eine Ebene nach unten gehen und erweitern, RenderObjectwie im Sektorbeispiel gezeigt . Das manuelle Erstellen Ihrer Layouts ist mehr Arbeit und schafft in Eckfällen mehr Fehlerpotential. Daher würde ich versuchen, mit den übergeordneten Layout-Grundelementen auszukommen, wenn Sie können.

So beantworten Sie Ihre spezifischen Fragen:

  • Verwenden Sie die Argumente leadingund trailing, AppBarum Ihre App-Leistenelemente zu positionieren. Wenn Sie Rowstattdessen ein verwenden möchten , verwenden Sie ein mainAxisAlignmentvon MainAxisAlignment.spaceBetween.
  • Verwenden Sie ein Rowmit einem crossAxisAlignmentvon, CrossAxisAlignment.centerum das Feuersymbol und die Nummer darunter zu positionieren.
  • Verwenden Sie ein Columnmit einem mainAxisAlignmentvon, MainAxisAlignment.spaceBetweenum Ihren oberen und unteren Titel zu positionieren. (Sie sollten in Betracht ziehen ListTile, die Listenkacheln zu verwenden, verlieren jedoch die Kontrolle über die genaue Positionierung, wenn Sie dies tun.)

Hier ist ein Code-Snippet, das das von Ihnen bereitgestellte Design implementiert. In diesem Beispiel habe ich ein verwendet IntrinsicHeight, um die Höhe der Song-Kacheln zu bestimmen. Sie können die Leistung jedoch verbessern, indem Sie sie fest auf eine feste Höhe codieren.

Bildschirmfoto

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

Abschließender Hinweis: In diesem Beispiel habe ich eine regelmäßige AppBar, aber man könnte auch eine Verwendung CustomScrollViewmit einer gepinnten , SliverAppBardie eine hat elevationvon 0.0. Dadurch wird der Inhalt sichtbar, wenn er hinter Ihrer App-Leiste angezeigt wird. Es ist schwierig, dies zum Spielen zu bringen PageView, da erwartet wird, dass sich ein Bereich mit fester Größe selbst auslegt.

Collin Jackson
quelle
Ich würde nicht empfehlen, IntrinsicHeight wegzulassen, da die Schriftgröße vom Benutzer geändert werden kann und das Layout leicht beschädigt werden kann.
Lukasz Ciastko
22

Sie können verwenden Stackund seine Kinder als Positionedoder haben Align.

Beispiel 1 (VerwendenPositionedinStack)

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

Beispiel 2 (VerwendenAligninStack)

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

Bildschirmfoto:

Geben Sie hier die Bildbeschreibung ein

CopsOnRoad
quelle
1
Wirklich hilfreich, ich denke, was die meisten Entwickler von Android suchen, ist ein Layout wie Constraint Layout. Gibt es so etwas im Flattern?
user3833732
1
@ user3833732 Mit dem in Flutter integrierten Widget können Sie so ziemlich alles erreichen. Wenn Sie ein Layout haben und der Meinung sind, dass Sie es mit Flutter nicht implementieren können, stellen Sie es als Frage und senden Sie mir eine Nachricht. Ich werde versuchen, es zu beantworten.
CopsOnRoad
3

Hier ist ein weiteres Beispiel, um zu zeigen, wie Stackzusammen mit Positionedverwendet werden kann, damit es funktioniert RelativeLayout.

Geben Sie hier die Bildbeschreibung ein

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

Ähnlich wie bei Android RelativeLayout(und tatsächlich leistungsfähiger) ist das AlignPositionedWidget aus dem align_positionedPaket:

https://pub.dev/packages/align_positioned

Aus seinen Dokumenten:

Wenn sich Ihr gewünschtes Layout für Spalten und Zeilen zu komplex anfühlt, ist AlignPositioned ein echter Lebensretter. Flutter ist sehr komponierbar, was gut ist, aber manchmal ist es unnötig komplex, einige Layoutanforderungen in eine Komposition aus einfacheren Widgets zu übersetzen.

Das AlignPositioned richtet sein untergeordnetes Element aus, positioniert es, dimensioniert es, dreht es und transformiert es in Bezug auf den Container und das untergeordnete Element selbst. Mit anderen Worten, Sie können einfach und direkt definieren, wo und wie ein Widget in Bezug auf ein anderes angezeigt werden soll.

Sie können beispielsweise festlegen, dass die obere linke Ecke des untergeordneten Elements 15 Pixel links von der oberen linken Ecke des Containers positioniert werden soll, und dass zwei Drittel der Größe des untergeordneten Elements nach unten plus 10 Pixel verschoben werden sollen 15 Grad drehen. Wissen Sie überhaupt, wie Sie damit beginnen können, indem Sie grundlegende Flutter-Widgets erstellen? Vielleicht, aber mit AlignPositioned ist es viel einfacher und es wird ein einziges Widget benötigt.

Das spezifische Beispiel in der Frage ist jedoch recht einfach. Ich würde sowieso nur Rows, Columns usw. verwenden.

MarcG
quelle