Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Flutterを言い訳にしない!アプリの使い心地改善テクニック5選🔥
Search
Kuno Ayana
November 20, 2024
Programming
3
700
Flutterを言い訳にしない!アプリの使い心地改善テクニック5選🔥
FlutterKaigi 2024 の LT です!
Kuno Ayana
November 20, 2024
Tweet
Share
More Decks by Kuno Ayana
See All by Kuno Ayana
iOS 18 がやってきた!
kno3a87
1
210
おうちハッカソン #2
kno3a87
0
130
ミクアカ成果報告会
kno3a87
0
33
SXSW2021
kno3a87
0
48
ミクアカ中間発表会
kno3a87
0
23
大学院進学ガイダンス
kno3a87
0
82
画像処理論セミナー7-1-3
kno3a87
0
23
内定者自己紹介LT
kno3a87
0
77
画像処理論セミナー7-1,2
kno3a87
0
20
Other Decks in Programming
See All in Programming
The Evolution of Enterprise Java with Jakarta EE 11 and Beyond
ivargrimstad
0
380
Hack Claude Code with Claude Code
choplin
8
2.7k
Flutterで備える!Accessibility Nutrition Labels完全ガイド
yuukiw00w
0
180
Git Sync を超える!OSS で実現する CDK Pull 型デプロイ / Deploying CDK with PipeCD in Pull-style
tkikuc
4
400
猫と暮らす Google Nest Cam生活🐈 / WebRTC with Google Nest Cam
yutailang0119
0
180
構造化・自動化・ガードレール - Vibe Coding実践記 -
tonegawa07
0
120
20250704_教育事業におけるアジャイルなデータ基盤構築
hanon52_
5
1.2k
チームのテスト力を総合的に鍛えて品質、スピード、レジリエンスを共立させる/Testing approach that improves quality, speed, and resilience
goyoki
5
1.2k
AI時代のソフトウェア開発を考える(2025/07版) / Agentic Software Engineering Findy 2025-07 Edition
twada
PRO
101
38k
CDK引数設計道場100本ノック
badmintoncryer
2
520
AWS Summit Japan 2024と2025の比較/はじめてのKiro、今あなたは岐路に立つ
satoshi256kbyte
1
210
コーディングエージェント概観(2025/07)
itsuki_t88
0
100
Featured
See All Featured
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
161
15k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
2.9k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
8
360
We Have a Design System, Now What?
morganepeng
53
7.7k
How STYLIGHT went responsive
nonsquared
100
5.6k
Art, The Web, and Tiny UX
lynnandtonic
300
21k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
126
53k
Making Projects Easy
brettharned
116
6.3k
Adopting Sorbet at Scale
ufuk
77
9.5k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
Into the Great Unknown - MozCon
thekraken
40
1.9k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
35
2.4k
Transcript
ʙΞϓϦͷ͍৺վળςΫχοΫબʙ 'MVUUFSΛݴ͍༁ʹ͠ͳ͍ʂ
None
ͳΜ͔εΫϩʔϧ͢Δͱ ΧΫπΫΜ͚ͩͲʜ
ͳΜ͔εΫϩʔϧ͢Δͱ ΧΫπΫΜ͚ͩͲʜ ͔ͣͬ͠ͱݟͯΔͱ ͕͘ͳͬͯΔؾ͕͢Δʂ
None
ಈը͕දࣔ͞Ε͍ͯΔͱ͖ʹͦ͏
None
˞J1IPOF J04 Λ༻
None
Ƃō
None
5FYUVSF8JEHFU͏ͱ ݹΊͷJ1IPOFͰύϑΥʔϚϯε͕ൃੜ 1MBUGPSN7JFXΛ͓͏ʂ
None
ݱࡏͷϏσΦϑϨʔϜͷ(16্ͷϐΫηϧόοϑΝʔΛ ຖϑϨʔϜ$16ʹίϐʔ͍ͯ͠Δ ͦΕ͕ॏͦ͏ʁ
1MBUGPSN7JFX͔͓ͭ?@?
None
ݟ͚ͭͨʂʂʂ ˞࣮ࡍͷϓϩμΫτͰɼωΠςΟϒଆͰ)-4ϓϨΠϦετͷඇಉظϩʔυ͕ඞཁͩͬͨΓ )%3ରԠ͕ඞཁͩͬͨΓͨ͠ͷͰɼͪ͜ΒͷϥΠϒϥϦΛࢀߟʹϑϧεΫϥονͯ͠·͢
˞J1IPOF J04 Λ༻
None
ͳΜ͔ΞΠίϯઃఆ ө͞ΕΔ·Ͱ͘ͳ͍ʁ
None
UP#ZUF%BUBͰQOHΛ%BSU7.Ͱ Τϯίʔυ͢Δͱ͜ΖͰ͕͔͔࣌ؒͬͯΔ
ϥΠϒϥϦଆͰը૾ʹͤͣ 3(#"όΠτͷ··ฦ͢Α͏ʹϥΠϒϥϦΛվ ωΠςΟϒଆͰ3(#"ΛΫϩοϓͯ͠ը૾Խ͢ΔΑ͏ʹʂ
None
None
J1IPOF࣌ܭͷͱ͜λοϓͰϦετ͕ Ұ൪্·Ͱͬͯཉ͍͠Μ͚ͩͲʜ
None
Ұ൪্·ͰεΫϩʔϧͯ͘͠Εͳ͍ʜ
4DB ff PME -JTU7JFX
4DB ff PME -JTU7JFX 1SJNBSZ4DSPMM$POUSPMMFS
4DB ff PME -JTU7JFX 1SJNBSZ4DSPMM$POUSPMMFS 1SJNBSZ4DSPMM$POUSPMMFS
4DB ff PME -JTU7JFX 1SJNBSZ4DSPMM$POUSPMMFS 4DSPMM$POUSPMMFS ԣʹεΫϩʔϧόʔΛग़ͨ͠Γ Ϧετඌ·ͰεΫϩʔϧ͞ΕͨݕΛ͢ΔͨΊՃ
4DB ff PME -JTU7JFX 1SJNBSZ4DSPMM$POUSPMMFS 4DSPMM$POUSPMMFS εΫϩʔϧόʔΛग़ͨ͠Γ Ϧετඌ·ͰεΫϩʔϧ͞ΕͨݕΛ͢ΔͨΊՃ εςʔλεόʔλοϓͷݕ ͬͪ͜ʹ͘Δʂ
4DB ff PME -JTU7JFX 1SJNBSZ4DSPMM$POUSPMMFS 4DSPMM$POUSPMMFS εΫϩʔϧόʔΛग़ͨ͠Γ Ϧετඌ·ͰεΫϩʔϧ͞ΕͨݕΛ͢ΔͨΊՃ εςʔλεόʔλοϓͷݕ ͬͪ͜ʹ͘Δʂ
ແԠʂ
4DB ff PME -JTU7JFX 1SJNBSZ4DSPMM$POUSPMMFS 4DSPMM$POUSPMMFS εΫϩʔϧόʔΛग़ͨ͠Γ Ϧετඌ·ͰεΫϩʔϧ͞ΕͨݕΛ͢ΔͨΊՃ εςʔλεόʔλοϓͷݕ ͬͪ͜ʹ͘Δʂ
ແԠʂ 1SJNBSZ4DSPMM$PUSPMMFSͷ λοϓݕΛΩϟον͢Εྑ͍ʂ
class _FakeScrollPositionWithSingleContext extends ScrollPositionWithSingleContext { _FakeScrollPositionWithSingleContext({ required BuildContext context, required
Future<void> Function() callback, }) : _callback = callback, super( physics: const NeverScrollableScrollPhysics(), context: _FakeScrollContext(context), ); final Future<void> Function() _callback; @override Future<void> animateTo( double to, { required Duration duration, required Curve curve, }) { return _callback.call(); } }
class _FakeScrollPositionWithSingleContext extends ScrollPositionWithSingleContext { _FakeScrollPositionWithSingleContext({ required BuildContext context, required
Future<void> Function() callback, }) : _callback = callback, super( physics: const NeverScrollableScrollPhysics(), context: _FakeScrollContext(context), ); final Future<void> Function() _callback; @override Future<void> animateTo( double to, { required Duration duration, required Curve curve, }) { return _callback.call(); } } ͕ࣗͨͪͨ͠ ίʔϧόοΫ͕ݺΕΔΑ͏ʹ
class _FakeScrollPositionWithSingleContext extends ScrollPositionWithSingleContext { _FakeScrollPositionWithSingleContext({ required BuildContext context, required
Future<void> Function() callback, }) : _callback = callback, super( physics: const NeverScrollableScrollPhysics(), context: _FakeScrollContext(context), ); final Future<void> Function() _callback; @override Future<void> animateTo( double to, { required Duration duration, required Curve curve, }) { return _callback.call(); } } ͕ࣗͨͪͨ͠ ίʔϧόοΫ͕ݺΕΔΑ͏ʹ 4DSPMM1PTJUJPO8JUI4JOHMF$POUFYUΛ ܧঝِͨ͠Λ࡞Δʂ
final primaryScrollController = PrimaryScrollController.maybeOf(Navigator.of(context).context) ?? PrimaryScrollController.maybeOf(context); if (primaryScrollController == null)
return; final scrollPositionWithSingleContext = _FakeScrollPositionWithSingleContext( context: context, callback: widget.onTap, ); primaryScrollController.attach(scrollPositionWithSingleContext);
final primaryScrollController = PrimaryScrollController.maybeOf(Navigator.of(context).context) ?? PrimaryScrollController.maybeOf(context); if (primaryScrollController == null)
return; final scrollPositionWithSingleContext = _FakeScrollPositionWithSingleContext( context: context, callback: widget.onTap, ); primaryScrollController.attach(scrollPositionWithSingleContext); ͦͷِΛ 1SJNBSZ4DSPMM$POUSPMMFSʹΞλονʂ
None
None
J1IPOFඪ४ͱൺͯ ֆจࣈͷදࣔখ͘͞ͳ͍ʁ
None
None
SwiftUI Flutter
SwiftUI Flutter
SwiftUI Flutter খ͍͞Θʂʂʂ
ྗٕͰ ղܾ
import 'package:flutter/material.dart'; class EmojiUtil { static const r = r'[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-
\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED- \u23EF\u23F1\u23F2\u23F8- \u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600- \u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E \u262F\u2638-\u263A\u2640\u2642\u2648- \u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694 - \u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4 \u26C8\u26CF\u26D1\u26E9\u26F0- \u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D \u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05- \u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?| [\u261D\u270C\u270D](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\u270A\u270B](?: \uD83C[\uDFFB-\uDFFF])?|[\u23E9- \u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD \u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]| \u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uFE0F|\uD83C[\uDFFB-
text.splitMapJoin( EmojiUtil.regex, onMatch: (m) { final emojiCharacters = m.group(0)!.characters; for
(final emojiCharacter in emojiCharacters) { state._add(emojiCharacter, true); } return ''; }, onNonMatch: (s) { if (s == '\u200D') { // ZeroWidthJoinerֆจࣈͱͯ͠ొ͓ͯ͘͠ state._add(s, true); } else { state._add(s, false); } return ''; }, ); return state._build(); } }
text.splitMapJoin( EmojiUtil.regex, onMatch: (m) { final emojiCharacters = m.group(0)!.characters; for
(final emojiCharacter in emojiCharacters) { state._add(emojiCharacter, true); } return ''; }, onNonMatch: (s) { if (s == '\u200D') { // ZeroWidthJoinerֆจࣈͱͯ͠ొ͓ͯ͘͠ state._add(s, true); } else { state._add(s, false); } return ''; }, ); return state._build(); } } ड͚औͬͨςΩετΛ ֆจࣈͱจࣈʹׂ
TextStyle _emojiStyle(TextStyle textStyle, double originalFontSize) { if (Platform.isAndroid) { return
textStyle; } else { return textStyle.copyWith( fontSize: textStyle.fontSize! * 1.4, fontFamilyFallback: [], height: 1.0, ); } } ͱΓ͋͑ͣഒʹͯ͠ΈΔʂ
Before After
࣮ࡍͷϓϩμΫτͰͪ͜ΒͷJTTVFΛࢀߟʹɼϑΥϯτͷେ͖͞ʹΑͬͯֆจࣈͷഒΛม͑ͨΓ จࣈͱ֦େֆจࣈͷηϯλʔϥΠϯΛἧ͑ͨΓ͍ͯ͠·͢ʂ
None
ͳΜ͔J1IPOFͩͱ จࣈ͕ࡉͯ͘ݟʹ͍͘ؾ͕͢Δʜ
SwiftUI Flutter
None
None
ϑΥϯτϨϯμϦϯά࣌ͷ ΞϯνΤΠϦΞεͷೱ͕ωΠςΟϒͱҧ͏ʂ
ྗٕͰ ղܾ
return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: Text(widget.title), ), body:
const Center( child: Stack( children: [ Text( 'Hello, FlutterKaigi 2024 LT!', style: TextStyle(fontSize: 24), ), Text( 'Hello, FlutterKaigi 2024 LT!', style: TextStyle(fontSize: 24), ), ], ), ), );
ild: Stack( children: [ Text( 'Hello, FlutterKaigi 2024 LT!', style:
TextStyle(fontSize: 24), ), Text( 'Hello, FlutterKaigi 2024 LT!', style: TextStyle(fontSize: 24), ),
Before After
˞࣮ࡍͷϓϩμΫτͰͪ͜ΒͷJTTVFίϝϯτIUUQTHJUIVCDPN fl VUUFS fl VUUFSJTTVFTJTTVFDPNNFOUΛࢀߟʹ ͨͩ4UBDLͰॏͶΔͷͰͳ͘4USPLFΛௐͯͨ͠Γ͠·͢ʂ
Sample GitHub: https://github.com/kno3a87/flutterkaigi-2024-lt ͓ΘΓ
,VOP"ZBOB גࣜձࣾ.*9*ͨΜΆΆࣨ 'MVUUFS,BJHJӡӦελοϑ