flutter

플러터로 예쁜 UI 만들기

paulaner80 2019. 3. 21. 12:16
반응형


1. 프로젝트 만들기


vscode에서 ctrl+shift+p를 눌러 명령 팔레트을 띠운다음. Flutter:New Project 명령을 선택합니다.




어플이름을 friendlychat으로 해줍니다.



이후 폴더를 지정해주면 프로젝트가 만들어집니다.


friendlychat은

 .실시간으로 문자 메시지를 표시합니다.

 .사용자가 문자를 입력하고 리턴키나 아이콘을 클릭하여 전송할 수 있습니다.



2. 메인UI 만들기



 

2-1 채팅화면 만들기 

앱을 두 개의 위젯으로 나눕니다.

 root : FriendlychatApp 위젯 (변하지 않음.)

 child: ChatScreen 위젯 (메시지를 보내거나 상태가 변할 때 리빌드됨.)


 지금은 둘다 StatelessWidget을 확장하겠습니다.

 나중에 ChatScreen은 StatefulWidget을 확장하고 상태를 관리하겠습니다.


import 'package:flutter/material.dart';

void main() => runApp(FriendlyChatApp());

class FriendlyChatApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(title: "FriendChat", home: ChatScreen());
}
}

class ChatScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("FriendlyChat"),
),
);
}
}



중요 개념들


  -. 빌드메소드는 위젯이 삽입될 때 혹은 종속성이 변경될 때 호출됩니다.

  -. @override는 수퍼 클래스의 메서드를 재정의한다는 것을 나타냅니다.

  -. Scaffold 및 AppBar와 같은 일부 위젯은 Material Design 앱에만 적용됩니다. 

     텍스트와 같은 다른 위젯은 일반 용도로 모든 응용 프로그램에서 사용할 수 있습니다.


3. 메시지 작성 UI 추가


메시지를 입력하고  보낼 수있는 UI를 작성해보겠습니다.

 -.텍스트 필드를 클릭하면 소프트키보드가 나타납니다. 

 -. 소프트키보드의 Return 키나 보내기 버튼을 눌러 채팅 메시지를 보낼 수 있습니다. 


3-1. StatefulWidget 만들기



Flutter 프레임워크는 TextField라는 StatefulWidget 위젯을 제공합니다. TextField의 속성을 통해 input 필드의 동작을 사용자가 정의할 수있습니다. 


StatefulWidget 위젯은 createState() 메소드로 State를만듭니다. State는 위젯이 빌드 될 때 읽혀지지고, 위젯이 살아 있는 동안에 변할 수 있습니다.  StatefulWidget을 추가하기 위해서는 몇 가지 수정 작업이 필요합니다.


Flutter에서 stateful 데이터를 위젯에 표시하려면 이 데이터를 State 객체에 캡슐화해야합니다. 그런 다음 State 객체를 StatefulWidget을 확장하는 클래스와 연결할 수 있습니다.


다음 코드스니펫은 StatefulWidget을 확장하는 클래스를 정의하는 방법을 보여줍니다.

먼저 ChatScreen 클래스를 StatefulWidget의 하위 클래스로 변경합니다. 

그리고 State 객체를 구현하는 새로운 ChatScreenState 클래스를 정의 할 것입니다.

그리고 ChatScreen의 createState()를 오버라이드하여 ChatScreenState를 붙여넣습니다.

기존에 StatelessWidget일때 가가지고 있던 build 메소드를 ChatScreenState 추가합니다.

이제 ChatScreen의 build()메소드에서 했던 것 들을 ChatScreenState의 build () 메소드에서 하게 될것입니다.

  

class ChatScreen extends StatefulWidget {
@override
State createState() => ChatScreenState();
}

class ChatScreenState extends State<ChatScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Friendlychart")),
);
}
}




3-2. 텍스트 입력 필드 추가

  

  이제 앱이 상태를 관리할 수 있게되었고, ChatScreenState를 통해서 입력 필드와 보내기 버튼을 빌드 할 수 있게 되었습니다.


텍스트 필드와 상호작용을 하기위해서   TextEditController가 도움이 될 것입니다.  이 객체를 통해서 텍스트 필드에 입력된 내용들을 읽어 오고, 텍스트 메지시를 보낸후 클리어 할 수도 있습니다.


 1. ChatScreenState의 TextEdingController 타입의 멤버변수 _textEditingController를 선언하세요.

 2. 텍스트 필드 내용을 제어하기 위해 TextField 생성자에 _textEditingController을 인수로 넣어줍니다.

 3. TextField 생성자의 onSubmitte 네임드 파라미터에 _handleSubmitted () 을 지정합니다. 



class ChatScreenState extends State<ChatScreen> {
TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("FriendlyChat")),
body: _buildTextComposer(),
);
}

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

_handleSubmitted(String text) {}
}



  화면 모서리와 입력 필드의 각면 사이에 수평 여백을 추가하는 Container 위젯으로 시작하십시오

  여기에있는 단위는 논리 픽셀입니다. 논리 픽셀은 장치의 픽셀 비율에 따라 특정 수의 실제 픽셀로 변환됩니다.

  iOS (포인트) 또는 Android (밀도 독립적 픽셀)에 해당하는 용어에 익숙 할 것입니다.


  TextField 위젯을 추가하고 다음과 같이 구성하여 사용자 상호 작용을 관리하십시오.


  사용자가 메시지를 제출할 때 알림을 받으려면 onSubmitte에 _handleSubmitted () 메서드를 지정하세요.

  지금은 이메서드로 필드를 지울 것이고, 나중에 메시지를 보낼 코드를 더 추가 할 것입니다. 

  이 메서드는 다음과 같이 정의하십시오.


  3-3. 문자 작성 위젯 만들기



  ChatScreenState 클래스의 build () 메서드에서 body 속성에  _buildTextComposer 라는 private 메서드를 추가합니다.

  _buildTextComposer() 메소드는 아래 그림과 같이 TextField와 IconButton으로 구성된 위젯을 리턴합니다.





[ChatScreenState 클래스]


@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Friendlychart")),
body: _buildTextComposer());
}


Widget _buildTextComposer() {
return IconTheme(
data: IconThemeData(color: Theme.of(context).accentColor),
child: Container(
margin: EdgeInsets.symmetric(horizontal: 0.8),
child: Row(
children: [
Flexible(
child: TextField(
controller: _textEditingController,
onSubmitted: _handleSubmitted,
decoration:
InputDecoration.collapsed(hintText: "Send a message"),
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 0.4),
child: IconButton(
icon: Icon(Icons.send),
onPressed: () => _handleSubmitted(_textEditingController.text),
),
),
],
),
),
);
}


3-3-1. TextFild

다음으로 텍스트 필드 오른쪽에 '보내기' 버튼을 추가합니다.

입력 필드 옆에 버튼을 표시하고자하므로 Row 위젯으로 싸줍니다.


그런 다음 TextField 위젯을 Flexible 위젯으로 싸줍니다.

이렇게하면 버튼이 사용하지 않는 나머지 공간을 사용하도록 텍스트 필드의 크기를 자동으로 지정합니다.


3-3-2. IconButton

이제 'Send' 아이콘을 표시하는 IconButton 위젯을 만들 수 있습니다. 

icon 변수 속성에서 Icons.send를 사용하는 새 Icon 인스턴스를 만듭니다. 

Icons.Send는 머티리얼 아이콘 라이브러리에서 제공하는 'Send'아이콘을 나타냅니다.

onPressed 속성의 경우 익명 함수를 사용하여 _handleSubmitted () 메서드를 호출하고 _textController를 사용하여 메시지 내용을 전달합니다.


Container 부모 위젯에 IconButton 위젯을 넣으세요. 이렇게하면 버튼의 마진을 정의 할 수 있으므로 더 좋습니다.



팁 : 뚱뚱한 화살 함수 선언 [=> 표현식]은  [{return 표현식; }] 보다 간편합니다.

익명 및 중첩 기능을 포함한 다트 기능 지원에 대한 개요는 다트 언어 둘러보기를 참조하십시오.


3-3-3 IconThemeData

기본 Material Design 테마의 버튼의 색상은 검은 색입니다. 

앱의 아이콘에 강조 색상을 지정하려면 색상 인수를 IconButton에 전달합니다. 



아이콘은 IconTheme 위젯으로 색상, 불투명도 및 크기를 IconTheme위젯으로 부터 받을 수 있습니다.

IconTheme 위젯은 이런특성들을 지정하기위해 IconThemeData 객체를 사용합니다.


IconTheme 위젯의 _buildTextComposer () 메소드에있는 모든 위젯을 래핑하고 

data 속성을 사용하여 현재 테마의 ThemeData 객체를 지정합니다. 

이렇게하면 버튼 (및 위젯 트리의이 부분에있는 다른 아이콘)에 현재 테마의 강조 색상이 표시됩니다.


BuildContext 객체는 앱 위젯 트리에서 위젯의 위치에 대한 핸들입니다. 

각 위젯에는 자체 BuildContext가 있습니다.

이것은 StatelessWidget.build 또는 State.build 함수에서 반환 된 위젯의 부모가됩니다.


즉, _buildTextComposer () 메서드는 해당 State 객체를 캡슐화하여 BuildContext 객체에 액세스 할 수 있습니다.

컨텍스트를 메서드에 명시 적으로 전달할 필요가 없습니다.




앱을 새로 고침합니다.


4.메시지를 표시하기위한 UI 추가 (6. Add a UI for displaying messages)

이 섹션에서는 여러 개의 작은 위젯으로 구성된 위젯을 만들어 채팅 메시지를 표시할것입니다. 채팅 메시지가 표시 될 영역을 정의해 봅시다채팅 메시지 하나 를 나타내는 위젯을 만들고, 그걸 스크롤할 수 있는 리스트에 넣고, 그걸 기본 앱 의 scaffold에 넣겠습니다.




4-1. 메시지 목록 구현




다음과 같이 ChatMessage라는 StatelessWidget을 정의하세요.


ChatMessage의 build () 메소드가 리턴하는 Row 위젯은 메시지를 보낸 사용자를 나타내는 간단한 그래픽 아바타, 보낸 사람 이름이 들어있는 Column 위젯 및 메시지 텍스트를 보여줍니다.


const String name = "Paul";

class ChatMessage extends StatelessWidget {
final String text;
ChatMessage({this.text});

@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 16.0),
child: CircleAvatar(
child: Text(name[0]),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(name, style: Theme.of(context).textTheme.subhead),
Container(margin: EdgeInsets.only(top: 5.0), child: Text(text))
],
)
],
),
);
}
}


4-1-1 _name 변수

_name 변수를 하드 코딩합니다. 이 변수는 각 채팅 메시지의 발신자입니다.

이 코드 랩에서는 단순화를 위해 값을 하지만, 대부분의 앱은 인증을 통해 발신자를 검색합니다.

_name 변수 값의 첫 번째 문자를 텍스트 위젯에 전달하여 사용자의 첫 번째 이니셜로 레이블을 지정합니다. 


4-1-2. 위젯 정렬하기

Row 위젯(아바타의 부모노드)          : 기본 축은 수평, 교차축은 수직.

Column 위젯(메시지의 부모 노드는 ) : 기본 축은 수직,  교차축은 수평.




4-1-3. Theme.of (context)를 사용해 ThemeData 개체를 가져오기

아바타 옆에, 두 개의 Text 위젯을 세로로 정렬하여 보낸 사람의 이름을 맨 위에 표시하고 텍스트의 텍스트를 아래에 표시합니다.

보낸 사람의 이름을 스타일링하고 메시지 텍스트보다 크게 만들려면 Theme.of (context)를 사용하여 적절한 ThemeData 개체를 가져와야합니다. textTheme 속성을 사용하면 부제목과 같은 텍스트의 재료 디자인 논리 스타일에 액세스 할 수 있으므로 글꼴 크기 및 기타 텍스트 속성을 하드 코딩하지 않아도됩니다.


 우리는이 응용 프로그램의 테마를 지정하지 않았으므로 Theme.of (context)는 기본 Flutter 테마를 검색합니다. 나중 단계에서이 기본 테마를 재정 의하여 Android와 iOS의 스타일을 다르게 지정합니다.


4-2. Implement a chat message list


다음 단계는 채팅 메시지 목록을 가져 와서 UI에 표시하는 것입니다. 사용자가 채팅 기록을 볼 수 있도록이 목록을 스크롤 할 수있게하려고합니다. 또한 목록은 가장 최근 메시지가 표시된 목록의 맨 아래 행에 표시된 순서대로 메시지를 표시해야합니다.


ChatScreenState 위젯에 _messages라는 List 멤버를 추가하여 각 채팅 메시지를 나타냅니다. 각 리스트 아이템은 ChatMessage 인스턴스입니다. 메시지 목록을 빈 목록으로 초기화해야합니다.

class ChatScreenState extends State<ChatScreen> {
final List<ChatMessage> _messages = <ChatMessage>[];
...생략 ...



현재사용자가 텍스트 필드를 통해서 메시지를 보내면, 앱은 그 메시지를 메시지 리스트에 추가할 것입니다. _handleSubmitted() 메소드에 그런 내용을 추가합니다.


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



_messages를 수정하고 setState()를 호출하여 위젯의 변경되어서 UI를 리빌드 해야한다고 프레임워크에 알려줍니다. 

setState()에서는 동기동작만 있어야합니다. 그렇지 않으면 프레임워크가 비동기동작이 끝나기전에 리빌드할 수있기 때문입니다.


일반적으로, 일부의 private 데이터가이 메소드 호출 외에서 변경된 후에는 빈 상태 (empty closure)의 setState ()를 호출 할 수 있습니다. 그러나 setState ()의 클로저 안에있는 데이터를 업데이트하는 것이 바람직하기 때문에 나중에 호출하는 것을 잊지 마십시오.


4-3. Place the message list


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


ListView.builder

ChatScreenState 클래스의 build () 메서드에 ListView 위젯을 추가합니다. 기본 생성자가 자식 매개 변수의 변형을 자동으로 감지하지 않기 때문에 ListView.builder 생성자를 선택합니다.

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("FriendlyChat2")),
body: Column(
children: <Widget>[
Flexible(
child: ListView.builder(
padding: EdgeInsets.all(8.0),
reverse: true,
itemBuilder: (_, int index) => _messages[index],
itemCount: _messages.length,
),
),
Divider(height: 1.0),
Container(
decoration: BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTextComposer(),
)
],
));
}

...생략


Scatffold의 body 속성은 받은 메시지, 입력 필드와 보내기 버튼을 가지고 있습니다. 

아래 같은 위젯을 사용했습니다.


 -. Column : 자식을 세로로 배치합니다. 열은 여러 하위 위젯을 취할 수 있으며 스크롤 위 목록과 입력 필드의 행이됩니다.

 - .Flexible : ListView의 부모 노드. 받은 메시지가 Colum을 채우도록합니다. 

 -. Divider : 메시지표시부분과 텍스트 입력 부분을 구분하는 수평선을 그어줍니다. 

 -. Container : 텍스트 컴포저의 부모 노드입니다. 배경이미지, 패딩, 마진등 레이아웃을 위해 사용됩니다.  decoration으로 BoxDecoration 객체을 사용합니다. 이 예제의 경우에 ThemeData 객체의 cardColor를 사용하는데. 이렇게하면 메시지 작성 UI가 메시지 목록과 다른 배경이됩니다.






[현재까지 전체 소스]

import 'package:flutter/material.dart';

void main() => runApp(FriendlyChatApp());

class FriendlyChatApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(title: "FriendChat", home: ChatScreen());
}
}

class ChatScreen extends StatefulWidget {
@override
State createState() => ChatScreenState();
}

class ChatScreenState extends State<ChatScreen> {
TextEditingController _textEditingController = TextEditingController();
final List<ChatMessage> _messages = <ChatMessage>[];

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("FriendlyChat2")),
body: Column(
children: <Widget>[
Flexible(
child: ListView.builder(
padding: EdgeInsets.all(8.0),
reverse: true,
itemBuilder: (_, int index) => _messages[index],
itemCount: _messages.length,
),
),
Divider(height: 1.0),
Container(
decoration: BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTextComposer(),
)
],
));
}

Widget _buildTextComposer() {
return IconTheme(
data: IconThemeData(color: Theme.of(context).accentColor),
child: Container(
margin: EdgeInsets.symmetric(horizontal: 0.8),
child: Row(
children: [
Flexible(
child: TextField(
controller: _textEditingController,
onSubmitted: _handleSubmitted,
decoration:
InputDecoration.collapsed(hintText: "Send a message"),
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 0.4),
child: IconButton(
icon: Icon(Icons.send),
onPressed: () => _handleSubmitted(_textEditingController.text),
),
),
],
),
),
);
}

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

const String name = "Paul";

class ChatMessage extends StatelessWidget {
final String text;
ChatMessage({this.text});

@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 16.0),
child: CircleAvatar(
child: Text(name[0]),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(name, style: Theme.of(context).textTheme.subhead),
Container(margin: EdgeInsets.only(top: 5.0), child: Text(text))
],
)
],
),
);
}
}




5. 애니메이션 적용 (Animate your app)


앱에 애니메이션 효과를 적용하면 사용자 경험을 더 유동적이고 직관적으로 만들 수 있습니다.

이 섹션에서는 채팅 메시지 목록에 기본적인 애니메이션 효과를 추가하는 방법에 대해 알아 보겠습니다.


사용자가 새 메시지를 보내면,

단순히 메시지 목록에 표시하는 대신 

에니메에션을 적용하여 메시지를 목록의 맨 아래에서 수직으로 움직일 것입니다.


플러터에서 에니메이션들은 Animation 객체에 캡술화 되어 있습니다.

그 객체에는 값과 상태들(앞으로, 뒤로, 완료, 해제 등등)이 있습니다.

위젯에 애니메이션 객체를 붙일 수도있고, 그 애니메이션 객체에 리스너를 추가할 수도 있습니다.

애니메이션 객체의 속성의 변경사항을 기반으로 프레임워크는 위젯이 나타나는 방식을 수정하고 위젯 트리를 재구성 할 수 있습니다.


5-1. 에니메이션 컨트롤러 지정(Specify an animation controller)


AnimationController 클래스를 사용하면 애니메이션의 지속 시간 및 재생 방향 (앞으로 또는 뒤로)과 같은 중요한 특성을 정의 할 수 있습니다.


5-1-1 TickerProviderStateMixin 추가

AnimationController를 만들 때는 vsync 인수를 전달해야합니다. (5-1-3에서 함)

vsync는 애니메이션이 오프스크일 때 불필요한 리소스를 소비하지 못하게합니다. 

ChatScreenState를 vsync로 사용하려면 ChatScreenState 클래스 정의에 TickerProviderStateMixin mixin을 포함시킵니다.


class ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
TextEditingController _textEditingController = TextEditingController();
final List<ChatMessage> _messages = <ChatMessage>[];



5-1-2. animationcontroller 변수 선언

ChatMessage 클래스에 animationController를 저장하기위한 변수를 선언하세요.


class ChatMessage extends StatelessWidget {
ChatMessage({this.text, this.animationController});
final String text;
final AnimationController animationController;
...


5-1-3 AnimationController 객체 생성 및 실행.

ChatScreenState클래스에 있는 _handleSubmitted() 메소드도 수정할 것입니다.

메소드에서 AnimationController의 객체를 객체화 시키고 애니메이션 실행시간을 700ms로 지정합니다. (듀레이션을 길게주면 에니메이션이 길어집니다. 앱을 실행시키는 중에는 듀레이션을 짧게 주는게 좋다.


에니메이션 컨트롤러를 ChatMessage에 붙여준다. 새로운 메시지가 챗리스트에 추가되면 지정한 에니메이션이 짧게 실행될 것이다.


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




5-2. SizeTransition 위젯 추가하기 (Add a SizeTransition widget)


5-2-1.SizeTransition 위젯 추가하기

ChatMessage의 build() 메소드에서 기존에 있던 Container를 SizeTransition위젯으로 감싸줍니다.

SizeTransition 클래스는  자식의 넓이 또는 높이에 주어진 값을 곱하는 에니메이션 효과를 제공합니다. 


CurvedAnimation  객체는 SizeTransition클래스와 함께 완화에니메이션효과를 생성합니다.

완화 효과는 메시지가 애니메이션의 시작 부분에서 빠르게 슬라이드되게하고 정지 할 때까지 느려지 게합니다.


class ChatMessage extends StatelessWidget {
final String text;
final AnimationController animationController;
ChatMessage({this.text, this.animationController});

@override
Widget build(BuildContext context) {
return SizeTransition(
sizeFactor:
CurvedAnimation(parent: animationController, curve: Curves.easeInOut),
axisAlignment: 0.0,
child: Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 16.0),
child: CircleAvatar(
child: Text(name[0]),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(name, style: Theme.of(context).textTheme.subhead),
Container(margin: EdgeInsets.only(top: 5.0), child: Text(text))
],
)
],
),
),
);
}
}



5-2-2. transition 종류들.

SizeTransition 

AlignTransition

DecoratedBoxTransition

FadeTransition 

PositionedTransition

RelativePositionedTransition 

RotationTransition 

ScaleTransition 

SlideTransition 



5-3 dispose() 에니메이션


에니메이션이 필요가 없어지면 리소스를 확보하기위해 에니메이션 컨트롤러를  dispose() 해주는 것이 좋습니다.

ChatScreenState의 dispose 메소드를 오버라이딩하여 작업을 구현하는 방법을 보여줍니다.

앱이 단일화면일 경우에는 dispose()가 호출되지 않습니다. 여러개의 화면을 사용하는 복잡한 앱일 경우 ChatScreenState객체가 더 이상 사용되지 않을 때 프레이워크는 이메소드를 호출합니다.


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


에니메이션을 보시려면 핫 리로드 보다는 앱을 다시 실행하고 메시지를 몇번 입력해보는 것이 좋을 것입니다. 

에니메이션을 추가로 더 실험해보고 싶으면 몇가기 아이디어가 있습니다.


hanldeSubmitted 메소드의  duration 값을 수정하여 속도를 높이거나 낮추기

Curve 클래스에 정의된 상수 값들을 사용하여 에니메이션 커브를 지정하기

SizeTransition 대신 FadeTransition으로 Container를  감싸서 페이드인 에니메이션 효과주기



6. 마무리 터치 적용


이번단계는 옵셔널입니다.

앱에 디테일을 넣어보겠습니다.

   -. 텍스트가 있을 때면 send 버튼을 활성화 시키기.

   -. 긴 메시지 감싸기

   -. 기본 모양 맞춤 설정


보내기버튼을 상황인식하게 만들기

인풋필드에 텍스트가 없어도 send 버튼이 활성화되어 있습니다. 인풋 필드에 보낼 텍스트가 있는지에 따라 버튼의 모양이 변경해야할 수 있습니다.


6-1.  보내기 버튼 활성/비활성화


멤버변수 _isComposing을 선언합니다. 이 변수는 인풋 필드에 사용자가 입력을 하면 true입니다. 

class ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
TextEditingController _textEditingController = TextEditingController();
final List<ChatMessage> _messages = <ChatMessage>[];

//TextField에 글자가 입력되면 true 가됩니다.
bool _isComposing = false;



텍스트 변경사항에 대한 알림을 받으려면 TextFied 생성자에게 onChanged 콜백을 넘겨주어야합니다.

TextFied는 값이 바뀌면 이 메소드를 호출해합니다. onChnaged콜백에서  _isComposing의 값이 true로 변경되었다고 알리기 위해 setState()를 호출합니다. 

child: TextField(
controller: _textEditingController,
onSubmitted: _handleSubmitted,
onChanged: (String text) {
setState(() {
_isComposing = text.length > 0;
});
},
decoration:
InputDecoration.collapsed(hintText: "Send a message"),
),



그러면  _isComposing이 false 이면  onPressed 인수는  null이 됩니다.

child: IconButton(
icon: Icon(Icons.send),
onPressed: _isComposing
? () => _handleSubmitted(_textEditingController.text)
: null,
),



_handleSubmitted도 텍스트필드를 클리어 시킬 때 _isComposing을 false로 만들도록 수정합니다.

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



_isComposing 변수는 이제 동작과 보내기 버튼의 외관을 제어합니다.


사용자가 TextFied에 문자열을 입력하면 _isComposing이 true이고 버튼의 색상이 Theme.of (context) .accentColor로 설정됩니다. 

사용자가 버튼을 누르면 시스템은 _handleSubmitted ()를 호출합니다.

사용자가 텍스트 필드에 아무 것도 입력하지 않으면 _isComposing은 false이고 widget의 onPressed 속성은 null로 설정되어 보내기 버튼을 비활성화합니다. 

프레임 워크는 자동으로 버튼의 색상을 Theme.of (context) .disabledColor로 변경합니다.






6-2. 긴 줄 바꿈.


사용자가 UI 너비를 초과하는 텍스트를 보냈을 때 전체 메시지를 표시하려면  줄바꿈이 되어야합니다.

텍스트를 올바르게 배치하는 간단한 방법을 Expanded 위젯을 추가하는 것입니다.

@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CircleAvatar(child: Text(_name[0])),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[Text(_name), Text(text)],
),
)
],
);
}




6-3 맟춤설정


앱의 UI를 자연스러운 모양과 느낌으로 만들기 위해 FriendlychatApp 클래스의 build () 메서드에 테마와 간단한 로직을 추가 할 수 있습니다.

이 단계에서는 다른 기본 색상 및 강조 색상 세트를 적용하는 플랫폼 테마를 정의합니다.

iOS에서는 CupertinoButton을 사용하고 Android에서는 Material Design IconButton을 사용하도록 보내기 버튼을 사용자 정의합니다.



6-3-1. 테마적용

먼저, 

iOS 용으로 kIOSTheme(오렌지 액센트가있는 밝은 회색)이라는 새로운 ThemeData 객체를 정의하고,

안드로이드용으로 kDefaultTheme(주황색 액센트의 보라색) 라는 ThemeData 객체를 정의합니다. 


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

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


MaterialApp 위젯의 theme 속성을 사용하여 FriendlyChatApp클래스를 변경하세요.

top-level 속성인 defualtTargetPlatform속성을 사용하여 테마를 선택하는 표현식을 만듭니다.


라이브러리를 임포트해줘야하는 것 같다.

import 'package:flutter/foundation.dart';



class FriendlychatApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
print(defaultTargetPlatform);
return MaterialApp(
title: 'Friendlychart',
theme: defaultTargetPlatform == TargetPlatform.iOS
? kIOSTheme
: kDefaultTheme,
home: ChatScreen(),
);
}
}



6-3-2. elevation 속성 적용.

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


class ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
final List<ChatMessage> _messages = <ChatMessage>[];
TextEditingController _textEditingController = TextEditingController();
bool _isComposing = false;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Friendlychart"),
elevation:
Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0,
),
...생략



6-3-3 . TargetPlatfrom 별로 버튼 정의하기

_buildTextComposer 메소드에서 Container 상위 위젯을 수정하여 보내기 아이콘을 사용자 정의하십시오.

child속성 및 조건연산자를 사용하여 버튼을 선택하는 식을 작성합니다.


Container(
margin: EdgeInsets.symmetric(horizontal: 4.0),
child: Theme.of(contex).platform == TargetPlatform.iOS
? CupertinoButton(
child: Text("send"),
onPressed: _isComposing
? () =>
_handleSubmitted(_textEditingController.text)
: null)
: IconButton(
icon: Icon(Icons.send),
onPressed: () =>
_handleSubmitted(_textEditingController.text),
),
)





최상위 ColumnContainer 위젯에 래핑하여 상단 가장자리에 밝은 회색 테두리를 지정합니다. 

이 테두리는 iOS의 앱 본문과 앱 표시 줄을 시각적으로 구분하는 데 도움이됩니다. 

Android에서 경계선을 숨기려면 이전 스니펫에서 AppBar에 사용 된 것과 동일한 로직을 적용하세요.


class ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
... 생략 ...
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Friendlychart"),
elevation:
Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0,
),
body: Container(
..생략...
decoration: BoxDecoration(
border: Border(top: BorderSide(color: Colors.grey[200]))),
));
}






'flutter' 카테고리의 다른 글

Dart mxins  (0) 2019.03.27
Flutter - 스트림. 다트에서 비동기 프로그래밍  (0) 2019.03.27
Flutter Stream  (0) 2019.03.20
A Design Pattern for Flutter  (0) 2019.03.20
flutter-todos-tutorial-with-flutter-bloc  (0) 2019.03.20