Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Flutterでも地図を描きたいお話
Search
Ryotaro Onoue
July 24, 2023
Programming
790
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Flutterでも地図を描きたいお話
YOUTRUST x ゆめみ Flutter LT会@渋谷 #2
https://yumemi.connpass.com/event/287984/
Ryotaro Onoue
July 24, 2023
More Decks by Ryotaro Onoue
See All by Ryotaro Onoue
APNsからLive Activityを開始する話
yumnumm
0
160
FlutterKaigi 2025 公式アプリ&WebサイトのCDについて
yumnumm
0
160
FlutterKaigi 2025 システム裏側
yumnumm
0
2.2k
Terraform+cloud-initで自宅サーバのLXDをIaCするお話
yumnumm
2
270
FlutterKaigi 2024における開発チームの取り組み と 2025への展望
yumnumm
0
120
Dart WebAssemblyを使ったWeb API on Cloudflare Workers
yumnumm
0
150
Apple Walletでパスを作るお話
yumnumm
0
280
私がやってきたアウトプット集
yumnumm
0
230
俺/私のこだわりデスク大大大自慢LT大会 (LTFes #12)
yumnumm
0
160
Other Decks in Programming
See All in Programming
AIに “答え“を書かせるのを やめてみた
___akari11
0
110
contenteditable と日本語入力に向き合う
colorful12
0
180
機械に任せるテスト、人が見るテスト ⽣成AIで引き直した、運⽤保守フェーズの線引き
dske104
0
170
dotNET_vulnclicker_intro.pdf
deka_morita
0
130
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
1
350
Kubernetes祭り #2 OP/ED
bells17
0
110
UPDATE をやめる — EF Core でマスタをバージョン管理する
panda728
PRO
0
1.2k
外から家のPCの電源入れたい!をPHPで解決する
kawashimo0
1
130
CodeRabbitの効果検証と過ごしてみた3ヶ月
armondando
0
210
更なる可用性を求めて、5年間運用したKotlinのアプリケーションをGoでリプレイスする話
ken_tunc
0
470
パズルゲームの作り方 / how to make puzzle games
kaityo256
PRO
2
280
半永久的に提供し続けられるプライベートクラウドを目指して ― 利用者の認知負荷を抑えるAPI抽象化とハードウェア世代交代の基盤設計
tomokon
0
470
Featured
See All Featured
Ruling the World: When Life Gets Gamed
codingconduct
0
390
Raft: Consensus for Rubyists
vanstee
142
7.7k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
390
Optimizing for Happiness
mojombo
378
71k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
203
77k
Skip the Path - Find Your Career Trail
mkilby
1
250
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
Chasing Engaging Ingredients in Design
codingconduct
0
350
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
590
Statistics for Hackers
jakevdp
799
230k
Side Projects
sachag
456
43k
Thoughts on Productivity
jonyablonski
76
5.4k
Transcript
2023.7.24 FlutterͰਤΛඳ͖͍͓ͨ YUMEMI Inc. Flutter Engineer - Onoue Ryotaro
YOUTRUST × ΏΊΈ Flutter LTձ@ौ୩ #2
0. whoami
1. Today’s topic FlutterͰਤΛඳ͖͍ͨΜ͡Όɻ
https: / / github.com/ingen084/KyoshinEewViewerIngen ਤඳըʹؔͯ͠KyoshinEewViewer for Ingenͱ͍͏ ΦʔϓϯιʔεͷΞϓϦέʔγϣϯΛࢀߟʹ ͍ͯ͠·͢
(C#.NET, AvaloniaUI)
1. ཁ݅ ࡞Δલʹ .. . 1. άϦάϦಈ͔ͤΔ (=ΠϯλϥΫςΟϒͳ)Ϛοϓʹ͍ͨ͠ - Ҡಈɾ֦େॖখ
- ͳΊΒ͔ʹಈ͘Α͏ʹ͍ͨ͠ 2. දࣔྖҬΛ Controller ͔Βࣗ༝ʹૢ࡞Ͱ͖ΔΑ͏ʹ͍ͨ͠ - த৺ͷҢܦɾζʔϜͷมߋɾΞχϝʔγϣϯ 3. ҙͷ Widget Λਤͷ্ʹॏͶΒΕΔΑ͏ʹ͍ͨ͠ - PlatformViewΛ͏ͱɺϚοϓҠಈ࣌ʹগ͠ΕͯWidgetͷҐஔमਖ਼͕͔͔ΔͨΊ BAD - ը໘্ͷWidgetͷ͕ଟ͍ͱ Ңܦ <—> ը໘࠲ඪͷMethodChannelΛୟ͘ճ͕ଟ͘ͳΓ ΓऔΓʹ͕͔͔࣌ؒΔ ↑ ͜ͷ ڈͷ12݄ʙ2݄ࠒͷMapBoxΛͬͨΞϓϦ։ൃڠྗͰࢮ͵΄Ͳۤ࿑ͨ͠
2. ਤσʔλͷ४උ ͓ֆ͔͖લͷԼ४උ
2. ਤσʔλͷ४උ https: / / www.data.jma.go.jp/developer/gis.html https: / /
github.com/datasets/geo-countries/ blob/master/data/countries.geojson
None
߹ܭͰ2GB͘Β͍͋Δ! Ͱ͔͗͢Δ!!!! GeoJSON
Douglas–Peucker๏ Λ༻͍ͯɺઢͷ؆ૉԽΛߦ͏ 2-1. σʔλѹॖख๏ 1 ڐ༰ൣғͰਤσʔλΛখ͘͞Ͱ͖Δ
2-2. σʔλѹॖख๏ 2 A B A B Ңܦͷྻ ཁૉ͝ͱʹղ͢Δͱʜ
GeoJSONͱTopoJSONͷҧ͍ GeoJSON
2-2. ѹॖख๏ 2 A B A B Ңܦͷྻ ཁૉ͝ͱʹղ͢Δͱʜ
GeoJSONͱTopoJSONͷҧ͍ GeoJSON ॏෳ͍ͯͯ͠ϜμͰ σʔλαΠζతʹ͓ֆ͔͖తʹ A B
A B 0 arcs ʹղ͢Δ 1 GeoJSONͱTopoJSONͷҧ͍ TopoJSON
2-2. ѹॖख๏ 2
ݩσʔλ (FP+40/ .# %PVHMBVT1FVDLFS ద༻ޙ (FP+40/ .# ݩσʔλ (FP+40/ .#
3. ࠲ඪܥͷ͓ Ңܦͷ··͡Ό͓ֆ͔͖Ͱ͖ͳ͍!
3. ࠲ඪܥͷ͓ ؙ͍ٿΛͲ͏ͬͯฏ໘ʹඳ͘ͷ͔ .. . ? Ө࠲ඪ GlobalPoint(224.0, 101.4010)
class LatLng extends Point<double> { const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } Ңܦ LatLng(35,135) ը໘࠲ඪ -PDBM Offset(0, 0)
3. ࠲ඪܥͷ͓ ͬ͟ͱ֓ཁ class LatLng extends Point<double> { const
LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } ը໘࠲ඪ -PDBM GlobalPoint(35,135) Ңܦ LatLng(35,135) Ө࠲ඪ GlobalPoint(224.0, 101.4010) class WebMercatorProjection implements Projection { static const int tileSize = 256; static const pixelsPerLonDegree = tileSize / 360; static const pixelsPerLonRadian = tileSize / (2 * math.pi); static const origin = Offset(128, 128); @override GlobalPoint project(LatLng latLng) { final siny = math.min( math.max( math.sin(latLng.lat * (math.pi / 180)), -0.9999, ), 0.9999, ); return GlobalPoint( origin.dx + latLng.lon * pixelsPerLonDegree, origin.dy + 0.5 * math.log((1 + siny) / (1 - siny)) * -pixelsPerLonRadian, ); } @override LatLng unproject(GlobalPoint point) { final lng = point.x - origin.dx / pixelsPerLonDegree; final latRadians = (point.y - origin.dy) / -pixelsPerLonRadian; final lat = 180 / math.pi * (2 * math.atan(math.exp(latRadians)) - math.pi / 2); return LatLng(lat, lng); } } WebϝϧΧτϧਤ๏Λ༻͍ͯӨ
3. ࠲ඪܥͷ͓ ͬ͟ͱ֓ཁ Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point<double>
{ const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } Ңܦ LatLng(35,135) ը໘࠲ඪ -PDBM GlobalPoint(35,135) Ө࠲ඪ GlobalPoint(224.0, 101.4010) class ProjectedPolygonFeature { ProjectedPolygonFeature._({ required this.code, required this.bbox, required this.points, }); final int? code; final LatLngBoundary bbox; final List<GlobalPoint> points; } ىಈ࣌ʹɺಡΈࠐΈ - > ӨΛࡁ·͓͖ͤͯ ӨޙͷσʔλΛอ͓࣋ͯ͘͠
3. ࠲ඪܥͷ͓ ͬ͟ͱ֓ཁ Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point<double>
{ const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } Ңܦ LatLng(35,135) ը໘࠲ඪ -PDBM GlobalPoint(35,135) 0 925 0 427 @freezed class MapState with _$MapState { const factory MapState({ @JsonKey(fromJson: _offsetFromJson, toJson: _offsetToJson) required Offset offset, required double zoomLevel, }) = _MapState; factory MapState.fromJson(Map<String, dynamic> json) = > _$MapStateFromJson(json); } extension MapStateProjection on MapState { / / / GlobalPointΛOffsetʹม͢Δ Offset globalPointToOffset(GlobalPoint point) { return Offset( (point.x - offset.dx) * zoomLevel, (point.y - offset.dy) * zoomLevel, ); } / / / ը໘࠲ඪOffsetΛGlobalPointʹม͢Δ GlobalPoint offsetToGlobalPoint(Offset offset) { return GlobalPoint( offset.dx / zoomLevel + this.offset.dx, offset.dy / zoomLevel + this.offset.dy, ); }a } 1. Offset(x,y)ͣΒ͢ 2. ݪΛத৺ʹzoomLevelഒ͢Δ
3. ࠲ඪܥͷ͓ ͬ͟ͱ֓ཁ Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point<double>
{ const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } Ңܦ LatLng(35,135) ը໘࠲ඪ -PDBM GlobalPoint(35,135) @freezed class MapState with _$MapState { const factory MapState({ @JsonKey(fromJson: _offsetFromJson, toJson: _offsetToJson) required Offset offset, required double zoomLevel, }) = _MapState; factory MapState.fromJson(Map<String, dynamic> json) = > _$MapStateFromJson(json); } extension MapStateProjection on MapState { / / / GlobalPointΛOffsetʹม͢Δ Offset globalPointToOffset(GlobalPoint point) { return Offset( (point.x - offset.dx) * zoomLevel, (point.y - offset.dy) * zoomLevel, ); } / / / ը໘࠲ඪOffsetΛGlobalPointʹม͢Δ GlobalPoint offsetToGlobalPoint(Offset offset) { return GlobalPoint( offset.dx / zoomLevel + this.offset.dx, offset.dy / zoomLevel + this.offset.dy, ); }a } 925 427 0 0
3. ࠲ඪܥͷ͓ ͬ͟ͱ֓ཁ Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point<double>
{ const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } Ңܦ LatLng(35,135) ը໘࠲ඪ -PDBM GlobalPoint(35,135) 0 925 0 427 @freezed class MapState with _$MapState { const factory MapState({ @JsonKey(fromJson: _offsetFromJson, toJson: _offsetToJson) required Offset offset, required double zoomLevel, }) = _MapState; factory MapState.fromJson(Map<String, dynamic> json) = > _$MapStateFromJson(json); } extension MapStateProjection on MapState { / / / GlobalPointΛOffsetʹม͢Δ Offset globalPointToOffset(GlobalPoint point) { return Offset( (point.x - offset.dx) * zoomLevel, (point.y - offset.dy) * zoomLevel, ); } / / / ը໘࠲ඪOffsetΛGlobalPointʹม͢Δ GlobalPoint offsetToGlobalPoint(Offset offset) { return GlobalPoint( offset.dx / zoomLevel + this.offset.dx, offset.dy / zoomLevel + this.offset.dy, ); }a }
4. ࣮ࡍʹFlutterͰ͓ֆ͔͖͢Δ CustomPainter × GestureDetectorͷເͷίϥϘϨʔγϣϯ
4-1. CustomPainter ͓ֆ͔͖Widget flutter/rendering
4-1. CustomPainterͷհ ͓ֆ͔͖Widget CustomPaint Widgetʹ CustomPainter classͷΠϯελϯεΛ͢ paintؔͰ canvas
ʹԿΛඳ͔͑͘Δ ࠶ඳը͢Δඞཁ͕͋Δ͔Ͳ͏͔
4-1. CustomPainter ͓ֆ͔͖Widget - ༷ʑͳਤܗΛඳըͰ͖Δ - ਤܗඳը࣌ʹϨΠΞτ͢Δඞཁ͕ͳ͍ͷͰ ۪ʹStackͰॏͶͨΓ͢ΔΑΓߴύϑΥʔϚϯε flutter/rendering
IUUQTEPDT fl VUUFSEFWDPPLCPPLF ff FDUTHSBEJFOUCVCCMFT
4-1. CustomPainter ͓ֆ͔͖Widget - ༷ʑͳਤܗΛඳըͰ͖Δ - ਤܗඳը࣌ʹϨΠΞτ͢Δඞཁ͕ͳ͍ͷͰ ۪ʹStackͰॏͶͨΓ͢ΔΑΓߴύϑΥʔϚϯε flutter/rendering
Flutter άϥϑͷ࣮͔ΒֶͿ Container ͱ Canvas ͷ͍͚ | CyberAgent Developers Blog https: / / developers.cyberagent.co.jp/blog/archives/36573/
͓ֆ͔͖Widget - ༷ʑͳਤܗΛඳըͰ͖Δ - ਤܗඳը࣌ʹϨΠΞτ͢Δඞཁ͕ͳ͍ͷͰ ۪ʹStackͰॏͶͨΓ͢ΔΑΓߴύϑΥʔϚϯε Flutter άϥϑͷ࣮͔ΒֶͿ Container
ͱ Canvas ͷ͍͚ | CyberAgent Developers Blog https: / / developers.cyberagent.co.jp/blog/archives/36573/ 4-1. CustomPainter Widget ਤܗඳըͰ͖ΔͷͰ ਤඳ͚Δ! -> Ͳ͏ͬͯಈ͔͢…?
ΠϯλϥΫςΟϒͳૢ࡞ΛՄೳʹ - ը૾ө૾ͷදࣔྖҬΛ֦େɾॖখɾҠಈɾճస͢Δͷʹ͑Δ - Listener - > GestureDetector -
> Transform Ϣʔβͷૢ࡞ࢹ දࣔௐ https: / / dartpad.dev/?id=e6f75e0d35a958257440ab514a4196af flutter/widgets 4-2. InteractiveViewer
ΠϯλϥΫςΟϒͳૢ࡞ΛՄೳʹ - ը૾ө૾ͷදࣔྖҬΛ֦େɾॖখɾҠಈɾճస͢Δͷʹ͑Δ - Listener - > GestureDetector -
> Transform Ϣʔβͷૢ࡞ࢹ දࣔௐ https: / / dartpad.dev/?id=e6f75e0d35a958257440ab514a4196af flutter/widgets - - Ҡಈɾ֦େͯ͠ ཁૉ͕ը໘֎ʹग़͔ͨͲ͏͔ΛผͰ͖ͳ͍ - Πϝʔδͱͯ͠WidgetΛը૾Խͯ͠ ͦΕΛࣸਅΞϓϦͰද͍ࣔͯ͠Δײ͡ - > දࣔྖҬʹ߹Θͤͯ࠷దԽͰ͖ͳ͍ - Controller͕ੜ͍͑ͯΔ͕ɺঢ়ଶΛߦྻͰอ͍࣋ͯ͠Δ - ਤ͚ͷૢ࡞Λૢ࡞͠ʹ͍͘ 4-2. InteractiveViewer
4-3-1. MapStateͱMapViewModel ΜͰͧ͘ʙ ঢ়ଶཧ InteractiveViewer WidgetͷதΛࢀߟʹͭͭ͠ ࣗલͰਤؔ࿈ͷؔΛ࣮͍ͯ͘͠ (with Riverpod)
- globalPointToOffset - handleScaleStart / handleScaleUpdate / . . . - animatedMoveTo(LatLng, Duration, Curve) - animatedZoomTo(double, Duration, Curve) - animatedApplyBounds (LatLngBoundary, Duration, Curve) ࣗ࡞
4-3-2. MapTouchHandlerWidget ΜͰͧ͘ʙ λονݕͱঢ়ଶཧ - λονͷݕग़෦ (ಁ໌) - Listener
- > GestureDetectorͰ Ϣʔβͷૢ࡞Λࢹ - ൃՐ࣌ʹ MapViewModelͷ .handleXXؔΛݺΜͰ ঢ়ଶΛߋ৽ͤ͞Δ ࣗ࡞
4-3-3. BaseMapPainter ΜͰͧ͘ʙ ਤඳը - ੈքਤɾຊਤΛCustomPainterͰ͓ֆ͔͖ - ֤छ࠷దԽ -
ζʔϜഒʹԠͯ͡Douglas-Peucker ๏ Λద ༻ͯ͠ઢΛ؆ૉԽ - ζʔϜϨϕϧ࣌ͷͷΛݮΒ͢ - > ύϑΥʔϚϯε͕ྑ͘ͳΔ ↑ Ωϟογϡ͓ͯ͘͠ - ը໘֎ͷϙϦΰϯඳը͠ͳ͍Α͏ʹ ࣗ࡞
4-3-4. 39 - stackͰॏͶ͍ͯ͘ Stack( children: [ <എܠ৭>, <ਤඳը>,
<ΖΖͷඳը> <δΣενϟʔݕ>, ], );
5. ·ͱΊ Μͩͧ! - ਤ͍͚͠ΕͲ ςΫ͍ ࣮͕͋ͬͨΓͯ͠ ͓͠Ζ͍ -
leaflet.js MapLibreͷਤؔ࿈ͷϥΠϒϥϦࢀߟʹͳΔ - FlutterͰਤΛඳ͍͍͘ײ͡ͷϥΠϒϥϦ͕ͳ͔ͬͨͷͰ ࠓޙ࡞͍͖͍ͬͯͨ - InteractiveViewerͷWidgetͷ෦࣮ΛோΊͯ ͦΕΛࢀߟʹ࣮͢Δ͜ͱ͕Ͱ͖Δ - SwiftUIͱ͔ͩͱ͜ͷٕ͑ͳ͍…? Φʔϓϯιʔεͷ͋Γ͕ͨΈ - CustomPainterͰදݱͷ෯͕͕Δ - ൺֱతϨϕϧͳCanvasΛ͏͜ͱͰ ٯʹදݱͷ෯͕͕Δ(͜ͱ͋Δ)
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠! ࣭ɾؾʹͳΔ͜ͱ͕͋Ε ͍ͭͰ࿈བྷ͍ͩ͘͞! Follow me on Twitter (X?): @YumNumm
TestFlight ιʔείʔυ