FlutterKaigi2021 2021/11/30 19:45〜 Live レギュラートーク(30分) https://fortee.jp/flutterkaigi-2021/proposal/d80e1204-3fd5-4c55-b728-83ee1dbf1d01
ΞΫηγϏϦςΟʔ͕ߴ͍FlutterΞϓϦέʔγϣϯΛ։ൃ͢ΔFlutter Kaigi 2021 Day21
View Slide
ࣗݾհ• ઋੴ ߊٱ (Sengoku Akihisa)• גࣜձࣾαΠόʔΤʔδΣϯτ• 2018 - 2021 Ξϝʔόϒϩά iOSΤϯδχΞ• 2021- ݱࡏ WINTICKET FlutterΤϯδχΞ2
WINTICKETͰͷΞΫηγϏϦςΟͷऔΓΈ• WINTICKETʮαʔϏεΛ௨ͯ͠୭ͰެӦڝٕΛָ͠ΊΔΑ͏ɺΞΫηγϏϦςΟͷ্ʹΊ͍ͯ·͢ɻʯ• WebͰWCAGͷγϯάϧAͷ४ڌΛඪͱ͍ͯ͠Δɻ• ݱࡏɺΞϓϦFlutterͷΞϓϦʹϦϓϨʔεΛߦͳ͓ͬͯΓɺͦΕʹ͍ΞϓϦͷΞΫηγϏϦςΟͷ্Λߦͳ͍ͬͯΔ• https://www.cyberagent.co.jp/way/csr/accessibility/3
ΞδΣϯμ• Flutter ΞΫηγϏϦςΟ• Scale Factor• Sufficient Contrast• Screen Reader• ΞΫηγϏϦςΟରԠ4
͞ͳ͍͜ͱ• Flutter for WebͷΞΫηγϏϦςΟ5
Flutter ΞΫηγϏϦςΟ ࢀߟ• Flutter• Flutter.dev Accessibility• iOS• Human Interface Guideline Accessibility• Android• Material Design Accessibility6
Flutter ΞΫηγϏϦςΟ• Large Fonts• ϑΥϯταΠζ͕େ͖͘ͳͬͯσβΠϯ่͕Εͳ͍Α͏ʹ͢Δ• σόΠεͰઃఆ͞Ε͍ͯΔϑΥϯτͷεέʔϧΛ༻ͯ͠ɺΞϓϦͷϑΥϯταΠζ͕ܾఆ͢Δ• Sufficient Contrast• ΞϓϦͷίϯςϯπ͕ेͳίϯτϥετൺ͕อͨΕ͍ͯΔ• Screen Reader• εΫϦʔϯϦʔμʔΛ༻ͯ͠ɺΞϓϦͷίϯςϯπΛಡΈ্͛Δ͜ͱ͕Ͱ͖Δ7
Flutter Checklist• Active interactions:Ϣʔβͷಈ࡞ʹରͯ͠ͷϑΣʔυόοΫΞϓϦͷ࣮ߦ݁ՌͷϑΟʔυόοΫ͕͋Δ͔ʁ• Screen reader testing: εΫϦʔϯϦʔμʔΛ༻ͯ͠ΞϓϦΛ༻͢Δ͜ͱ͕Ͱ͖Δ͔ʁ• Contrast ratios: ίϯτϥετൺ͕ेʹอͨΕ͍ͯΔ͔ʁ• Context switching: Ϣʔβͷ֬ೝͳ͠ͰϢʔβͷίϯςϯπͷมߋ͍ͯ͠ͳ͍͔ʁ• Tappable targets: 48px *48px ͷλοϓྖҬ͕֬อ͞Ε͍ͯΔ͔ʁ• Errors: ΤϥʔͱҰॹʹमਖ਼ํ๏͕Θ͔Δ͔ʁ• Color vision deficiency testing: ৭֮ҟৗϞʔυͱάϨʔεέʔϧϞʔυͰ༻ՄೳͰ͋Δ͔ʁ• Scale factors: ϑΥϯταΠζΛେ͖ͯ͘͠σβΠϯ่͕Εͳ͍͔ʁ8
iOSͱAndroidͷΞΫηγϏϦςΟiOS44px * 44px3:1Voice Over̋×Android48px * 48px3:1 or 4.5:1(14ptະຬ)Talk Back̋̋λοϓྖҬBoldίϯτϥετൺScreen ReaderϑΥϯταΠζͷมߋදࣔαΠζͷมߋ9
Flutter Framework Material• λοϓྖҬͷ࠷খͰ͋Δ48px͕ఆٛ͞Ε͍ͯΔ•flutter / lib / src / material / constant.dart• const double kMinInteractiveDimension = 48.0;• MaterialͷIconButtonͰ༻͞Ε͓ͯΓɺσϑΥϧτͰ48pxͷλοϓྖҬ͕֬อ͞ΕΔ•flutter / lib / src / material / icon_button.dart• BoxConstraints(minWidth: _kMinButtonSize, minHeight: _kMinButtonSize)10
Flutter Framework Cupertino• λοϓྖҬͷ࠷খͰ͋Δ44px͕ఆٛ͞Ε͍ͯΔ•flutter / lib / src / cupertino / constraints.dart• const double kMinInteractiveDimensionCupertino = 44.0;• CupertinoButtonͰ༻͞Ε͓ͯΓɺσϑΥϧτͰ44pxͷλοϓྖҬ͕֬อ͞ΕΔ•flutter / lib / src / cupertino / button.dart11
σβΠϯσʔλͷαϯϓϧ• λοϓྖҬɿ40px * 40px• IconSizeɿ32px• Paddingɿ4px4px• IconButton• padding: 4• Icon: Icon()12
࣮ ྫ4px13
࣮ ྫIconButtonͷConstraintσϑΥϧτͰ48pxͷͨΊɺsizeͱpaddingͷࢦఆͷΈͰ࣮Ͱ͖ͣɺConstraintͷࢦఆ͕ඞཁ😢14
ΞΫηγϏϦςΟΛཧղ͓ͯ͘͜͠ͱͰΞΫηγϒϧͳΞϓϦ։ൃΛ!15
ΞδΣϯμ• Flutter ΞΫηγϏϦςΟ• Scale Factor• Sufficient Contrast• Screen Reader• ΞΫηγϏϦςΟରԠ16
System Font Scale17
System Font Scale18
System Font Scale19
System Font Scale• MediaQueryData• ϝσΟΞʢγεςϜʣใʹ͍ͭͯ• MediaQueryData.textScaleFactor: ϑΥϯτͷεέʔϧൺ• MediaQueryData.size: ΞϓϦ͕දࣔ͞Ε͍ͯΔWindow Size• MediaQueryData.boldText: ϑΥϯτΛଠ͘͢Δࢦఆ• MediaQueryData.highContrast: ίϯτϥετΛ্͛Δࢦఆ20
App Bar Title͕`1.3ͱ2.0ͰมԽ͍ͯ͠ͳ͍ʁ21
App Bar• ࠷େαΠζʢkMaxTitleTextScaleFactor: 1.34ʣΛࢦఆͯ͠σβΠϯΛҡ͍࣋ͯ͠Δ22
App Bar σβΠϯ่͕Εͳ͍Α͏ʹ23
TextButton• test/material/text_butt• ϑΥϯτͷεέʔϧʹΑͬͯTextButtonͷScale͕มΘΔͷ͔ͷςετέʔε͕ଘࡏ24
Example• RowͱColumnͰ࡞ΒΕͨΑ͋͘Γͦ͏ͳWidget• ͺͬͱΈͳ͠🙆25
Widget Test Passed26
Widget Test Failed27
Error Message28
Error Message29
Error Message• SizedBoxͰߴ͕͞ࢦఆ͞Ε͓ͯΓɺColumnׂ͕ΓͯΒΕͨεϖʔεΑΓ͘ͳΔͨΊ30
FixSizedBoxΛআ31
Widget Test Failed32
Widget Test Failed33
Error Message34
Fixed35
Widget Test Failed36
࣮࣌ͷҙ• ࣮ࡍʹtextScaleFactorΛมԽͤͯ֬͞ೝͯ͠ΈΔͷ͕Ұ൪ྑ͍😅• TextͷදࣔྖҬͷߴ͞ or ԣ෯Λݻఆ͍ͯ͠Δ ← ΄΅͜Ε• → PaddingΛ͏ͳͲͯ͠ಈతͳϨΠΞτʹमਖ਼• → MediaQueryͰWidgetΛWrapͯ͠maxͷtextScaleFactorΛࢦఆ• จݴ͕͍έʔε2ߦͷέʔε• ยଆͷPadding͕ਖ਼֬ʹઃఆ͞Ε͍ͯͳ͍37
ิ Display SizeͷରԠ• AndroidͰදࣔαΠζΛมߋ͢Δ͜ͱ͕Ͱ͖Δ• දࣔαΠζΛมߋ͢Δ͜ͱͰΞϓϦͷදࣔ͞ΕΔWindowSize͕มΘΔ• Display Sizeখ͘͞ → WindowSize େ͖͘• Display Sizeେ͖͘ → WindowSize খ͘͞38
ϨεϙϯγϒͳUI• DisplaySizeΛେ͖͘ʢදࣔྖҬ͕খ͘͞ͳΔʣͱ͖ʹσβΠϯΛҡ࣋• LayoutBuilder• ImageͷAspectൺͷҡ࣋• AspectRatio• Document• https://docs.flutter.dev/development/ui/layout/adaptive-responsive#creating-a-responsive-flutter-app39
ΞδΣϯμ• Flutter ΞΫηγϏϦςΟ• Scale Factor• Sufficient Contrast• Screen Reader40
Sufficient Contrast• 4.5 : 1• 18ptະຬ Regular Text,14ptະຬ Bold Text• 3: 1• 18ptҎ্ Regular Text,14ptҎ্ ,Bold Text , จࣈը૾• W3Cͷਪ• https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html41
Contrast Check• Android Ϣʔβʔิॿݕূπʔϧ• https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor• λοϓྖҬͱίϯτϥετൺͷ֬ೝ͕Մೳ42
meetsGuideline• widget͕ΞΫηγϏϦςΟΛຬ͍ͨͯ͠Δ͔Λ֬ೝ͢Δ͜ͱ͕Ͱ͖ΔMatcher• AsyncMatcher meetsGuideline(AccessibilityGuideline guideline)• AccessibilityGuideline• textContrastGuideline: ςΩετͷίϯτϥετ• androidTapTargetGuideline: AndroidͷλοϓྖҬ 48 * 48• iOSTapTargetGuideline: iOSͷλοϓྖҬ 44 * 44• labeledTapTargetGuideline: λοϓΤϦΞʹηϚϯςΟοΫϥϕϧͷ༗ແ43
textContrastGuideline44
textContrastGuideline45
meetsGuideline• FlutterGalleryͷDemoͷΞΫηγϏϦςΟͷςετʹΘΕ͍ͯΔ• https://github.com/flutter/flutter/blob/24207183899d546983873dd6d6e3b80a2825a982/dev/integration_tests/flutter_gallery/test/accessibility_test.dart46
ΞδΣϯμ• Flutter ΞΫηγϏϦςΟ• Scale Factor• Sufficient Contrast• Screen Reader• ΞΫηγϏϦςΟରԠ47
Screen Reader48
Semantics Widget• WidgetʹηϚϯςΟοΫʢҙຯʣใΛ༩͢Δ• VoiceOverTalkbackͳͲͷࢧԉπʔϧͰ༻͍ΒΕΔ• labelҎ֎ʹ༷ʑͳҙຯใΛ༩Մೳ49
Semantics Tree• WidgetTreeΛ࡞͢ΔͱSemanticsTree͕ੜ͞ΕΔ• WidgetTreeͱSemanticsTree1ର̍ͰରԠ͍ͯ͠ͳ͍• SemanticsใΛอ͍࣋ͯ͠ͳ͍WidgetෳͷWidget͕Ϛʔδ͞ΕΔͨΊWidgetTree SemanticsTree50
Semantics Node• SemanticsWidgetΛ͏͜ͱͰSemanticsNodeΛੜ͢Δ͜ͱ͕Ͱ͖Δ• SemanticsNodeʹSemanticsWidgetͰ༩ͨ͠ҙຯใ͕ؔ࿈͚ΒΕ͍ͯΔSemanticsNode- Label: `Flutter`- Button: true- Enable: true51
Flutter Skelton Template• ύοέʔδͷதͰSemanticsͷWidgetΘΕ͍ͯͳ͍• ͏·͘εΫϦʔϯϦʔμʔͰಡΈ্͛Մೳʁ52
Material IconButton• Semantics͕ΘΕ͓ͯΓɺSemanticใ͕༩͞Ε͍ͯΔ• ଞͷWidgetIconButtonಉ༷ʹదͳSemanticใΛอ࣋ & ઃఆͰ͖ΔΑ͏ʹͳ͍ͬͯΔ53
Sample Code• SemanticsͲ͏ͳΔ͔ʁ54
Sample Code 2ᶃᶄ55
Widget Tree and Semantics TreeCardText TextButtonlabel: 'FlutterKaigi'Label: 'Start'Button: trueɾɾɾলུText56
Semantics Widget Property• bool container (default false)• trueͷ߹ʹ৽͍͠SemanticsNodeΛੜ͢Δ• falseͷ߹ͷSemanticsNodeʹϚʔδ͞ΕΔʢ͕ڐՄ͍ͯ͠Δ߹ʣ• bool explicitChildNodes (default false)• trueͷ߹ࢠͷSemanticsNodeΛࣗͷSemanticsNodeʹϚʔδ͕ෆՄʹͳΔ• falseͷ߹Ϛʔδ͢Δ͜ͱ͕Մೳ• bool excludeSemantics (default false)• trueͷ߹ʹchildͷSemanticsNodeΛআ֎͢Δ57
Widget Tree and Semantics TreeCardcontainer: trueTextlabel 'Flutter'label: 'FlutterKaigi'Button: trueLabel: 'Start'ɾɾɾলུTextlabel 'Kaigi'Buttoncontainer: truebutton: trueTextlabel 'Start!'58
Widget Tree and Semantics TreeCardcontainer: trueTextlabel 'Flutter'label: 'FlutterKaigi'ɾɾɾলུTextlabel 'Kaigi'Buttoncontainer: truebutton: trueTextlabel 'Start!'mergemergeButton: trueLabel: 'Start!'59
ExplicitChildNodesCardcontainer: trueexplicitChildNodes: trueTextlabel 'Flutter'Button: trueLabel: 'Start'ɾɾɾলུTextlabel 'Kaigi'Buttoncontainer: truebutton: trueTextlabel 'Start!'Label: 'Kaigi'Label: 'Flutter'60
Semantics Class• MergeSemantics: childͷsemanticsΛϚʔδ• ExcludeSemantics: childͷsemanticsΛআ֎• BlockSemantics: Widgetͷഎޙʹ͋ΔSemanticsΛແޮʢStackͷWidgetΛҰॹʹ༻Մೳʣ• OrdinalSortKey: ಡΈ্͛ॱংΛorderʹԠͯ͡ιʔτʢSemanticsͷsortKeyʹࢦఆʣ61
SemanticsͷLabelͷ͚ํ• WCAGWCAGͷୡํ๏ूΛࢀߟ• https://waic.jp/docs/WCAG21/• https://waic.jp/docs/WCAG21/Techniques/• ྫᶃ : ը૾ʹίϯςϯπΛཧղ͢ΔͨΊͷจࣈؚ͕·ΕΔ• ಉ͡จࣈΛSemanticsͷLabelʹ༩• ྫᶄɿ০తͷը૾• SemanticsͷใΛ༩͠ͳ͍62
࣮࣌ͷҙ• ࣮ࡍʹ֬ೝͯ͠ΈΔͷ͕Ұ൪ྑ͍😅• ը૾• Exclude or Labelͷ༩• CustomWidget• ଐੑͷ༩ʢButton, SelectedͳͲʣ• Mergeͯ͠1ͭͷSemanticsNodeͱͯ͠ಡΈ্͛Δ63
ΞδΣϯμ• Flutter ΞΫηγϏϦςΟ• Scale Factor• Sufficient Contrast• Screen Reader• ΞΫηγϏϦςΟରԠ64
։ൃͰʁػೳ։ൃΞΫηγϏϦςΟͷνΣοΫΞΫηγϏϦςΟͷνΣοΫΞΫηγϏϦςΟͷνΣοΫΞΫηγϏϦςΟରԠΞΫηγϏϦςΟରԠΞΫηγϏϦςΟରԠ• ݄ʹ1ճͷΞΫηγϏϦςΟΛߦͳ͍ͬͯΔ• ͨ͠ػೳը໘ͷΞΫηγϏϦςΟΛνΣοΫͯ͠ɺͦͷޙ͙͢ʹमਖ਼ͷରԠΛߦ͏65
ྑ͔ͬͨ͜ͱ• νʔϜͷΞΫηγϏϦςΟͷϨϕϧ্͕͕Δ• ௨ৗͷ։ൃ͔ΒΞΫηγϏϦςΟΛҙࣝͨ͠ίʔυΛॻ͘͜ͱ͕Ͱ͖ɺޙ͔ΒͷରԠίετ͕Լ͕Δ• ϨϏϡʔͰͷΞΫηγϏϦςΟͷࢦఠ͕Ͱ͖Δ• UIͷઃܭ࣌ʹΞΫηγϏϦςΟΛߟྀ͢Δ͜ͱ͕Ͱ͖Δ• ڞ௨ͷCustomWidgeΞΫηγϏϦςΟରԠࡁΈ66
·ͱΊ• ։ൃதͷΞϓϦͷΞΫηγϏϦςΟΛνΣοΫͯ͠ΈΑ͏• ࠒ͔ΒΞΫηγϏϦςΟΛߟྀͨ͠։ൃΛߦ͏͜ͱͰɺগͳ͍ίετͰΞΫηγϏϦςΟ͕ߴ͍ΞϓϦͷ։ൃ͕Մೳ67