日本固有の悩みだろうか

Android で Google Map を配置したアプリを作成する場合は Google Maps Android API を使用することになると思うが、現状 Google Map 内に表示される文字サイズを変更するような API は用意されていないし Android 端末の設定の文字サイズを変更してもアプリ内の文字サイズは変わる (但しアプリ側が行儀よく sp 単位でフォントサイズ指定されている場合) のだが、Google Map 内の文字サイズは残念ながら連動しない。

老眼、つまり高齢化は先進国における世界的な流れだと思うのだが、何故 Google Maps Android API にそのような考慮がなされていないのか。 恐らく欧米はラテン文字を使用しているためフォントサイズが比較的小さくても視認性が高くそういった問題は余り出てこないのではないだろうか。 その点漢字は不利である。

高齢者向けのアプリを開発する場合どうしても「文字サイズを大きくする」というところを考慮しなければならない。 API が用意されていないので、逆に地図自体を大きくするのはどうかと考えた。 少しボヤケた感じにはなるが、意外と悪くないアプローチなのでここに書き残しておく。

まず地図自体を大きくする

SupportMapFragment のインスタンスを取得しそれに紐づく View に対し setScaleXsetScaleY を呼べば簡単に変更できる。 例えば 2 倍にする場合は以下でよい:

mMapFragment.view?.scaleX = 2.0f
mMapFragment.view?.scaleY = 2.0f

だがこれを愚直に行うと以下の問題が発生する:

  • Google ロゴや現在位置ボタン、ズームイン・アウトボタンが画面の領域外に見切れてしまう
  • ボタン類や配置したオブジェクト (ピン、吹き出しなど) も 2 倍になってしまう

ロゴやボタンが見切れないようにする

幸い GoogleMap#setPadding というメソッドで Google Map に対しパディングを設定できる。 パディングは端末の縦横ピクセルを取得し適切な値を設定しなければならない。 Android アプリ開発ではよくあるパターンではあるが View のサイズ取得はビューツリー構築後でないと計算されておらず 0 になってしまうところに注意する。 このように ViewTreeObserver#addOnGlobalLayoutListener を使うのがイディオムだ:

mMapFragment.view?.viewTreeObserver?.addOnGlobalLayoutListener {
    view?.let { view ->
        mMapFragment.getMapAsync { map ->
            // 2 倍だけでなく 1.5 倍などもサポートする場合もう少し工夫する必要あり
            val (width, height) = view.width to view.height
            val paddingLeft = (width / 4).toInt()
            val paddingTop = (height / 4).toInt()
            map.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
        }
    }
}

2 倍になってしまうボタンを使わずに自作する

2 倍になってしまう Google ロゴは仕方ないとして、ボタン類が 2 倍になっているのはどうにも不格好だ。 まずデフォルトのボタン類を以下で非表示にする:

// map は GoogleMap インスタンス (getMapAsync() での取得後)
map.uiSettings.isZoomControlsEnabled = false
map.uiSettings.isMyLocationButtonEnabled = false

続いて自作のアイコンを配置するわけだが、アイコン画像は Google Material Icons に自由に使えるアイコンが配布されているので、これを引っ張ってくれば良い。 そして GoogleMap#getMapAsync()GoogleMap インスタンスを取得できたタイミングで以下のような感じでイベントをセットすればよい:

myLocation.setOnClickListener {
    // TODO 本当は myLocation は deprecated なので FusedLocationApi を使用したものに置き換えるのがよい
    val latLng = LatLng(map.myLocation.latitude, map.myLocation.longitude)
    map.animateCamera(CameraUpdateFactory.newLatLng(latLng))
}
zoomIn.setOnClickListener { map.animateCamera(CameraUpdateFactory.zoomIn()) }
zoomOut.setOnClickListener { map.animateCamera(CameraUpdateFactory.zoomOut()) }