このチュートリアルでは、Flutterで最初のチャットアプリを作成する方法を学びます。データベースやクラウドストレージは使用せず、flutterでcah UIを作成する方法を紹介するためのダミーのチャットアプリを作成します。
例1: Hello World チャットアプリ
データベースを使用しない、言語によるチャットアプリの簡単なハローワールド版です。
ステップ1:プロジェクトの作成
空のflutterプロジェクトを作成します。
ステップ2:依存関係の追加
この例では、外部プラグインは使用していません。
ステップ 3: コードを書く
まず、material.dart, foundation.dart
と cupertino.dart
を含むインポートの定義から始めます。
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
iOSのテーマを与えるために、Themeデータをインスタンス化します。
final ThemeData iOSTheme = new ThemeData(
primarySwatch: Colors.red,
primaryColor: Colors.grey[400],
primaryColorBrightness: Brightness.dark,
);
次に、material
パッケージを利用するアンドロイド用の別のテーマをインスタンス化します。
final ThemeData androidTheme = new ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.green,
);
デフォルトのユーザー名を作成します。
const String defaultUserName = "John Doe";
MyApp` インスタンスを実行する main 関数を作成します。
void main() => runApp(new MyApp());
ここで、MyApp
をステートレスウィジェットとして作成します。
class MyApp extends StatelessWidget {
build()` 関数をオーバーライドして、タイトルとテーマを設定します。デフォルトのターゲットプラットフォームがiOSの場合はiOSのテーマを適用し、そうでない場合はandroidのテーマを適用します。
@override
Widget build(BuildContext ctx) {
return new MaterialApp(
title: "Chat Application",
theme: defaultTargetPlatform == TargetPlatform.iOS
? iOSTheme
: androidTheme,
home: new Chat(),
);
}
}
ここで、ステートフルウィジェットとして Chat
クラスを作成します。
class Chat extends StatefulWidget {
createState 関数をオーバーライドして、
ChatWindow` クラスを返します。
@override
State createState() => new ChatWindow();
}
今度は State
クラスを継承して ChatWindow
を作成します。
class ChatWindow extends State<Chat> with TickerProviderStateMixin {
ここで、このクラスのインスタンスフィールドとして、メッセージのリスト、TextEditingController、boolean 値を用意します。
final List<Msg> _messages = <Msg>[];
final TextEditingController _textController = new TextEditingController();
bool _isWriting = false;
ビルドメソッドをオーバーライドして、チャットウィンドウのタイトルを設定し、appBarとアプリ本体を設定します。
@override
Widget build(BuildContext ctx) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Chat Application"),
elevation:
Theme.of(ctx).platform == TargetPlatform.iOS ? 0.0 : 6.0,
),
body: new Column(children: <Widget>[
new Flexible(
child: new ListView.builder(
itemBuilder: (_, int index) => _messages[index],
itemCount: _messages.length,
reverse: true,
padding: new EdgeInsets.all(6.0),
)),
new Divider(height: 1.0),
new Container(
child: _buildComposer(),
decoration: new BoxDecoration(color: Theme.of(ctx).cardColor),
),
]),
);
}
また、チャット編集ウィジェットをビルドして来ます。チャットメッセージの入力や構成に使用されるウィジェットです。
Widget _buildComposer() {
return new IconTheme(
data: new IconThemeData(color: Theme.of(context).accentColor),
child: new Container(
margin: const EdgeInsets.symmetric(horizontal: 9.0),
child: new Row(
children: <Widget>[
new Flexible(
child: new TextField(
controller: _textController,
onChanged: (String txt) {
setState(() {
_isWriting = txt.length > 0;
});
},
onSubmitted: _submitMsg,
decoration:
new InputDecoration.collapsed(hintText: "Enter some text to send a message"),
),
),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 3.0),
child: Theme.of(context).platform == TargetPlatform.iOS
? new CupertinoButton(
child: new Text("Submit"),
onPressed: _isWriting ? () => _submitMsg(_textController.text)
: null
)
: new IconButton(
icon: new Icon(Icons.message),
onPressed: _isWriting
? () => _submitMsg(_textController.text)
: null,
)
),
],
),
decoration: Theme.of(context).platform == TargetPlatform.iOS
? new BoxDecoration(
border:
new Border(top: new BorderSide(color: Colors.brown))) :
null
),
);
}
そして、入力されたテキストメッセージを送信する機能を作成します。
void _submitMsg(String txt) {
_textController.clear();
setState(() {
_isWriting = false;
});
Msg msg = new Msg(
txt: txt,
animationController: new AnimationController(
vsync: this,
duration: new Duration(milliseconds: 800)
),
);
setState(() {
_messages.insert(0, msg);
});
msg.animationController.forward();
}
アニメーションコントローラのリソースを破棄する dispose
関数をオーバーライドします。
@override
void dispose() {
for (Msg msg in _messages) {
msg.animationController.dispose();
}
super.dispose();
}
}
チャットメッセージを表現するモデルクラスを作成します。
class Msg extends StatelessWidget {
Msg({this.txt, this.animationController});
final String txt;
final AnimationController animationController;
@override
Widget build(BuildContext ctx) {
return new SizeTransition(
sizeFactor: new CurvedAnimation(
parent: animationController, curve: Curves.easeOut),
axisAlignment: 0.0,
child: new Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
margin: const EdgeInsets.only(right: 18.0),
child: new CircleAvatar(child: new Text(defaultUserName[0])),
),
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(defaultUserName, style: Theme.of(ctx).textTheme.subhead),
new Container(
margin: const EdgeInsets.only(top: 6.0),
child: new Text(txt),
),
],
),
),
],
),
),
);
}
}
以下がコードの全文です。
main.dart
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
final ThemeData iOSTheme = new ThemeData(
primarySwatch: Colors.red,
primaryColor: Colors.grey[400],
primaryColorBrightness: Brightness.dark,
);
final ThemeData androidTheme = new ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.green,
);
const String defaultUserName = "John Doe";
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext ctx) {
return new MaterialApp(
title: "Chat Application",
theme: defaultTargetPlatform == TargetPlatform.iOS
? iOSTheme
: androidTheme,
home: new Chat(),
);
}
}
class Chat extends StatefulWidget {
@override
State createState() => new ChatWindow();
}
class ChatWindow extends State<Chat> with TickerProviderStateMixin {
final List<Msg> _messages = <Msg>[];
final TextEditingController _textController = new TextEditingController();
bool _isWriting = false;
@override
Widget build(BuildContext ctx) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Chat Application"),
elevation:
Theme.of(ctx).platform == TargetPlatform.iOS ? 0.0 : 6.0,
),
body: new Column(children: <Widget>[
new Flexible(
child: new ListView.builder(
itemBuilder: (_, int index) => _messages[index],
itemCount: _messages.length,
reverse: true,
padding: new EdgeInsets.all(6.0),
)),
new Divider(height: 1.0),
new Container(
child: _buildComposer(),
decoration: new BoxDecoration(color: Theme.of(ctx).cardColor),
),
]),
);
}
Widget _buildComposer() {
return new IconTheme(
data: new IconThemeData(color: Theme.of(context).accentColor),
child: new Container(
margin: const EdgeInsets.symmetric(horizontal: 9.0),
child: new Row(
children: <Widget>[
new Flexible(
child: new TextField(
controller: _textController,
onChanged: (String txt) {
setState(() {
_isWriting = txt.length > 0;
});
},
onSubmitted: _submitMsg,
decoration:
new InputDecoration.collapsed(hintText: "Enter some text to send a message"),
),
),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 3.0),
child: Theme.of(context).platform == TargetPlatform.iOS
? new CupertinoButton(
child: new Text("Submit"),
onPressed: _isWriting ? () => _submitMsg(_textController.text)
: null
)
: new IconButton(
icon: new Icon(Icons.message),
onPressed: _isWriting
? () => _submitMsg(_textController.text)
: null,
)
),
],
),
decoration: Theme.of(context).platform == TargetPlatform.iOS
? new BoxDecoration(
border:
new Border(top: new BorderSide(color: Colors.brown))) :
null
),
);
}
void _submitMsg(String txt) {
_textController.clear();
setState(() {
_isWriting = false;
});
Msg msg = new Msg(
txt: txt,
animationController: new AnimationController(
vsync: this,
duration: new Duration(milliseconds: 800)
),
);
setState(() {
_messages.insert(0, msg);
});
msg.animationController.forward();
}
@override
void dispose() {
for (Msg msg in _messages) {
msg.animationController.dispose();
}
super.dispose();
}
}
class Msg extends StatelessWidget {
Msg({this.txt, this.animationController});
final String txt;
final AnimationController animationController;
@override
Widget build(BuildContext ctx) {
return new SizeTransition(
sizeFactor: new CurvedAnimation(
parent: animationController, curve: Curves.easeOut),
axisAlignment: 0.0,
child: new Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
margin: const EdgeInsets.only(right: 18.0),
child: new CircleAvatar(child: new Text(defaultUserName[0])),
),
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(defaultUserName, style: Theme.of(ctx).textTheme.subhead),
new Container(
margin: const EdgeInsets.only(top: 6.0),
child: new Text(txt),
),
],
),
),
],
),
),
);
}
}
実行
コードをプロジェクトにコピーするか、以下の参考リンクからコードをダウンロードしてください。
参考
参考リンクは以下の通りです。
番号 | リンク |
---|---|
1.コード | ダウンロード |
2.コード作成者 | フォロー |