Flutter의 RelativeLayout과 동일


84

RelativeLayoutAndroid 에서 수행하는 것과 유사한 것을 구현하는 방법이 있습니까?

특히 내가 뭔가를 찾고 있어요 유사에 centerInParent, layout_below:<layout_id>, layout_above:<layout_id>, 및alignParentLeft

RelativeLayout에 대한 추가 참조 : https://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html

편집 : 여기에 의존하는 레이아웃의 예가 있습니다. RelativeLayout

스크린 샷

그래서의 이미지는 위의 상단에, "두부의 노래"텍스트는 다음과 같이 정렬 centerInParent, 안쪽 RelativeLayout. 다른 2 반면 alignParentLeftalignParentRight

화재 아이콘이있는 각 셀에서 아래쪽에있는 좋아요 수가 불꽃 아이콘 중앙을 기준으로 정렬됩니다. 또한 각 셀의 상단 및 하단 제목은 각각 이미지 아바타의 오른쪽과 상단 및 하단에 정렬됩니다.

답변:


213

Flutter 레이아웃은 일반적으로 Column, RowStack위젯 트리를 사용하여 빌드됩니다 . 이 위젯은 아이들이 부모를 기준으로 배치하는 방법에 대한 규칙을 지정 생성자 인수를, 그리고 당신은 또한에 그들을 배치하여 개별 아동의 레이아웃에 영향을 줄 수있다 Expanded, Flexible, Positioned, Align, 또는 Center위젯.

를 사용하여 복잡한 레이아웃을 만들 수도 있습니다 CustomMultiChildLayout. 이것이 Scaffold내부적으로 구현되는 방법이며 앱에서 사용하는 방법의 예가 Shrine 데모에 나타납니다 . 또는를 사용 LayoutBuilder하거나 섹터 예제에 표시된대로 CustomPaint레이어를 아래로 이동하여 확장 할 수도 있습니다 . 이와 같이 레이아웃을 수동으로 수행하면 더 많은 작업이 수행되고 코너 케이스에서 오류가 발생할 가능성이 더 커지므로 가능한 경우 고급 레이아웃 기본 요소를 사용하려고합니다.RenderObject

구체적인 질문에 답하려면 :

  • leadingtrailing인수를 사용 AppBar하여 앱 바 요소를 배치합니다. Row대신 a 를 사용하려면 mainAxisAlignmentof를 사용하십시오 MainAxisAlignment.spaceBetween.
  • 용도 RowA의 crossAxisAlignment의를 CrossAxisAlignment.center화재 아이콘과 숫자 아래에 위치 할 수 있습니다.
  • 용도 ColumnA의 mainAxisAlignment의를 MainAxisAlignment.spaceBetween상위 및 하위 제목을 배치합니다. ( ListTile목록 타일을 레이아웃하는 데 사용 하는 것을 고려해야 하지만 이렇게하면 정확한 위치를 제어 할 수 없게됩니다.)

다음은 제공 한 디자인을 구현하는 코드 스 니펫입니다. 이 예에서는 IntrinsicHeight노래 타일의 높이를 결정하는 데를 사용 했지만 고정 높이로 하드 코딩하여 성능을 향상시킬 수 있습니다.

스크린 샷

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

마지막 참고 :이 예에서는 일반을 사용 AppBar했지만 0.0 이 CustomScrollView고정 된 SliverAppBar을 사용할 수도 있습니다 elevation. 그러면 앱 바 뒤로 스크롤 할 때 콘텐츠가 표시됩니다. 멋지게 플레이를 할 수 까다 롭다 PageView는 자체로 배치하는 고정 된 크기의 영역을 기대하고 있기 때문에.


사용자가 글꼴 크기를 변경할 수 있고 레이아웃이 쉽게 깨질 수 있으므로 IntrinsicHeight를 생략하지 않는 것이 좋습니다.
Lukasz Ciastko

23

을 사용할 Stack수 있으며 그 자식을 Positioned또는 로 가질 수 있습니다 Align.

실시 예 # 1 (사용Positioned에서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")),
  ],
)

실시 예 # 2 (사용Align에서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),),),
  ],
);

스크린 샷 :

여기에 이미지 설명 입력


1
정말 도움이되는 것은 대부분의 Android 개발자가 찾고있는 것은 Constraint Layout과 같은 레이아웃입니다. 플러터에 그런 게 있나요?
user3833732

1
@ user3833732 Flutter 내장 위젯을 사용하여 거의 모든 것을 달성 할 수 있습니다. 레이아웃이 있는데 Flutter를 사용하여 구현할 수 없다고 생각되면 질문으로 게시하고 메시지를 보내 주시면 답변 해 드리겠습니다.
CopsOnRoad

3

여기에 방법을 보여주는 또 다른 예 Stack를 따라와 Positioned이 같은 일을하는 데 사용 될 수있다 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(),
            ],
          ),
        ),
      ],
    ),
  );
}

1

Android와 비슷하고 RelativeLayout실제로 더 강력한 AlignPositioned것은 align_positioned패키지 의 위젯입니다 .

https://pub.dev/packages/align_positioned

문서에서 :

원하는 레이아웃이 열과 행에 대해 너무 복잡하다고 느껴질 때 AlignPositioned는 실제 생명의 은인입니다. Flutter는 매우 구성이 가능하고 좋습니다. 그러나 때로는 일부 레이아웃 요구 사항을 더 간단한 위젯 구성으로 변환하는 것이 불필요하게 복잡합니다.

AlignPositioned는 컨테이너와 자식 자체와 관련하여 자식을 정렬, 위치, 크기, 회전 및 변형합니다. 즉, 위젯이 다른 위젯과 관련하여 표시되어야하는 위치와 방법을 쉽고 직접 정의 할 수 있습니다.

예를 들어, 컨테이너의 왼쪽 상단 모서리 왼쪽에있는 15 픽셀에 자식의 왼쪽 상단을 배치하고 자식 높이의 2/3에 10 픽셀을 더한 값으로 이동하도록 지시 할 수 있습니다. 15도 회전합니다. 기본 Flutter 위젯을 작성하여이를 시작하는 방법을 알고 있습니까? 아마도 AlignPositioned를 사용하면 훨씬 쉽고 하나의 위젯이 필요합니다.

그러나 질문의 ​​구체적인 예는 매우 간단 합니다. 어쨌든 Rows, Columns 등을 사용하겠습니다 . 참고 :이 패키지의 작성자입니다.


이것은 정말로 flutter에서 빠진 것입니다.
user5381191
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.