ComposeでのlabelFor対応
最近「labelFor属性はComposeでどうすれば良いか?」と聞かれて、「2021年のDroidKaigi登壇で答えたから見てね」って答えたけど、確認したら説明してませんでした。失礼しました😇
labelFor属性とは
XMLでラベルとEditTextを分けて書いた場合、入力箇所の用途がTalkBackユーザーにより分かりやすく伝わるように、ラベルのTextViewに設定すべきものです。
入力箇所のラベルと入力された内容(もしくは初期のヒント)が一緒に読み上げられます。
ヒントは何か入力されたら消えるものなので、入力した内容しか案内されなくなってしまいます。
そして特に一つの画面に複数の入力箇所があれば、要素をグループ化してフォーカス移動の数を減らした方がより良い体験に繋がるそうです。
なので、通常の体験で見えないラベルでも良いから、ラベルをつけた方が良さそうです。
<TextView
// ...
android:labelFor="@id/editText"
android:text="お名前" />
<EditText
android:id="@+id/editText"
// ... />
結果はこんな感じになります。
- 何も入力されてない場合:「編集ボックス、お名前」
- 「ティフェン」と入力した場合:「ティフェン、編集ボックス、お名前」
詳しくは公式ドキュメントとアクセシビリティサポートを確認しましょう。
Composeだとこの書き方が出来ませんけど、別のやり方で同じ効果が得られます。
① マテリアルデザインに従うアプリの場合
マテリアルデザインのシステムではこの問題は既に配慮されています。デフォルトのコンポーネントを使えば、ラベルは直接TextFieldに設定できます。
TextFieldかOutlinedTextFieldを使って、Text Composableをlabelとして渡すだけで、完成です。labelForと同じ感じで一緒に読み上げられます。終わり 🎉
var name by rememberSaveable { mutableStateOf("") }
TextField(
value = name,
onValueChange = { newText -> name = newText },
label = { Text(text = "お名前") }
)
。。。しかし担当アプリのデザインがマテリアルデザインのシステムに従わない場合、同じ効果を得るのに少し工夫が必要になります 😵💫
② その他(ほとんど)のアプリの場合
例えば、こんな仕様を渡されたとしましょう。
セマンティクスを結合する
まぁラベルはTextで、入力する部分はマテリアルデザインのラベル無しOutlinedTextFieldで、両方をColumnに入れれば、と考えてしまうでしょう。
そしてlabelFor効果は、その親Columnのセマンティクスを結合させればいいじゃん、楽勝〜、と呟きながらこう書くでしょう。(少なくとも私はそうです🥲)
var name by rememberSaveable { mutableStateOf("") }
// コピペしないでね
Column(modifier = Modifier.semantics(mergeDescendants = true){}) {
Text(text = "お名前")
OutlinedTextField(
value = name,
onValueChange = { newText -> name = newText }
)
}
。。。残念ながらこれじゃ問題は解決されません🙅♀️
確かにTalkBackフォーカスは見た目的にColumn全体を包みますが、読み上げられるのは「お名前」のラベルだけです。「編集ボックス」や「ダブルタップでテキストを編集します」とか、TextField系は何も案内されません。
実際ダブルタップすれば、ちゃんと入力モードに入りますけど、TalkBackユーザーからすれば、入力できる事がまず伝わってません。
ちなみにマテリアルデザインのTextFieldはヒントがないとTalkBackフォーカスをもらえないバグ(?)があるので、上のコードでセマンティクスを結合しなければ、そもそもTalkBackモードで入力ができません。
一応、マテリアルデザインのTextFieldではなく、Composeのfoundationライブラリに入っているBasicTextFieldなら、TextField系の案内は読み上げられますし、ヒントがなくても無視されません。
var name by rememberSaveable { mutableStateOf("") }
// これもlabelFor効果にならないよ
Column(modifier = Modifier.semantics(mergeDescendants = true) {}) {
Text(text = "お名前")
BasicTextField(
// 適当に枠をつける(padding調整などは省略する)
modifier = Modifier.border(
width = 1.dp,
color = Color.Gray,
shape = RoundedCornerShape(percent = 8)
),
value = name,
onValueChange = { newText -> name = newText }
)
}
しかし、上と同様にColumn全体がフォーカスされても、読み上げられるのは「お名前」のラベルだけです。BasicTextFieldの内容を案内してもらうには、フォーカスを次の要素へ動かす必要があります。
つまりlabelFor効果になってません。
セマンティクス経由でグループ化する方法は、もう一つあります。Modifier.clearAndSetSemantics{}ですね。しかしこれを使うと、TextFieldのアクション(テキスト編集など)や案内を自分で管理することになります。
非常に面倒・・・かなりややこしくなりますし、TalkBackのプロではない限りあまりおすすめできません。
今回はセマンティクスに頼らず、別の方法を試してみましょう。
BasicTextFieldのdecorationBoxをカスタマイズする
decorationBox属性はTextFieldのレイアウトをカスタマイズする時に使います。
こんな感じでラベルとTextFieldの入ったColumnをdecorationBoxに渡します。
var name by rememberSaveable { mutableStateOf("") }
BasicTextField(
value = name,
onValueChange = { newText -> name = newText },
// フォントサイズを指定し直す必要がある
// TextStyle.Default.copy()はinnerTextFieldのレイアウトが崩れるから使わない
textStyle = TextStyle(fontSize = 16.sp),
decorationBox = { innerTextField ->
Column {
// ラベル
Text(text = "お名前")
// 枠のあるTextField(自作のComposable)
OutlinedTextFieldInput(
inputValue = name,
innerTextField = innerTextField
)
}
}
)
コードを読みやすくするために、入力する部分は別途書き出します。
枠をつけたり、デフォルトのpaddingなどOutlinedTextFieldのスタイルを引き継ぐためにOutlinedTextFieldDecorationBoxを使います。
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun OutlinedTextFieldInput(
inputValue: String,
innerTextField: @Composable () -> Unit
) {
// 枠をつけてもらう
TextFieldDefaults.OutlinedTextFieldDecorationBox(
value = inputValue,
innerTextField = {
Box(modifier = Modifier.fillMaxWidth()) {
// 入力したものは実際これで表示される
innerTextField()
}
},
// ここから下は本来親のBasicTextFieldから引き継ぐべき
// (とりあえずデフォルトの値にする)
visualTransformation = VisualTransformation.None,
interactionSource = MutableInteractionSource(),
enabled = true,
singleLine = false
)
}
これでちゃんとlabelFor効果になります😭✨
TL;DR
labelFor効果は、マテリアルデザインのシステムを使うか、BasicTextFieldのDecorationBoxをカスタマイズすれば再現できます。
実際動かしてみたい方はどうぞこちらへ:
ノシ
【メモ】アクセシビリティ リンク集
DroidKaigi 2022 - 感想
どうも🎃
今年もDroidKaigiに参加して来ましたー
去年は初参加 & 初登壇でした:
今年は新しい点で言うと、こちらですかね:
- オフライン参加
- オフラインカンファレンスでの登壇
- スポンサー企業の社員としての参加
- DroidKaigiスタッフになった
初めてがいっぱいw 😱
参加者として
コロナ対策がかなりしっかりしてるのを見て、オフライン参加にしてみました。
久しぶりに知り合いに会えて、昔のオフラインイベントが懐かしくなりました☺️
ただ、あまりゆっくり話せかったから、来年は知り合いと話す時間や企業ブースを回る時間も確保すべきだと反省してます。
ちなみに初DroidKaigiオフライン参加だったけど、いつものイベントと規模が違って、Androidエンジニアが思ったより多くてびっくりしました。
そしてmeetupでやっと海外のスピーカーとお話が出来ました!!!
エンジニアになってからずっと日本語環境だったし、英語でAndroidや業界の話が出来て、感動的でした😭✨
ネイルは出来なかったから来年リベンジします。。
セッションに関して、Chet Haaseさんが来れなかった事と、ユーザープライバシー関連のセッションが自分と同じ時間帯だった事はしょんぼりしましたw 🥺
でも見たかったセッションがいくつか見れました:
- Getting started with Dagger and Hilt
- マンガアプリのメモリ改善とメモリ解析方法
- 長期運用アプリのリファクタリングを考える
- Why Projects Succeed: Lessons Learned from the Android OS
- アクセシビリティは向上させる物 ~視覚障害当事者の立場で私が目指している理想的な社会~
- BLEを使ったアプリを継続的に開発するために
- All your Compose @Previews to screenshot tests without instrumentation
- Kotlinize your CI, from Gradle tuning to Kotlin Script
詳しくコメントしたかったけどこの記事が小説並みの長さになりそうなので、一言で言うとすごく勉強になりました、ありがとうございました👍
他はアーカイブがアップロードされたら全部見ますからね!!
Day 3はみんながパソコンを持ってきて、コードを書いたり、雑談をしてました。
雰囲気的に昔のTokyo Developers Meetupと同じ感じでした。(あれめっちゃ好きだったからオフライン復活したら絶対行きます!!)
午後はGDEなど豪華なメンバーとの相談会があって、私は自分のロールモデルであるyanzmさんとキャリア相談が出来ました。
贅沢すぎる。信じられない。
おかげさまで長期的な方向性が見えるようになって、とても感謝してます🙇♀️✨
頑張るぞー
登壇者として
今年も登壇が出来て、初DroidKaigiオフライン登壇になりました🎉
エンジニアになった時に上司に「DroidKaigiで登壇してみな」って何度も言われて、「私には無理ですよ」って答えてた自分を見返しました。(やれば出来るってば!)
またアクセシビリティ関連の話になったけど、ちゃんとバリエーションがありますw
去年は基礎知識とコードでの実装方法と言うAndroidエンジニア視点で話したので、今年は会社で具体的にどうやってアクセシビリティを開発プロセスに取り組めるかを説明しました。
セッションが同時通訳対象に選ばれて、英語スライドの用意をお願いされたけど、読み間違えて、日本語も英語も両方を作ってしまいました😇
まぁ今年の誤解は人に迷惑をかけない程度で済んで良かったですw
当日はまた緊張で手が震えてました。なかなか慣れません🥲
震えがひどくて次のスライドを表示するために毎回キーボードを見る事になってしまったから、オフライン登壇の時はクリッカーを持っていくべきだと分かりました。
あと、Google Slideを使ってたら、スピーカーモードでもスライドの上にURLやタブが表示されてて、カッコ悪かった。。非表示方法は登壇後に知りました。。。orz
次回こそ完璧を目指します🫠
Ask the Speakerのおかげで発表を聞いてくれた人と直接お話が出来て、アクセシビリティ推進で悩んでる人が自分だけじゃないって実感できて、感激でした。
やはりオフラインって良いなと思いました。
スライドはSpeakerDeckにアップロードしました。
ところで日本語版の閲覧数がおかしい??と思ったら、Twitterでスライドが妙にバズった事を発見しました😳
話題がAndroid専門ではないから、リーチが広くなるって事ですかね?
どんな理由でも、ありがたいです。
少しでもお役に立てれば、作った甲斐がありました。
スポンサー企業の社員として
去年入社したメドピア株式会社は今年プラチナスポンサーでした!
運営スタッフのシフトの間に企業ブースをお手伝いしました🙋♀️
(日本語と説明が下手だから本当に役に立てたかはなんとも言えないけどw)
他の部署のAndroidエンジニアとわいわいしながら企画を立てたり、(フルリモートだから)ブースで初めてリアルでお会い出来たり、自分の用意した質問がDroidQuizやチラシに使われたり、会社から登壇応援ツイートも書いていただいたり、嬉しいことばっかりでした。
そしてなんと、ブースを見に来てくれた人の中に自分の担当アプリのユーザーがいました!!
直接フィードバックを聞けました!!!
アプリエンジニアとしてこれ以上嬉しい事はありますか!!!!
もう。嬉しすぎて空が飛べるかと思いました。
自分が運営スタッフをやっているイベントのために、会社が投資をしたり、同僚がCfSに応募してくれたり、社内の賑やかな雰囲気を見て、やる気が倍になります😤
運営スタッフとして
2019年にオンライン学校の卒業プロジェクトに集中するために、次が決まってない状態で転職エージェントの仕事を辞めました。
辞める前から1年間ずっとぼっちでアプリを作ったり独学で勉強してたので、やはり仲間が欲しくて、勇気を出して初めてエンジニアイベントに参加してみました。
発表内容は興味深かったし、みんながわいわいしてるのを見て「良いな」と思ったけど、すごく心細かったです。
会社の肩書きがないのは居づらいか、全ての発表の内容を理解できないレベルの人は場違いじゃないか、男性が多いから女性で大丈夫か。。。外国人は自分だけで目立ってたのもあって、自分は本当にここにいて良いのか、終わったら早く帰ろうと、発表を聞きながら内心でモヤモヤしてました。
そこでもう一人の参加者に声をかけていただきました。普通に発表の感想を話し合ったり、知り合いを紹介してくれました。
そして(一人だとハードルが高すぎて絶対参加できなかった)イベント後の飲み会にも連れて行ってくれたおかげで、色んな人とAndroid開発の悩みを相談したり、知らない技術を教えていただきました。とても暖かいコミュニティだと実感して、またイベントに来たいなと思うようになりました。
あれからずっと私を歓迎してくれたコミュニティへの恩返しをしたいと考えてました。
それで自分に出来る事は多分この2つかなと言う結論に至りました:
- 今日本にない海外の情報/ノウハウを持ち込む
- 日本語話者と英語話者のコミュニティの交流を増やす
①に関して、アクセシビリティ関連の活動はまさにこれです。
②は多少の影響力がないと出来ないものなので、ずっと後回しにしてました。
しかし今年やっときっかけが来ました。DroidKaigiのボランティアスタッフ募集のお知らせです。
多様性を重視してるのを見て、カジュアル面談で詳しくお話を聞いて、自分の目標と一致してそうだと判断して、応募してみました。
めでたくスタッフになれて、グローバルチームに配属されました。主に日英翻訳(のレビュー😣ゴメンナサイ)、英語での問い合わせ対応と英語話者のアテンドをお手伝いしてます。
あとはチームに縛られずにやりたい事があれば手を上げれば良いと言われたので、遠慮なくあっちこっち意見を言ってみました😂
自分のいくつかの提案が本当に採用されて、具体化されたのを見て、正直けっこうビビったw みんな行動力高いなw
でもやはり新人でも貢献が出来るのは誇らしくて、とても嬉しかったです。
MTG中も分からない事があれば気軽に聞けるし、いつも同時に数人から説明が来ると言う、コミュニティの暖かい雰囲気がここにもあります。
今年は迷惑をかけまくった気がするけど、こんな自分でも良ければ(首にされなければw)来年はさらに良い感じにするように頑張りたいと思います。
またDroidKaigiでお会いしましょうー
ノシ
Accessiブランチ #14 レポート
昨日Accessiブランチと言うイベントに参加してきました。デジタル分野のアクセシビリティに興味ある人が月1回集まるオンラインランチです。
特に気になった点をまとめておきます。
モバイル系
AndroidのWebView画面でダイアログ(モーダル)が表示されてる時に、フォーカスがダイアログの外に行けるそうですって?!
軽く確認してみたところ、ネイティブ画面は問題なさそうですが、念のためアクセシビリティのチェックリストにこの点も追加した方が良さそうですね😨
Web系
先週Web系アクセシビリティのイベントがあって、かなり盛り上がってたようですねー
見逃したフロントエンドの方は是非見てみてください 👀
実装系
最近AndroidのTalkBackを使って、タブ選択後のフォーカスの動きが気になってて、質問をしてみました。
ボトムバーとか、タブがいくつかある画面で、一つのタブを選択した後は、フォーカスは選択したタブにそのまま残した方が良いですか?
それともトップ(例えば新しく表示されたリストの一番上のアイテム)へ遷移させた方が良いですか?
それで皆さんが話し合った結論は「残した方が良い」です🤔
理由はこの3つです:
- 通常トップへ遷移しないなら、ユーザーはこちらの動きに慣れてるはず
- いきなり遷移させられたら、わけ分からなくなる可能性がある
- 間違って選択したとか、隣のタブの内容を読み上げてもらったら隣の方に興味を持った場合が多そう
ただし、選択された(状態が変わった)ってアナウンスが必要です⚠️
Androidの場合、カスタムビューじゃなければ、通常は状態を読み上げてくれるはずです。
(正直アナウンスされたり、されなかったりする気がしますが。。)
とりあえず、フォーカスの動きのカスタマイズに関しては、(カスタムビュー以外の場合)「よっぽどの理由がなければ、しないほうが良い」だそうです!
デバイス系
Bonocleと言う点字ディスプレイの話を聞きました。
左から右へとか、決められた方向で1行1行を読むのではなくて、
マウスを使う時と同じ感じで、デバイスを動かして、ポインター(?)の下にある内容が点字に変換されます。
パソコンのマウスのサイズで、モーションセンサーやハプティックフィードバックがついてて、AndroidとiOS両方とでも使えてて、ゲームも出来ます。
そして一番びっくりしたところは、点字のセルが一桝しかないです。
なんか新しい時代が来たな、とちょっと感動しました。
公式サイトに詳しく説明されてます:
Bonocleみたいに最先端ではないけど、ドットビューとオプタコンの話も出ました 。
どっちも知らなかったので、勉強になりました👀
ゲーム系
いろいろ話しましたが、ストリートファイター6の広告で音声の良さ(ちゃんと右と左を分けてる?)と、声優の声が変わったってしか覚えてませんw
そういうところ、気にするんですねー
あとはアクセシビリティ全く関係ないけど、「謎かけ」と言う遊びの存在を知りましたw
私はこれで3回目の参加ですが、実装系の質問を気軽に聞けるし、その他もいろいろ面白くて勉強になりますので、また参加させていただくつもりです✨
次回まで「A Blind Legend」をクリアしますねw
ノシ