Flutterで構築する漫画ビューア / FlutterKaigi 2023
by
katsu
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
Flutterで構築する漫画ビューア @katsu
Slide 2
Slide 2 text
自己紹介 ● 伊藤 克弘 ● エキサイト株式会社 ● Android / Flutter エンジニア ● 10年ほどAndroidアプリ開発 ● Flutter歴は1年半ほど 2
Slide 3
Slide 3 text
経緯 株式会社講談社様との協業 週刊漫画誌「モーニング」の電⼦版アプリ 「Dモーニング」を2013年にリリース Flutter化のための技術検証 漫画アプリのFlutter化 3
Slide 4
Slide 4 text
前提事項 ● Flutterバージョンは 3.13.8、Dartバージョンは 3.1.4 を使⽤ ● モバイルアプリのみを対象とする(ウェブなどは対象外) ● ⼀般的な横開きの漫画を対象とする(縦読み漫画などは対象外) ● マテリアルデザインで統⼀する 環境および制約 4
Slide 5
Slide 5 text
目次 1. 画像を表示する 2. 画像を拡大する 3. 暗号化された画像を使用する 4. 全画面表示に対応する 5. ページを操作する 6. 見開きページを表示する 5
Slide 6
Slide 6 text
1. 画像を表⽰する 6
Slide 7
Slide 7 text
1. 画像を表⽰する 今回使う画像ファイル ● ファイル名: 001.png ~ 010.png ● サイズ: 980 x 1,400 ● 配置場所: /images/ 使用する画像 7
Slide 8
Slide 8 text
1. 画像を表⽰する ビューアの画⾯としてViewerScreenを⽤意する 画面の作成 8
Slide 9
Slide 9 text
1. 画像を表⽰する アプリのディレクトリを取得するためにpath_providerを使う プラットフォームごとの各種ディレクトリにアクセスできる ファイルの参照 pubspec.yaml 9
Slide 10
Slide 10 text
1. 画像を表⽰する 画像ファイルを⼀覧で取得する ファイルの参照 10
Slide 11
Slide 11 text
1. 画像を表⽰する 画像ファイルを⼀覧で取得する ファイルの参照 アプリケーションディレクトリを取得 11
Slide 12
Slide 12 text
1. 画像を表⽰する 画像ファイルを⼀覧で取得する ファイルの参照 ディレクトリの要素の Listを取得 12
Slide 13
Slide 13 text
1. 画像を表⽰する 画像ファイルを⼀覧で取得する ファイルの参照 1. Fileのみを抽出 2. ファイル名で並び替え 13
Slide 14
Slide 14 text
1. 画像を表⽰する FutureBuilderでFutureからウィジェットを構成する ウィジェットで画像を参照する 14
Slide 15
Slide 15 text
1. 画像を表⽰する FutureBuilderでFutureからウィジェットを構成する ウィジェットで画像を参照する Futureの状態に 応じて処理する 15
Slide 16
Slide 16 text
1. 画像を表⽰する 画像の表⽰にはImageを使う ページ表⽰にはPageViewを使う 画像をPageViewで表示する 16
Slide 17
Slide 17 text
1. 画像を表⽰する PageViewの向きを反転する → reverseをtrueにする 画像をPageViewで表示する 17
Slide 18
Slide 18 text
2. 画像を拡⼤する 18
Slide 19
Slide 19 text
目次 1. 画像を表示する 2. 画像を拡大する 3. 暗号化された画像を使用する 4. 全画面表示に対応する 5. ページを操作する 6. 見開きページを表示する 19
Slide 20
Slide 20 text
2. 画像を拡⼤する 画像の操作にはInteractiveViewerを使う 拡大縮小や、拡大状態での移動が可能 InteractiveViewerでの操作 20
Slide 21
Slide 21 text
2. 画像を拡⼤する InteractiveViewerのイベントがPageViewと競合する → 拡⼤したまま横に動かせなくなる 拡⼤中はPageViewのスクロールを無効にする PageViewとInteractiveViewerでの問題 21
Slide 22
Slide 22 text
2. 画像を拡⼤する TransformationController InteractiveViewerに設定する ‧外部からの操作 ‧現在の状態を取得 → 拡⼤状態を判定するのに使う 拡大中の判定を行う 22
Slide 23
Slide 23 text
2. 画像を拡⼤する リスナーで値の変化を検知する getMaxScaleOnAxisで拡⼤率を取得する 拡⼤率が1より⼤きければ拡⼤中とする 拡大中の判定を行う 23
Slide 24
Slide 24 text
2. 画像を拡⼤する PageViewのphysicsでスクロールの反応を定義 AlwaysScrollableScrollPhysics → スクロール可 NeverScrollableScrollPhysics → スクロール不可 PageViewのページ移動の可否を設定する 24
Slide 25
Slide 25 text
2. 画像を拡⼤する ピンチ操作だけではなく、ダブルタップでも拡⼤できるようにする ダブルタップを検出するためにGestureDetectorを追加 ダブルタップでの操作 25
Slide 26
Slide 26 text
2. 画像を拡⼤する ダブルタップでの操作 26
Slide 27
Slide 27 text
2. 画像を拡⼤する ダブルタップでの操作 タップ位置が必要なので onDoubleTapDownを使う 27
Slide 28
Slide 28 text
2. 画像を拡⼤する ダブルタップでの操作 拡大中の場合は初期状態に戻す 28
Slide 29
Slide 29 text
2. 画像を拡⼤する ダブルタップでの操作 (50, 50) (150, 150) (0, 0) (100, 100) (300, 300) x3.0 拡大した分の座標を移動させる 29
Slide 30
Slide 30 text
2. 画像を拡⼤する ダブルタップでの操作 30
Slide 31
Slide 31 text
3. 暗号化された画像を使⽤する 31
Slide 32
Slide 32 text
目次 1. 画像を表示する 2. 画像を拡大する 3. 暗号化された画像を使用する 4. 全画面表示に対応する 5. ページを操作する 6. 見開きページを表示する 32
Slide 33
Slide 33 text
3. 暗号化された画像を使⽤する AndroidでもiOSでも、アプリ内のファイルにユーザーがアクセスすることはできない ただし、ユーザーが端末のルート化やジェイルブレイクなどで 強⼒なアクセス権を持った場合はアクセスできてしまう → コンテンツを暗号化して守ることが必要 暗号化の必要性 33
Slide 34
Slide 34 text
3. 暗号化された画像を使⽤する AndroidでもiOSでも、アプリ内のファイルにユーザーがアクセスすることはできない ただし、ユーザーが端末のルート化やジェイルブレイクなどで 強⼒なアクセス権を持った場合はアクセスできてしまう → コンテンツを暗号化して守ることが必要 ファイルになんらかの暗号化を施したという体で進める。 暗号化の必要性 34
Slide 35
Slide 35 text
3. 暗号化された画像を使⽤する 今までは画像ファイルをそのまま読み込んで表⽰していた → ファイルが暗号化されているためそのままでは表⽰できない ImageProviderを拡張して画像の読み込みをカスタマイズする ImageProviderの拡張 35
Slide 36
Slide 36 text
3. 暗号化された画像を使⽤する ImageProviderは画像をどのように処理するのかを定義するクラス いくつかのImageProviderが標準で⽤意されている ● FileImage(ファイルの画像) ● NetworkImage(ネットワーク上の画像) ● MemoryImage(メモリに展開された画像) ⾃作のImageProviderを作成して、画像を復号して読み込めるようにする ImageProviderの拡張 36
Slide 37
Slide 37 text
3. 暗号化された画像を使⽤する 復号用のImageProviderを作る FileImageを参考に 共通部分を作る 37
Slide 38
Slide 38 text
3. 暗号化された画像を使⽤する 復号用のImageProviderを作る 最適化のための比較処理 38
Slide 39
Slide 39 text
3. 暗号化された画像を使⽤する 復号用のImageProviderを作る 画像の識別のために自身を返す 39
Slide 40
Slide 40 text
3. 暗号化された画像を使⽤する 復号用のImageProviderを作る 40 loadImageで 画像読み込みの処理を実装する 画像データをCodecに、 Codecを画像フレームに変換する
Slide 41
Slide 41 text
3. 暗号化された画像を使⽤する 復号用のImageProviderを作る 画像データを展開して復号する Codecに変換する 41
Slide 42
Slide 42 text
3. 暗号化された画像を使⽤する 作成したImageProviderをImageに指定する 復号用のImageProviderを作る 42 復号用のImageProviderを作る
Slide 43
Slide 43 text
4. 全画⾯表⽰に対応する 43
Slide 44
Slide 44 text
目次 1. 画像を表示する 2. 画像を拡大する 3. 暗号化された画像を使用する 4. 全画面表示に対応する 5. ページを操作する 6. 見開きページを表示する 44
Slide 45
Slide 45 text
4. 全画⾯表⽰に対応する 全画⾯表⽰ = コンテンツ以外のUIを⾮表⽰にして画⾯全体に表⽰する ● より⼤きく鮮明に表⽰できる ● 余計な要素がなくなり没⼊感が⾼まる 通知の確認や画⾯遷移の際にUIが必要になる → UIの表⽰を切り替えられるようにする 全画面表示とは 45
Slide 46
Slide 46 text
4. 全画⾯表⽰に対応する OSが管理しているUI ‧ステータスバー ‧ナビゲーションバー ‧ホームインジケーター システムUI iOS Android ステータスバー ナビゲーションバー ホームインジケーター 46
Slide 47
Slide 47 text
4. 全画⾯表⽰に対応する コンテンツの⾼さが変わるため 表⽰を切り替えた際に位置がズレてしまう → UIの裏にも表⽰しておくことで コンテンツの⾼さを⼀定にする UIの裏にコンテンツを表示する 47
Slide 48
Slide 48 text
4. 全画⾯表⽰に対応する Flutterではデフォルトで⼀部のシステムUIの裏に表⽰される Androidのナビゲーションバーは除く UIの裏にコンテンツを表示する ナビゲーションバーの裏には 表示されない 48
Slide 49
Slide 49 text
4. 全画⾯表⽰に対応する SystemChromeでSystemUiModeを切り替えてシステムUIを管理する SystemUiMode ● edgeToEdge ● leanBack ● immersive ● immersiveSticky ● manual UIの裏にコンテンツを表示する 49
Slide 50
Slide 50 text
4. 全画⾯表⽰に対応する SystemChromeでSystemUiModeを切り替えてシステムUIを管理する SystemUiMode ● edgeToEdge ● leanBack ● immersive ● immersiveSticky ● manual UIの裏にコンテンツを表示する 後ほど説明 50
Slide 51
Slide 51 text
4. 全画⾯表⽰に対応する edgeToEdge システムUIの裏にコンテンツを表⽰する ビューア画⾯の最初に設定しておく UIの裏にコンテンツを表示する 51
Slide 52
Slide 52 text
4. 全画⾯表⽰に対応する AppBarの裏にコンテンツを表⽰する ScaffoldのextendBodyBehindAppBarをtrueにする UIの裏にコンテンツを表示する 52
Slide 53
Slide 53 text
4. 全画⾯表⽰に対応する 画⾯をタップでUIの表⽰を切り替える UIが表⽰されている → ⾮表⽰に UIが表⽰されていない → 表⽰に UIの表示状態を切り替える 53
Slide 54
Slide 54 text
4. 全画⾯表⽰に対応する UIの表示状態を切り替える 表示状態の変更時に フラグを更新 54 現在の表示状態を 自前で管理
Slide 55
Slide 55 text
4. 全画⾯表⽰に対応する UIの表示状態を切り替える GestureDetectorに タップ時の処理を追加 55
Slide 56
Slide 56 text
4. 全画⾯表⽰に対応する UIの表示状態を切り替える システムUIを非表示にする システムUIを表示する 56
Slide 57
Slide 57 text
4. 全画⾯表⽰に対応する ● edgeToEdge ● leanBack ● immersive ● immersiveSticky ● manual SystemUiMode 57
Slide 58
Slide 58 text
4. 全画⾯表⽰に対応する SystemUiMode edgeToEdge leanBack immersive immersiveSticky manual 表示にする 非表示にする 手動で設定する 58
Slide 59
Slide 59 text
4. 全画⾯表⽰に対応する SystemUiMode edgeToEdge leanBack immersive immersiveSticky manual 表示にする 非表示にする パラメータで決まる iOSでは全て同じ挙動 59
Slide 60
Slide 60 text
4. 全画⾯表⽰に対応する edgeToEdge ● 全てのシステムUIが表⽰される ● コンテンツはシステムUIの裏にも表⽰される SystemUiMode 60
Slide 61
Slide 61 text
4. 全画⾯表⽰に対応する leanBack ● 全てのシステムUIが⾮表⽰になる ● 画⾯をタップするとシステムUIが表⽰される 動画再⽣など、基本的に画⾯を触らない場合に有効 SystemUiMode 61
Slide 62
Slide 62 text
4. 全画⾯表⽰に対応する immersive ● 全てのシステムUIが⾮表⽰になる ● 端をスワイプするとシステムUIが表⽰される ギャラリーなど、⼤まかな操作をする場合に有効 SystemUiMode 62
Slide 63
Slide 63 text
4. 全画⾯表⽰に対応する immersiveSticky ● 全てのシステムUIが⾮表⽰になる ● 端をスワイプするとシステムUIが表⽰される ● ⼀定時間経過で再び⾮表⽰になる ● スワイプのイベントをアプリも受け取れる お絵かきなど、画⾯全体を操作する場合に有効 SystemUiMode 63
Slide 64
Slide 64 text
4. 全画⾯表⽰に対応する manual ● 表⽰するシステムUIを指定する ● システムUIは上下に分類される どちらも省略した場合はleanBackと同様 SystemUiMode 64
Slide 65
Slide 65 text
4. 全画⾯表⽰に対応する AppBarの表⽰状態 表⽰する場合はAppBarを、⾮表⽰にする場合はnullを設定する。 UIの表示状態を切り替える 65
Slide 66
Slide 66 text
4. 全画⾯表⽰に対応する 動作イメージ 66
Slide 67
Slide 67 text
5. ページを操作する 67
Slide 68
Slide 68 text
目次 1. 画像を表示する 2. 画像を拡大する 3. 暗号化された画像を使用する 4. 全画面表示に対応する 5. ページを操作する 6. 見開きページを表示する 68
Slide 69
Slide 69 text
5. ページを操作する PageViewはスワイプでページ移動ができるが、 それ以外の操作もサポートしていると操作性が⾼まる ● タップでの簡単なページ移動 ● スライダーでの素早いページ移動 これらの操作に対応する スワイプ以外でのページ操作 69
Slide 70
Slide 70 text
5. ページを操作する PageController PageViewを外部から操作する コントローラー経由でページ移動を⾏う ページ操作の基本 70
Slide 71
Slide 71 text
5. ページを操作する ページ移動には下記のメソッドが使える ● jumpTo ● jumpToPage ● animateTo ● animateToPage ● nextPage ● previousPage PageControllerの操作 71
Slide 72
Slide 72 text
5. ページを操作する ページ移動には下記のメソッドが使える ● jumpTo ● jumpToPage ● animateTo ● animateToPage ● nextPage ● previousPage PageControllerの操作 72 指定したページに瞬時に移動する オフセットもしくはインデックスを指定する
Slide 73
Slide 73 text
5. ページを操作する ページ移動には下記のメソッドが使える ● jumpTo ● jumpToPage ● animateTo ● animateToPage ● nextPage ● previousPage PageControllerの操作 73 指定したページにアニメーションで移動する オフセットもしくはインデックスを指定する
Slide 74
Slide 74 text
5. ページを操作する ページ移動には下記のメソッドが使える ● jumpTo ● jumpToPage ● animateTo ● animateToPage ● nextPage ● previousPage PageControllerの操作 74 隣のページにアニメーションで移動する 内部ではanimateToPageが使われている
Slide 75
Slide 75 text
5. ページを操作する タップでページ移動ができるようにする 左側の25%の範囲をタップしたら次のページに移動 右側の25%の範囲をタップしたら前のページに移動 タップでのページ操作 75
Slide 76
Slide 76 text
5. ページを操作する タップでのページ操作 76
Slide 77
Slide 77 text
5. ページを操作する タップでのページ操作 タップ位置を知るために onTapUpを使う 77
Slide 78
Slide 78 text
5. ページを操作する タップでのページ操作 横軸のタップ位置を取得 ページ移動判定の閾値を算出 78
Slide 79
Slide 79 text
5. ページを操作する タップでのページ操作 左右のタップ時に 隣のページに移動する 79
Slide 80
Slide 80 text
5. ページを操作する タップでのページ操作 UIの表示切替のタップイベントを統 合する 80
Slide 81
Slide 81 text
5. ページを操作する タップでのページ操作 81
Slide 82
Slide 82 text
5. ページを操作する Slider : ⼀定の範囲から値を選択するウィジェット 下記の要件を実装する ‧画⾯下部にスライダーを設置 ‧ページ数を範囲とする ‧選択したページに移動する スライダーでのページ操作 82
Slide 83
Slide 83 text
5. ページを操作する スライダーでのページ操作 83 ・Stackでコンテンツに重ねて表示する ・SafeAreaでシステムUIを避ける ・UIの表示フラグに合わせる SafeAreaなし SafeAreaあり
Slide 84
Slide 84 text
5. ページを操作する スライダーでのページ操作 84 選択中のページを変数で保持する ページの変更時に変数を更新する
Slide 85
Slide 85 text
5. ページを操作する スライダーと選択中のページを紐付ける スライダーでのページ操作 85
Slide 86
Slide 86 text
5. ページを操作する スライダーと選択中のページを紐付ける スライダーでのページ操作 86 値の範囲は0.0 ~ 1.0 選択中のページ/ページ数
Slide 87
Slide 87 text
5. ページを操作する スライダーと選択中のページを紐付ける スライダーでのページ操作 87 値 x ページ数 対象のページに移動
Slide 88
Slide 88 text
5. ページを操作する スライダーを反転させる PageViewのような単純な⽅法はない 計算でページのインデックスを逆順にする → ロジックが少し複雑になる スライダーでのページ操作 88
Slide 89
Slide 89 text
5. ページを操作する スライダーを反転させる PageViewのような単純な⽅法はない 計算でページのインデックスを逆順にする → ロジックが少し複雑になる ウィジェット⾃体を反転させる スライダーでのページ操作 89
Slide 90
Slide 90 text
5. ページを操作する Transform.flip ウィジェットの中⼼を軸に反転させる flipXを指定すると⽔平⽅向に反転 スライダーでのページ操作 90
Slide 91
Slide 91 text
5. ページを操作する スライダーでのページ操作 91
Slide 92
Slide 92 text
6. ⾒開きページを表⽰する 92
Slide 93
Slide 93 text
目次 1. 画像を表示する 2. 画像を拡大する 3. 暗号化された画像を使用する 4. 全画面表示に対応する 5. ページを操作する 6. 見開きページを表示する 93
Slide 94
Slide 94 text
6. ⾒開きページを表⽰する ⾒開きページは向かい合う左右のページで構成される 漫画は⾒開き⽤に構成されていることが多い → 可能な場合はアプリでも⾒開きで表⽰する 見開きページとは 94
Slide 95
Slide 95 text
6. ⾒開きページを表⽰する OrientationBuilderで親ウィジェットの向きを取得する 横向きの場合は⾒開き表⽰にする 見開き表示の判定 95
Slide 96
Slide 96 text
6. ⾒開きページを表⽰する ページ数の管理 96 画面あたりのページ数が 変わるため調整する
Slide 97
Slide 97 text
6. ⾒開きページを表⽰する ページ数の管理 97 見開きの場合はページ数の 最大値を半分として扱う 半分にしたページ数を PageViewとSliderに反映する
Slide 98
Slide 98 text
6. ⾒開きページを表⽰する ページ数の管理 98 選択中のページは 1ページ表示を基準とする 基準の値をSliderの 現在値に反映する
Slide 99
Slide 99 text
6. ⾒開きページを表⽰する 表⽰が切り替わった際にページがズレてしまう ページ数の管理 99 index == 2
Slide 100
Slide 100 text
6. ⾒開きページを表⽰する ページ数の管理 100 表示が切り替わった際に ページを移動させる
Slide 101
Slide 101 text
6. ⾒開きページを表⽰する ページ数の管理 101 直前の向きを保持しておき 切り替わったかどうかを判定
Slide 102
Slide 102 text
6. ⾒開きページを表⽰する ページ数の管理 102 表示が切り替わった場合は 対応したページに移動させる
Slide 103
Slide 103 text
6. ⾒開きページを表⽰する 表⽰が切り替わった際に正しいページが表⽰される ページ数の管理 103
Slide 104
Slide 104 text
6. ⾒開きページを表⽰する 2枚の画像を1枚に連結する 画像の連結 104
Slide 105
Slide 105 text
6. ⾒開きページを表⽰する 画像の連結 105 画像を連結するために ImageProviderを変更
Slide 106
Slide 106 text
6. ⾒開きページを表⽰する 画像の連結 106 名前付きコンストラクタで 画像の指定を追加
Slide 107
Slide 107 text
6. ⾒開きページを表⽰する 画像の連結 107 比較処理をListに対応
Slide 108
Slide 108 text
6. ⾒開きページを表⽰する 画像を復号した後に連結する 画像の連結 108
Slide 109
Slide 109 text
6. ⾒開きページを表⽰する 画像処理のライブラリを使⽤して画像を連結する 画像の連結 109 pubspec.yaml
Slide 110
Slide 110 text
6. ⾒開きページを表⽰する 画像処理のライブラリを使⽤して画像を連結する 画像の連結 110 画像をImage型に デコードする
Slide 111
Slide 111 text
6. ⾒開きページを表⽰する 画像処理のライブラリを使⽤して画像を連結する 画像の連結 111 出力先の画像の 大きさを決定する
Slide 112
Slide 112 text
6. ⾒開きページを表⽰する 画像処理のライブラリを使⽤して画像を連結する 画像の連結 112 画像の数だけ 順番に連結する
Slide 113
Slide 113 text
6. ⾒開きページを表⽰する 変更したImageProviderを使⽤する 画像の連結 113
Slide 114
Slide 114 text
6. ⾒開きページを表⽰する 画像連結の処理が重いのでページ移動の際に画⾯が固まってしまう 画像の連結 114
Slide 115
Slide 115 text
Futureで⾮同期処理にしても同⼀のスレッドで実⾏される → 重い処理を実⾏するとメインスレッドがブロックされる 重い処理は並列で実⾏する必要がある 6. ⾒開きページを表⽰する 並列処理の実行 115
Slide 116
Slide 116 text
6. ⾒開きページを表⽰する Isolate Dartで並列処理を⾏うための仕組み ● メインスレッドも⼀つのIsolateである ● Isolate毎に異なるイベントループが実⾏される ● Isolate間は分断されている → 別のIsolateで処理を実⾏すれば画⾯が固まることはない 並列処理の実行 116
Slide 117
Slide 117 text
6. ⾒開きページを表⽰する Isolateを使う ● Isolate.spawn: 低レイヤーでの操作で、⾊々と⾃前で管理する必要がある ● compute: Flutterの拡張機能で、Isolateをシンプルに扱える ● Isolate.run: Dart 2.19で追加され、computeと同様にIsolateをシンプルに扱える → 今回は⼀番シンプルなIsolate.runを使う 並列処理の実行 117
Slide 118
Slide 118 text
6. ⾒開きページを表⽰する 処理全体をIsolate.runのブロックに含める 並列処理の実行 118
Slide 119
Slide 119 text
6. ⾒開きページを表⽰する 画⾯が固まることがなくなる 並列処理の実行 119
Slide 120
Slide 120 text
最後に 120
Slide 121
Slide 121 text
最後に 今回実装したのは最低限の基本的な機能 → 改善できる点はたくさんある ● OSバージョンごとの最適化 ● 読み込みの最適化、キャッシュ処理 ● 操作の効率化 ● 動作のアニメーション ● etc. さらなる改善 121
Slide 122
Slide 122 text
最後に 今回実装したのは最低限の基本的な機能 → 改善できる点はたくさんある ● OSバージョンごとの最適化 ● 読み込みの最適化、キャッシュ処理 ● 操作の効率化 ● 動作のアニメーション ● etc. さらなる改善 122 最高のサービスを目指して!
Slide 123
Slide 123 text
123