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
150
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
140
Apple Walletでパスを作るお話
yumnumm
0
280
私がやってきたアウトプット集
yumnumm
0
230
俺/私のこだわりデスク大大大自慢LT大会 (LTFes #12)
yumnumm
0
160
Other Decks in Programming
See All in Programming
最近”作らせてる” ローカルAIアプリの話
akit37
0
400
Embedded Swiftで作る自作USBデバイス によるiOSデバイスの自動テスト / iOSDC Japan 2026 glassfiber
glassfiber
0
310
残高管理から台帳サービスへの進化
artoy
0
180
AI時代のコードレビューは人に向けるな、仕組みに向けろ
texmeijin
5
3.5k
WebRTC映像をAirPlayに対応させる挑戦.pdf
monolithic_adam
0
350
Are APIs Still Relevant in the AI Era?
soyuka
0
440
カツオ、ご期待ください
suneo3476
0
160
setup-vp GitLab対応の裏側
naokihaba
0
150
Workers Cache を知る
syumai
0
350
Turning Architecture into Unit Tests in the AI Era (NSSpain XIV)
steliosf
PRO
1
130
Java 27新機能 / Java 27 new features
kishida
2
210
半永久的に提供し続けられるプライベートクラウドを目指して ― 利用者の認知負荷を抑えるAPI抽象化とハードウェア世代交代の基盤設計
tomokon
0
470
Featured
See All Featured
Designing Experiences People Love
moore
143
24k
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
520
WCS-LA-2024
lcolladotor
0
860
Joys of Absence: A Defence of Solitary Play
codingconduct
1
530
Bash Introduction
62gerente
615
220k
Chasing Engaging Ingredients in Design
codingconduct
0
350
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
510
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Large-scale JavaScript Application Architecture
addyosmani
516
110k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.6k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
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 ιʔείʔυ