webview_flutterライブラリ
普段、シンプルにWebviewを表示させたい時にはwebview_flutterライブラリを使用しています。他にもライブラリは色々あるようですが、自分は今のところwebview_flutterで満足しています。
公式ドキュメント
https://pub.dev/packages/webview_flutter
今回はそのwebview_flutterで指定のURLのページを読み込み中にインジケーターを表示させる方法をメモしておきます。
Flutter Version: 1.22.4
webview_flutter version: 1.0.7
ベースとして使用するコード
以下のコードを使います。Qiitaで記事一覧を取得してリスト表示→アイテムをタップすると記事の詳細を開く、というものです。
変更前の状態では、記事の取得時にインジケーターが表示されますが、記事をタップして記事詳細を開く時にはインジケーターが表示されない状態です。
今回変更するarticle_detail_screen.dartの変更前は以下のようになります。
class ArticleDetailScreen extends StatelessWidget {
ArticleDetailScreen({@required this.qiitaInfo});
final QiitaInfo qiitaInfo;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Expanded(
child: Text(
qiitaInfo.title,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
),
),
),
),
body: WebView(
initialUrl: qiitaInfo.url,
javascriptMode: JavascriptMode.unrestricted,
),
);
}
}
変更点
上記のファイル変更します。まずsetStateを使う必要があるため、StatefulWidgetに変更する必要があります。
書き方はいくつか方法があります。
import 'package:flutter/material.dart';
import 'package:qiita_sample/data/entities/qiita_info.dart';
import 'package:webview_flutter/webview_flutter.dart';
class ArticleDetailScreen extends StatefulWidget {
ArticleDetailScreen({
@required this.qiitaInfo,
});
final QiitaInfo qiitaInfo;
@override
_ArticleDetailScreenState createState() => _ArticleDetailScreenState();
}
class _ArticleDetailScreenState extends State<ArticleDetailScreen> {
num position = 1;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Expanded(
child: Text(
widget.qiitaInfo.title,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
),
),
),
),
body: IndexedStack(
index: position,
children: [
WebView(
initialUrl: widget.qiitaInfo.url,
javascriptMode: JavascriptMode.unrestricted,
// ローディング開始時に呼ばれる
onPageStarted: (_) {
setState(() {
position = 1;
});
},
// ローディング完了時に呼ばれる
onPageFinished: (_) {
setState(() {
position = 0;
});
},
),
Container(
color: Colors.white,
child: Center(
child: CircularProgressIndicator(),
),
),
],
),
);
}
}
修正すると、タップした記事詳細のページ読み込み時にインジケーターが表示されます。
最初に「書き方はいくつかある」と書きましたが、
具体的には以下のようにStackを使って書けば、onPageFinishedのイベントを取得するだけでほとんど同じにような動きになるのですが、こちらはページが表示された後も少しの間インジケーターが回り続けます。
class _ArticleDetailScreenState extends State<ArticleDetailScreen> {
bool _isLoading = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.qiitaInfo.title,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
),
),
),
body: Stack(
children: [
WebView(
initialUrl: widget.qiitaInfo.url,
javascriptMode: JavascriptMode.unrestricted,
onPageFinished: (_) {
setState(() {
_isLoading = false;
});
},
),
_isLoading
? Center(child: CircularProgressIndicator())
: SizedBox.shrink()
],
),
);
}
}
このように違いが出る原因としては、おそらくおそらくStackだと、webviewの上にインジケーターを重ねているので、読み込みが完全に終わる前からページが表示されます。IndexedStackの場合だと画面を切り替えているので、読み込みが終わるまで画面が表示されません。
(IndexedStack Widgetを今まで知らなかったです。。)
参考にしたコード
追記
「WebView表示の読み込み時にCircularProgressIndicatorあまり使わないかも」というツッコミを頂きました。
なのでLinearProgressIndicatorを使って書き換えてみました。
// ...(略)
class _ArticleDetailScreenState extends State<ArticleDetailScreen> {
bool isLoading = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Expanded(
child: Text(
widget.qiitaInfo.title,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
),
),
),
),
body: Column(
children: [
isLoading ? LinearProgressIndicator() : SizedBox.shrink(),
Expanded(
child: WebView(
initialUrl: widget.qiitaInfo.url,
javascriptMode: JavascriptMode.unrestricted,
onPageFinished: (_) {
setState(() {
isLoading = false;
});
},
),
),
],
),
);
}
}
(AppBarのすぐ下の細長いインジケーターに注目してください)
これが一番違和感がなく、良いかもしれません。
追記2
webview_flutterではLoadingの進行度を取得することが出来なかったので、他のライブラリを使用して可能にしました。