Slide 1

Slide 1 text

2023.7.24 FlutterͰ΋஍ਤΛඳ͖͍͓ͨ࿩ YUMEMI Inc. Flutter Engineer - Onoue Ryotaro YOUTRUST × ΏΊΈ Flutter LTձ@ौ୩ #2

Slide 2

Slide 2 text

0. whoami

Slide 3

Slide 3 text

1. Today’s topic FlutterͰ΋஍ਤΛඳ͖͍ͨΜ͡Όɻ

Slide 4

Slide 4 text

Slide 5

Slide 5 text

Slide 6

Slide 6 text

https: / / github.com/ingen084/KyoshinEewViewerIngen ஍ਤඳըʹؔͯ͠KyoshinEewViewer for Ingenͱ͍͏ ΦʔϓϯιʔεͷΞϓϦέʔγϣϯΛࢀߟʹ ͍ͯ͠·͢ (C#.NET, AvaloniaUI੡)

Slide 7

Slide 7 text

1. ཁ݅ ࡞Δલʹ .. . 1. άϦάϦಈ͔ͤΔ (=ΠϯλϥΫςΟϒͳ)Ϛοϓʹ͍ͨ͠ - Ҡಈɾ֦େॖখ - ͳΊΒ͔ʹಈ͘Α͏ʹ͍ͨ͠ 2. දࣔྖҬΛ Controller ͔Βࣗ༝ʹૢ࡞Ͱ͖ΔΑ͏ʹ͍ͨ͠ - த৺ͷҢ౓ܦ౓ɾζʔϜ཰ͷมߋɾΞχϝʔγϣϯ 3. ೚ҙͷ Widget Λ஍ਤͷ্ʹॏͶΒΕΔΑ͏ʹ͍ͨ͠ - PlatformViewΛ࢖͏ͱɺϚοϓҠಈ࣌ʹগ͠஗ΕͯWidgetͷҐஔमਖ਼͕͔͔ΔͨΊ BAD - ը໘্ͷWidgetͷ਺͕ଟ͍ͱ Ң౓ܦ౓ <—> ը໘࠲ඪͷMethodChannelΛୟ͘ճ਺͕ଟ͘ͳΓ ΍ΓऔΓʹ͕͔͔࣌ؒΔ ↑ ͜ͷ໰୊͸ ڈ೥ͷ12݄ʙ2݄ࠒͷMapBoxΛ࢖ͬͨΞϓϦ։ൃڠྗͰࢮ͵΄Ͳۤ࿑ͨ͠

Slide 8

Slide 8 text

2. ஍ਤσʔλͷ४උ ͓ֆ͔͖લͷԼ४උ

Slide 9

Slide 9 text

2. ஍ਤσʔλͷ४උ https: / / www.data.jma.go.jp/developer/gis.html https: / / github.com/datasets/geo-countries/ blob/master/data/countries.geojson

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

߹ܭͰ2GB͘Β͍͋Δ! Ͱ͔͗͢Δ!!!! GeoJSON

Slide 12

Slide 12 text

Douglas–Peucker๏ Λ༻͍ͯɺઢ෼ͷ؆ૉԽΛߦ͏ 2-1. σʔλѹॖख๏ 1 ڐ༰ൣғ಺Ͱ஍ਤσʔλΛখ͘͞Ͱ͖Δ

Slide 13

Slide 13 text

2-2. σʔλѹॖख๏ 2 A B A B Ң౓ܦ౓ͷ഑ྻ ཁૉ͝ͱʹ෼ղ͢Δͱʜ GeoJSONͱTopoJSONͷҧ͍ GeoJSON

Slide 14

Slide 14 text

2-2. ѹॖख๏ 2 A B A B Ң౓ܦ౓ͷ഑ྻ ཁૉ͝ͱʹ෼ղ͢Δͱʜ GeoJSONͱTopoJSONͷҧ͍ GeoJSON ॏෳ͍ͯͯ͠ϜμͰ͸ σʔλαΠζతʹ΋͓ֆ͔͖తʹ΋ A B

Slide 15

Slide 15 text

A B 0 arcs ʹ෼ղ͢Δ 1 GeoJSONͱTopoJSONͷҧ͍ TopoJSON 2-2. ѹॖख๏ 2

Slide 16

Slide 16 text

ݩσʔλ (FP+40/ .# %PVHMBVT1FVDLFS ద༻ޙ (FP+40/ .# ݩσʔλ (FP+40/ .#

Slide 17

Slide 17 text

3. ࠲ඪܥͷ͓࿩ Ң౓ܦ౓ͷ··͡Ό͓ֆ͔͖Ͱ͖ͳ͍!

Slide 18

Slide 18 text

3. ࠲ඪܥͷ͓࿩ ؙ͍஍ٿΛͲ͏΍ͬͯฏ໘ʹඳ͘ͷ͔ .. . ? ౤Ө࠲ඪ GlobalPoint(224.0, 101.4010) class LatLng extends Point { const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point { 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)

Slide 19

Slide 19 text

3. ࠲ඪܥͷ͓࿩ ͬ͟ͱ֓ཁ class LatLng extends Point { const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point { 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ϝϧΧτϧਤ๏Λ༻͍ͯ౤Ө

Slide 20

Slide 20 text

3. ࠲ඪܥͷ͓࿩ ͬ͟ͱ֓ཁ ౤Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point { const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point { 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 points; } ىಈ࣌ʹɺಡΈࠐΈ - > ౤ӨΛࡁ·͓͖ͤͯ ౤ӨޙͷσʔλΛอ͓࣋ͯ͘͠

Slide 21

Slide 21 text

3. ࠲ඪܥͷ͓࿩ ͬ͟ͱ֓ཁ ౤Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point { const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point { 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 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ഒ͢Δ

Slide 22

Slide 22 text

3. ࠲ඪܥͷ͓࿩ ͬ͟ͱ֓ཁ ౤Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point { const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point { 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 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

Slide 23

Slide 23 text

3. ࠲ඪܥͷ͓࿩ ͬ͟ͱ֓ཁ ౤Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point { const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point { 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 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 }

Slide 24

Slide 24 text

4. ࣮ࡍʹFlutterͰ͓ֆ͔͖͢Δ CustomPainter × GestureDetectorͷເͷίϥϘϨʔγϣϯ

Slide 25

Slide 25 text

4-1. CustomPainter ͓ֆ͔͖Widget flutter/rendering

Slide 26

Slide 26 text

4-1. CustomPainterͷ঺հ ͓ֆ͔͖Widget CustomPaint Widgetʹ CustomPainter classͷΠϯελϯεΛ౉͢ paintؔ਺಺Ͱ canvas ʹԿΛඳ͔͘఻͑Δ ࠶ඳը͢Δඞཁ͕͋Δ͔Ͳ͏͔

Slide 27

Slide 27 text

Slide 28

Slide 28 text

Slide 29

Slide 29 text

Slide 30

Slide 30 text

Slide 31

Slide 31 text

4-1. CustomPainter ͓ֆ͔͖Widget - ༷ʑͳਤܗΛඳըͰ͖Δ - ਤܗඳը࣌ʹϨΠΞ΢τ͢Δඞཁ͕ͳ͍ͷͰ ۪௚ʹStackͰॏͶͨΓ͢ΔΑΓ΋ߴύϑΥʔϚϯε flutter/rendering IUUQTEPDT fl VUUFSEFWDPPLCPPLF ff FDUTHSBEJFOUCVCCMFT

Slide 32

Slide 32 text

4-1. CustomPainter ͓ֆ͔͖Widget - ༷ʑͳਤܗΛඳըͰ͖Δ - ਤܗඳը࣌ʹϨΠΞ΢τ͢Δඞཁ͕ͳ͍ͷͰ ۪௚ʹStackͰॏͶͨΓ͢ΔΑΓ΋ߴύϑΥʔϚϯε flutter/rendering Flutter ๮άϥϑͷ࣮૷͔ΒֶͿ Container ͱ Canvas ͷ࢖͍෼͚ | CyberAgent Developers Blog https: / / developers.cyberagent.co.jp/blog/archives/36573/

Slide 33

Slide 33 text

͓ֆ͔͖Widget - ༷ʑͳਤܗΛඳըͰ͖Δ - ਤܗඳը࣌ʹϨΠΞ΢τ͢Δඞཁ͕ͳ͍ͷͰ ۪௚ʹStackͰॏͶͨΓ͢ΔΑΓ΋ߴύϑΥʔϚϯε Flutter ๮άϥϑͷ࣮૷͔ΒֶͿ Container ͱ Canvas ͷ࢖͍෼͚ | CyberAgent Developers Blog https: / / developers.cyberagent.co.jp/blog/archives/36573/ 4-1. CustomPainter Widget ਤܗඳըͰ͖ΔͷͰ ஍ਤඳ͚Δ! -> Ͳ͏΍ͬͯಈ͔͢…?

Slide 34

Slide 34 text

ΠϯλϥΫςΟϒͳૢ࡞ΛՄೳʹ - ը૾΍ө૾ͷදࣔྖҬΛ֦େɾॖখɾҠಈɾճస͢Δͷʹ࢖͑Δ - Listener - > GestureDetector - > Transform Ϣʔβͷૢ࡞؂ࢹ දࣔௐ੔ https: / / dartpad.dev/?id=e6f75e0d35a958257440ab514a4196af flutter/widgets 4-2. InteractiveViewer

Slide 35

Slide 35 text

ΠϯλϥΫςΟϒͳૢ࡞ΛՄೳʹ - ը૾΍ө૾ͷදࣔྖҬΛ֦େɾॖখɾҠಈɾճస͢Δͷʹ࢖͑Δ - Listener - > GestureDetector - > Transform Ϣʔβͷૢ࡞؂ࢹ දࣔௐ੔ https: / / dartpad.dev/?id=e6f75e0d35a958257440ab514a4196af flutter/widgets - ໰୊఺ - Ҡಈɾ֦େͯ͠ ཁૉ͕ը໘֎ʹग़͔ͨͲ͏͔Λ൑ผͰ͖ͳ͍ - Πϝʔδͱͯ͠͸WidgetΛը૾Խͯ͠ ͦΕΛࣸਅΞϓϦͰද͍ࣔͯ͠Δײ͡ - > දࣔྖҬʹ߹Θͤͯ࠷దԽͰ͖ͳ͍ - Controller͕ੜ͍͑ͯΔ͕ɺঢ়ଶΛߦྻͰอ͍࣋ͯ͠Δ - ஍ਤ޲͚ͷૢ࡞Λૢ࡞͠ʹ͍͘ 4-2. InteractiveViewer

Slide 36

Slide 36 text

4-3-1. MapStateͱMapViewModel ૊ΜͰͧ͘ʙ ঢ়ଶ؅ཧ InteractiveViewer Widgetͷத਎Λࢀߟʹͭͭ͠ ࣗલͰ஍ਤؔ࿈ͷؔ਺Λ࣮૷͍ͯ͘͠ (with Riverpod) - globalPointToOffset - handleScaleStart / handleScaleUpdate / . . . - animatedMoveTo(LatLng, Duration, Curve) - animatedZoomTo(double, Duration, Curve) - animatedApplyBounds (LatLngBoundary, Duration, Curve) ࣗ࡞

Slide 37

Slide 37 text

4-3-2. MapTouchHandlerWidget ૊ΜͰͧ͘ʙ λονݕ஌ͱঢ়ଶ؅ཧ - λονͷݕग़෦෼ (ಁ໌) - Listener - > GestureDetectorͰ Ϣʔβͷૢ࡞Λ؂ࢹ - ൃՐ࣌ʹ͸ MapViewModelͷ .handleXXؔ਺ΛݺΜͰ ঢ়ଶΛߋ৽ͤ͞Δ ࣗ࡞

Slide 38

Slide 38 text

4-3-3. BaseMapPainter ૊ΜͰͧ͘ʙ ஍ਤඳը - ੈք஍ਤɾ೔ຊ஍ਤΛCustomPainterͰ͓ֆ͔͖ - ֤छ࠷దԽ - ζʔϜഒ཰ʹԠͯ͡Douglas-Peucker ๏ Λద ༻ͯ͠ઢ෼Λ؆ૉԽ - ௿ζʔϜϨϕϧ࣌ͷ఺ͷ਺ΛݮΒ͢ - > ύϑΥʔϚϯε͕ྑ͘ͳΔ ↑ Ωϟογϡ͓ͯ͘͠ - ը໘֎ͷϙϦΰϯ͸ඳը͠ͳ͍Α͏ʹ ࣗ࡞

Slide 39

Slide 39 text

4-3-4. ׬੒඼ 39 - stackͰॏͶ͍ͯ͘ Stack( children: [ <എܠ৭>, <஍ਤඳը>, <΋Ζ΋Ζͷඳը> <δΣενϟʔݕ஌>, ], );

Slide 40

Slide 40 text

5. ·ͱΊ ૊Μͩͧ! - ஍ਤ͸೉͍͚͠ΕͲ ςΫ͍ ࣮૷͕͋ͬͨΓͯ͠ ͓΋͠Ζ͍ - leaflet.js ΍ MapLibre౳ͷ஍ਤؔ࿈ͷϥΠϒϥϦ΋ࢀߟʹͳΔ - FlutterͰ஍ਤΛඳ͍͍͘ײ͡ͷϥΠϒϥϦ͕ͳ͔ͬͨͷͰ ࠓޙ࡞͍͖͍ͬͯͨ - InteractiveViewer౳ͷWidgetͷ಺෦࣮૷ΛோΊͯ ͦΕΛࢀߟʹ࣮૷͢Δ͜ͱ͕Ͱ͖Δ - SwiftUIͱ͔ͩͱ͜ͷٕ͸࢖͑ͳ͍…? Φʔϓϯιʔεͷ͋Γ͕ͨΈ - CustomPainterͰදݱͷ෯͕޿͕Δ - ൺֱత௿ϨϕϧͳCanvasΛ࢖͏͜ͱͰ ٯʹදݱͷ෯͕޿͕Δ(͜ͱ΋͋Δ)

Slide 41

Slide 41 text

͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠! ࣭໰ɾؾʹͳΔ͜ͱ͕͋Ε͹ ͍ͭͰ΋࿈བྷ͍ͩ͘͞! Follow me on Twitter (X?): @YumNumm TestFlight ιʔείʔυ