Building Beautiful UIs with Flutter

Ashe·2019년 12월 14일
2

잿더미의 Flutter

목록 보기
5/8

Overview

이전 플러터 앱 제작보다 좀 더 깊게 들어갑니다.

Build the main user interface

이 섹션에서는 기본 앱을 채팅 앱으로 만듭니다.

  • 실시간으로 메세지를 보여줍니다.
  • 사용자는 텍스트 문자열 메세지를 입력하고 리턴 키 또는 보내기 아이콘을 눌러 보낼 수 있습니다.
  • IOS, Android 장치 모두에서 실행됩니다.

Create the main app sacffold

첫 번째로 추가할 요소는 앱의 정적인 제목을 보여주는 간단한 앱 표시 줄입니다. 이 코드 랩의 후속 섹션을 진행하면서 액션을 추가하고 UI를 점진적으로 추가합니다.

main.dart 파일은 Flutter 프로젝트의 lib 디렉토리에 있으며 앱을 시작하는 main() 함수를 포함합니다.

main()runApp() 함수 정의는 기본 앱과 동일합니다. runApp() 함수는 런타임에 앱 화면에 표시되는 위젯을 인수로 사용합니다. 앱의 UI는 Material Design을 사용하므로 새 MaterialApp 객체를 생성하고 이를 runApp() 함수에 전달합니다. 이 위젯은 위젯 트리의 루트가됩니다.

void main() {
  runApp(
  	new MaterialApp(
    	title: 'Friendlychat',
      home: new Scaffold(
        appBar: new AppBar(
        	title: new Text("FriendlyChat"),
        ),
      ),
    ),
  );
}

사용자가 앱에서 볼 수 있는 기본 화면을 지정하려면 MaterialApp의 속성에서 home 인수를 지정합니다. home 인수는 이 앱의 메인 UI 를 정의하는 위젯을 참조합니다. 위젯은 간단한 AppBar가 있는 Scaffold 위젯으로 구성됩니다.

Build the chat screen

채팅 화면의 토대를 마련하기 위한 앱을 두가지 하위 위젯으로 나눕니다. 변경되지 않는 루트 수준의 FriendlyChatApp 위젯과 메세지가 전송되고 내부 상태가 변경될 때 다시 빌드 할 수 있는 하위 ChatScreen입니다. 현재 이 두 클래스는 StatelessWidget을 상속받지만 ChatScreen은 나중에 StatefulWidget을 사용하도록 수정합니다.

void main() {
  runApp(new FriendlychatApp());
}

class FriendlychatApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return new MaterialApp(
    	title: 'Friendlychat',
      home: new ChatScreen(),
    );
  }
}

class ChatScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new AppBar(title: new Text('Friendlychat')),
      );
    )
  }
}

이 단계에서 Flutter의 몇 주요 개념을 소개합니다.

  • build() 메서드는 위젯을 표시되는 사용자 인터페이스의 일부를 정의합니다. 프레임워크는 위젯을 계층 구조에 삽입하거나 종속이 변경될 때 build() 메서드를 호출합니다.
  • @override 는 태그 된 메서드가 수퍼 클래스의 메서드를 대체함을 나타내는 Dart 어노테이션입니다.
  • Scaffold 및 AppBar와 같은 일부 위젯은 Material Design 앱에만 해당됩니다. 텍스트와 같은 다른 위젯은 일반적이며 모든 앱에서 사용 가능합니다. Flutter 프레임워크에 있는 다른 라이브러리의 위젯은 호화 가능하며 단일 앱에서 함께 동작할 수 있습니다.

Add a UI for composing messages

이번 섹션에서는 사용자가 문자 메세지를 입력하고 보낼 수 있는 사용자 정의 컨트롤을 작성하는 방법을 배웁니다. 장치에서 텍스트필드를 클릭하면 키보드가 나타납니다. 비어있지 않은 문자열을 입력하고 키보드에서 엔터키를 눌러 메세지를 보낼 수 있습니다. 또는 사용자 입력 필드 옆 보내기 버튼으로 입력 메세지를 보낼 수 있습니다.

지금은 메세지 작성 UI가 채팅 화면의 맨 위에 있지만 다음 단계에서 메세지를 표시하기 위한 UI를 추가하면 채팅 화면의 맨 아래로 이동합니다.

Add an interactive text input field

Flutter는 TextField라는 Material Design Widget을 기본적으로 제공합니다. 입력 필드의 값을 저장하고 동작을 사용자정의하기 위해 StatefulWidget입니다. 상태는 위젯이 빌드될 때 동기적으로 읽을 수 있고 위젯 수명동안 변경될 수 있는 정보입니다.

Flutter에서 위젯에 변경되는 데이터를 표현하길 원한다면 데이터를 State객체에 캡슐화해야합니다. 그런 다음 State 객체를 StatefulWidget 클래스를 상속받는 위젯에 연결할 수 있습니다.

// ChatScreen을 StatefulWidget으로 변경합니다.

class ChatScreen extends StatefulWidget {  // 수정됨
  
  State createState() => new ChatScreenState();  // 새 줄
}

class ChatScreenState extends State<ChatScreen> {  // 추가됨
  
  Widget build(BuildContext context) {
    return new Scaffold(
    	appBar: new AppBar(title: new Text('Friendlychat')),
    )
  }
}

이제 앱이 상태를 관리할 수 있으므로 입력 필드를 사용하여 ChatScreenState 클래스를 구성하고 보내기 버튼을 사용할 수 있습니다.

textfield를 동작시키기위해 TextEditingControll 객체를 사용하는게 도움이 됩니다. 이는 입력필드의 내용을 읽고 문자 메세지를 보낸 후 필드를 지우는데 사용합니다.

// ChatScreenState에 클래스 변수를 추가합니다.

class ChatScreenState extends State<ChatScreen> {
  final TextEditingController _textController = new TextEditingController();
}

다음은 TextField 위젯으로 컨테이너 위젯을 리턴하는 _buildTextCompose() 라는 메서드를 정의합니다.

// ChatScreenState 클래스에 함수를 추가합니다.

Widget _buildTextComposer() {
  return new Container(
  	margin: const EdgeInsets.symmetric(horizontal: 8.0),
    child: new TextField(
    	controller: _textController,
      onSubmitted: _handleSubmitted,
      decoration: new InputDecoration.collapsed(
      	hintText: "Send a message"
      ),
    ),
  );
}

TextField 위젯에서 상호작용을 관리하기위해 다음을 추가합니다.

  • TextField 의 내용을 제어하기위해 TextEditingController 를 추가합니다.이 컨트롤러를 사용하여 필드를 지우거나 해당 값을 읽을 수 있습니다.
  • 사용자가 메세지를 보낼 때 알려면 onSubmitted 인수를 사용하여 콜백메서드에서 받을 수 있습니다. 지금 동작은 필드만 지우도록합니다.
// ChatScreenState 클래스에 새 함수를 정의합니다.

void _handleSubmitted(String text) {
  _textController.clear();
}

Place the text compose widget

이제 앱에 텍스트 필드를 표시합니다. ChatScreenStatebuild() 메서드에 _buildTextCompose() 메서드를 호출합니다.

// ChatScreenState 클래스를 변경합니다.


Widget build(BuildContext context) {
  return new Scaffold(
  	appBar: new AppBar(
    	title: new Text('Friendlychat'),
    ),
    body: _buildTextCompose(),  // 새 줄
  );
}

Add a responsive Send Button

다음으로 보내기 버튼을 텍스트 필드 우측에 배치합니다.

// _buildTextCompose 메서드를 수정합니다.

Widget _buildTextCompose() {
  return new Container(
  	margin: const EdgeInsets.symmetric(horizontal: 8.0),
    child: new Row(  // 새로
    	children: <Widget> [  // 새로
        new Flexible(  // 새로
        	child: new TextField(
          	controller: _textController,
            onSubmitted: _handleSubmitted,
            decoration: new InputDecoration.collapsed(
            	hintText: 'Send a message',
            )
          )
        )
      ]
    )
  )
}

그리고 나서 아이콘을 추가합니다. IconButton 위젯을 Container 위젯을 만들어서 넣습니다. 그리고 동작을 지정해주기 위해 onPressed 속성을 사용하여 작성을 완료하는 _handleSubmitted() 메서드를 호출하게합니다.

Widget _buildTextCompose() {
  return new Container(
  	margin: const EdgeInsets.symmetric(horizontal: 8.0),
    child: new Row(
    	children: <Widget> [
        new Flexible(
        	child: new TextField(
          	controller: _textController,
            onSubmitted: _handleSubmitted,
            decoration: new InputDecoration.collapsed(
            	hintText: 'Send a message',
            )
          )
        ),
        new Container(  // 여기서 부터 새로 추가
        	margin: new EdgeInsets.symmetric(horizontal: 4.0),
          child: new IconButton(
          	icon: Icon(Icons.send),
            onPressed: () => _handleSubmitted()
          )
        ),  // 여기까지 새로 추가
      ]
    ),
  );
}

Material Design의 버튼의 기본 색상은 검은색입니다. 앱 아이콘에 색상을 부여려면 IconButton 에 직접 인수로 정하거나 다른 테마를 적용할 수 있습니다.

아이콘은 IconThemeData 객체를 사용하여 IconTheme 위젯의 색상, 불투명도 및 크기를 상속합니다. _buildTextCompose() 메서드의 모든 위젯을 래핑하고 data 속성을 사용하여 ThemeData 오브젝트를 지정하여 IconTheme를 변경할 수 있습니다.

Widget _buildTextCompose() {
  return new IconTheme(
  	data: new IconThemeData(
    	color: Theme.of(context).accentColor
    ),
    child: new Container(
      margin: const EdgeInsets.symmetric(horizontal: 8.0),
      child: new Row(
        children: <Widget> [
          new Flexible(
            child: new TextField(
              controller: _textController,
              onSubmitted: _handleSubmitted,
              decoration: new InputDecoration.collapsed(
                hintText: 'Send a message',
              )
            )
          ),
          new Container(  // 여기서 부터 새로 추가
            margin: new EdgeInsets.symmetric(horizontal: 4.0),
            child: new IconButton(
              icon: Icon(Icons.send),
              onPressed: () => _handleSubmitted()
            )
          ),  // 여기까지 새로 추가
        ]
      ),
    )
  );
}

각 위젯에는 자체 BuildContext 가 있으며 이 컨텍스는 build 메서드에서 반환하는 위젯의 상위 위젯을 가리킵니다. 이 BuildContext는 위젯의 어디서나 접근할 수 있어서 _buildContextComposer() 메서드에서도 명시적으로 넘겨주지 않아도 사용할 수 있습니다.

Add a UI for displaying messages

Scaffold 위젯을 사용하여 채팅 메세지가 표시될 영역을 정의합니다.

Implement a message list

이 섹션에서는 사용자의 채팅 메세지를 표시하는 위젯을 작성합니다. 여러개의 작은 위젯을 작성하고 결합하여 사용합니다. 단일 채팅 메세지를 나타내는 위젯을 작성하고, 스크롤 가능한 리스트 위젯에 포함시키고, 그 위젯을 Scaffold 위젯에 포함시킵니다.

먼저 단일 채팅 메세지를 나타내는 위젯이 필요합니다. StatelessWidget 을 상속하는 ChatMessage 를 정의합니다. 발신자 이름이 포함된 메세지 텍스트를 나타내는 간단한 그래픽 아바타를 표시하는 Row 위젯을 리턴합니다.

class ChatMessage extends StatelessWidget {
  ChatMessage({this.text});
  
  final String text;
  
  
  Widget build(BuildContext context) {
    return new Container(
    	margin: const EdgeInsets.symmetric(vertical: 10.0),
      child: new Row(
      	crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          new Container(
          	margin: const EdgeInsets.only(right: 16.0),
            child: new CircleAvatar(child: new Text(_name[0])),
          ),
          new Column(
          	crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              new Text(_name, style: Theme.of(context).textTheme.subhead),
              new Container(
              	margin: const EdgeInsets.only(top: 5.0),
                child: new Text(text),
              )
            ]
          )
        ]
      )
    )
  }
}

_name 변수를 정의하고 자신의 이름으로 바꿉니다. 이 변수를 사용하여 각 채팅 메세지에 발신자 이름을 표시합니다. 이 코드 랩에서는 단순성을 위해 하드코딩했지만 Firebase for Flutter 코드랩에서는 인증을 통해 발신자를 변경합니다.

const String _name = 'Your Name';

CircleAvatar 위젯을 개인화하려면 _name 변수 값의 첫번째 문자를 하위 텍스트 위젯에 전달하여 사용자의 첫 이니셜을 텍스트위젯으로 전달합니다.

아바타의 부모인 Row 위젯은 main axis가 수평이므로 CrossAxisAlignment.start 를 사용하여 세로축의 가장 높은 위치를 제공합니다. 메세지의 부모인 Column 은 main axis가 수직이므로 CrossAxisAlignment.start 를 사용하여 좌측 정렬합니다.

발신인의 이름을 일반 텍스트보다 더 크게 하고싶을 때 ThemeDataTheme.of(context) 를 사용합니다. 하드코딩된 글꼴 크기와 일관되지못한 텍스트 속성을 피할 수 있게 ThemeDataTextTheme 를 사용하여 부제목같은 텍스트 속성을 사용합니다.

이 앱에 대한 테마를 지정하지 않았으므로, Theme.of(context) 는 기본 Flutter 테마를 검색합니다.

Implement a chat message list

채팅 목록을 가져와 UI에 표시하는 작업입니다. 채팅기록을 볼 수 있도록 이 목록은 스크롤이 가능해야합니다. 이 목록은 또한 메세지를 시간순으로 표시해야하며 최근 메세지는 맨 아래 행에 표시되어야합니다.

ChatScreenState 위젯에서 _message 라는 클래스 변수를 추가하여 각 대화 메세지를 저장합니다.

class ChatScreenState extends State<ChatScreen> {
  final List<ChatMessage> _messages = <ChatMessage>[];  // 추가
  ...
}

현재 사용자가 텍스트 필드에서 메세지를 보내면 앱에서 새 메세지를 목록에 추가해야합니다. 이 동작을 구현하기 위해 _handleSubmitted() 메서드를 수정합니다.

void _handleSubmitted(String text) {
  _textController.clear();
  ChatMessage message = new ChatMessage(
  	text: text,
  );
  setState(() {
    _messages.insert(0, message);
  })
}

setState() 를 호출하여 _messages 를 수정하고 이 위젯이 변경되었음을 알리고 다시 빌드합니다. setState() 에서는 동기 작업만 수행해야합니다. 비동기 작업이면 완료 전 다시 빌드할 가능성이 있기 때문입니다.

일부 데이터가 변경된 후 setState() 를 호출해도 되지만, setState() 내부에서 데이터를 업데이트하는 것이 바람직합니다.

Place the message list

이제 채팅 메세지 목록을 표시 할 준비가 되었습니다. _message 에서 ChatMessage 위젯을 가져와 스크롤 가능 목록을 ListView 위젯에 넣습니다.

ChatScreenState 클래스의 build() 메서드에서 메세지 목록에 대한 ListView 위젯을 추가합니다. 기본 생성자는 자식 인수의 변경을 자동으로 감지하지 않기 때문에 ListView.builder 생성자를 사용합니다.

Widget build(BuildContext context) {
  return new Scaffold(
  	appBar: new AppBar(title: new Text('Friendlychat')),
    body: new Column(  // 여기서부터 수정
    	children: <Widget>[
        new Flexible(
        	child: new ListView.builder(
          	padding: new EdgeInsets.all(8.0),
            reverse: true,
            itemBuilder: (_, int index) => _messages[index],
            itemCount: _messages.length,
          )
        ),
        new Divider(height: 1.0),
        new Container(
        	decoration: new BoxDecoration(
          	color: Theme.of(context).cardColor
          ),
          child: _buildTextComposer(),
        )
      ]
    )  // 여기까지 수정
  );
}

Scaffold 위젯의 body 속성에 입력필드 & 보내기버튼 그리고 메세지 목록을 포함합니다. 우리는 다으모가 같은 레이아웃을 사용하고 있습니다.

  • Column: 바로 하위 위젯을 수직 배치합니다. Column은 여러개의 하위 위젯을 가질 수 있으며 이는 스크롤 목록 및 입력 필드를 가질 위젯입니다.

  • Flexible: 수신된 메세지 목록이 아래서부터 위로 높이를 채울 수 있도록 합니다.

  • Divider: 메세지를 표시하는 UI와 메세지 작성 UI를 구분하는 구분자를 그립니다.

  • Container: 배경 이미지, 패딩, 여백 및 기타 공통 레이아웃 세부사항을 정의하는 데 유용한 위젯입니다. Decoration을 사용하여 배경색을 지정합니다.

ListView.builder 생성자 인수로 리스트의 모양을 정의할 수 있습니다.

  • padding: 메세지 텍스트 상하좌우 공백
  • reverse: ListView를 아래서부터 표시하기 위한 속성
  • itemBuilder 에서 각 항목을 index 를 통해 구분할 수 있습니다. 현재 BuildContext가 필요없기때문에 첫번째 인수는 _ 를 주어 무시했습니다.

Animate your App

위젯 애니메이션은 사용자경험을 더 유동적이고 직관적으로 만들 수 있습니다. 이 섹션에서는 채팅 메세지 목록에 기본 애니메이션 효과를 추가하는 방법을 설명합니다.

Flutter 애니메이션은 Animation 이라는 객체로 캡슐화됩니다. Animation 을 위젯에 첨부하거나 Animation 객체의 변경사항을 청취하고 위젯 트리를 다시 작성할 수 있습니다.

Specify an animation controller

AnimationController 클래스를 사용하여 애니메이션 실행 방법을 지정 할 수 있습니다. 지속시간 및 재생방향(정방향, 역방향)과 같은 중요한 특성을 정의할 수 있습니다.

AnimationController 를 만들 때 반드시 vsync 를 인자로 보내야합니다. vsync 는 애니메이션이 불필요한 리소스를 소비하지 않도록합니다. ChatScreenStatevsync 로 사용하려면 클래스 정의에 TickerProviderStateMixin 을 포함해야합니다.

class ChatScreenState extends State<ChatScree> with TickProviderStateMixin {
  
}

그리고 ChatMessage 클래스 정의에서 애니메이션 컨트롤러를 지정합니다.

class ChatMessage extends StatelessWidget {
  ChatMessage({this.text, this.animationController}); // 수정됨
  final String text;
  final AnimationControll animationController;  // 추가됨
}

ChatScreenState 클래스에서 _handleSubmitted 메서드를 수정합니다. 이 메서드에서 AnimationController 객체를 인스턴스화하고 애니메이션 지속시간은 700 밀리초로 지정합니다. ( 결과를 보기위해 느리게 지정했습니다. 실제 사용할 때는 더 빠르게 설정해서 사용할것입니다. )

애니메이션 컨트롤러를 새 ChatMessage 인스턴스에 연결하고 새 메세지가 추가될 때마다 애니메이션이 재생되도록합니다.

void _handleSubmitted(String Text) {
  _textController.clear();
  ChatMessage message = new ChatMessage(
  	text: text,
    animationController: new AnimationController(
    	duration: new Duration(milliseconds: 700),
      vsync: this,
    )
  );
  setState(() {
    _messages.insert(0, message);
  });
  message.animationController.forward();
}

Add a Size Transition widget

ChatMessage 객체의 build() 메서드를 수정하여 이전에 정의한 컨테이너 하위 위젯을 래핑하는 SizeTransition 위젯을 리턴합니다. SizeTransition 클래스는 자식의 너비나 높이에 주어진 크기 계수 값을 곱한 애니메이션 효과를 제공합니다.

SizeTransition 클래스와 함께 CurvedAnimation 객체는 ease-out의 애니메이션 효과를 만듭니다. ease-out 효과를 사용하면 시작될 때 빠르고 멈출 때 속도가 느려집니다.

class ChatMessage extends StatelessWidget {
  ...
    
    Widget build(BuildContext context) {
    return new SizeTransition(  // 추가
    	sizeFactor: new CurvedAnimation(
      	parent: animationController,
        curve: Curves.easeOut
      ),
      axisAlignment: 0.0,
      child: new Container(  // 기존 Container를 Warpping
      	...
      )
    )
  }
}

Dispose the animation

더 이상 필요하지 않은 리소스를 비우기 위해 애니메이션 컨트롤러를 폐기하는 것이 좋습니다. 다음 코드는 ChatScreenState 에서 dispose() 메서드를 오버라이드하여 구현합니다. 현재 앱에서는 단일 화면만 있기 때문에 dispose() 를 호출하지 않습니다. 화면이 여러개인 복잡한 앱에서는 ChatScreenState 객체를 더 이상 사용하지 않을 때 dispose() 메서드가 호출됩니다.


void dispose() {
  for (ChatMessage message in _messages) {
    message.animationController.dispose();
  }
  super.dispose();
}

애니메이션 효과를 보기위해서 앱을 재시작하고 다시 몇가지 메세지를 입력합니다.

애니메이션을 좀더 즐기고 싶은분들에게

  • _handleSubmitted() 메서드를 duration 을 좀더 짧게 해보세요
  • 다른 Curves 애니메이션을 지정해보세요
  • SizeTransition 말고 FadeTransition 을 사용해보세요.

Apply finishing touches

텍스트 있을 때만 보내기를 활성화하고 더 긴 메세지를 래핑하여 IOS 및 Android에 기본 모양의 사용자 정의를 추가하는 등 정교한 세부 정보를 앱에 제공합니다.

Make the send button context-aware

현재는 아무 값이 없어도 보내기 버튼이 활성화되어있습니다. 보낼 텍스트가 없으면 모양을 변경합니다.

입력한게 있음을 알려주는 _isComposing 변수를 정의합니다.

class ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
  ...
  bool _isComposing = false; // 추가

사용자가 텍스트 변경 사항에 대해 알림을 받으려면 onChanged 콜백을 TextField 생성자에 전달합니다. onChanged 콜백 메서드에 텍스트가 포함된 경우 setState() 를 사용하여 _isComposing 을 참으로 합니다.

그런 다음 onPressed 가 눌릴 때 _isComposing 이 false며 아무것도 실행하지 않도록합니다.

Widget _buildTextComposer() {
  return new IconTheme(
    data: new IconThemeData(color: Theme.of(context).accentColor),
    child: new Container(
      margin: const EdgeInsets.symmetric(horizontal: 8.0),
      child: new Row(
        children: <Widget>[
          new Flexible(
            child: new TextField(
              controller: _textController,
              onChanged: (String text) {          // 추가
                setState(() {                     // 추가
                  _isComposing = text.length > 0; // 추가
                });                               // 추가
              },                                  // 추가
              onSubmitted: _handleSubmitted,
              decoration:
                  new InputDecoration.collapsed(hintText: "Send a message"),
            ),
          ),
          new Container(
            margin: new EdgeInsets.symmetric(horizontal: 4.0),
            child: new IconButton(
              icon: new Icon(Icons.send),
              onPressed: _isComposing
                  ? () => _handleSubmitted(_textController.text)    // 수정
                  : null,                                           // 수정
            ),
          ),
        ],
      ),
    ),
  );
}

메세지가 전송되면 _isComposing 을 업데이트합니다.

// Modify the _handleSubmittted method definition as follows.

void _handleSubmitted(String text) {
  _textController.clear();
  setState(() {                                                    // 추가
    _isComposing = false;                                          // 추가
  });                                                              // 추가
  ChatMessage message = new ChatMessage(
    text: text,
    animationController: new AnimationController(
      duration: new Duration(milliseconds: 700),
      vsync: this,
    ),
  );
  setState(() {
    _messages.insert(0, message);
  });
  message.animationController.forward();
}

이제 _isComposing 변수는 전송 버튼의 동작과 UI를 조정합니다.

  • 텍스트 필드에 입력하면 _isComposing 이 true가 되고 색상이 Theme.of(context).accentColor 로 설정됩니다. 전송 버튼을 누르면 _handleSubmitted() 가 실행됩니다.

  • 텍스트 필드에 아무것도 없으면 onPressed 동작이 아무것도 실행되지 않고 버튼의 색상이 Theme.of(context).disabledColor 로 적용됩니다.

Wrap longer lines

지금은 사용자가 메세지를 한줄 이상으로 보내면 에러가 나타납니다. 이를 간단하게 해결하기 위해서는 Expanded 위젯을 사용하려 래핑합니다.

new Expanded(                                               //추가
  child: new Column(                                   // 수정
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      new Text(_name, style: Theme.of(context).textTheme.subhead),
      new Container(
        margin: const EdgeInsets.only(top: 5.0),
        child: new Text(text),
      ),
    ],
  ),
),                                                          // 추가

Customize for IOS and Android

앱의 UI에 자연스러운 모양과 느낌을 주기위해 간단한 로직을 추가합니다. 이 단계에서는 다른 색상 세트 및 기본 UI 세트를 가지는 플랫폼 테마를 정의합니다. 또한 IOS에서는 CupertinoButton 을 사용하여 보내기 버튼을 주고 Android에서는 IconButton 의 아이콘으로 보내기 버튼을 표시되게 합니다.

먼저 IOS 용 색상은 kIOSTheme 로 생성하고, Android는 kDefaultTheme 를 정의합니다.

final ThemeData kIOSTheme = new ThemeData(
	primarySwatch: Colors.orange,
  primaryColor: Colors.grey[100],
  primaryColorBrightness: Brightness.light,
);

final ThemeData kDefaultTheme = new ThemeData(
  primarySwatch: Colors.purple,
  accentColor: Colors.orangeAccent[400],
);

FriendlychatApptheme 속성을 사용하여 테마를 변경합니다. defaultTargetPlatform 을 사용하여 IOS나 Android를 구분하여 테마를 적용할 수 있습니다.

// Add the following code to main.dart.

import 'package:flutter/foundation.dart';                        // 추가

// Modify the FriendlychatApp class definition in main.dart.

class FriendlychatApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Friendlychat",
      theme: defaultTargetPlatform == TargetPlatform.iOS         // 추가
        ? kIOSTheme                                              // 추가
        : kDefaultTheme,                                         // 추가
      home: new ChatScreen(),
    );
  }
}

선택한 테마를 AppBar 위젯에 적용할 수 있습니다. elevation 속성은 z 좌표를 정의합니다. iOS에서는 그림자가 없고(0.0) Android에서는 그림자를 정의(4.0)합니다.

// Modify the build() method of the ChatScreenState class.

Widget build(BuildContext context) {
  return new Scaffold(
    appBar: new AppBar(
      title: new Text("Friendlychat"),                                 // 수정
      elevation:
         Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0, // 추가
   ),

iOS에서는 CupertinoButton 을 사용하여 Android에서는 IconButton 이 표시되게 합니다.

// Add the following code to main.dart.

import 'package:flutter/cupertino.dart';                      // 추가

// Modify the _buildTextComposer method.

new Container(
   margin: new EdgeInsets.symmetric(horizontal: 4.0),
   child: Theme.of(context).platform == TargetPlatform.iOS ?  // 수정
   new CupertinoButton(                                       // 추가
     child: new Text("Send"),                                 
     onPressed: _isComposing                                  
         ? () =>  _handleSubmitted(_textController.text)      
         : null,) :                                           // 추가
   new IconButton(                                            // 수정
       icon: new Icon(Icons.send),
       onPressed: _isComposing ?
           () =>  _handleSubmitted(_textController.text) : null,
       )
   ),
profile
Qué será, será

2개의 댓글

comment-user-thumbnail
2020년 1월 3일

엄청엄청 친절하게 작성해 주셔서 신나게 읽었네요 ㅎㅎ 감사합니다.
그런데 다음 편은 언제 나올까요...? 현기증이... 생깁니다...

1개의 답글