夜も安心、目に優しい UI を作ろう

# TL;DR

夜などの暗闇でスマートフォンを使うときには、目に優しい色使いをしましょう

# これ is 何

夜な夜なスマートフォンの画面を注視していると、画面の明るさに目が馴染んでしまって、くらいところで周りが見えなくなった体験は無いでしょうか。あるいは、夜車を運転している時にスマートフォンの画面をつけていると、その光がチラチラと視界に入ってきて鬱陶しく感じることはないでしょうか。

この記事では、画面の輝度を変更する方法ではない、暗所でもユーザの目に負担とならないような UI の作り方を見ていきます。

# 目に優しい色

## 色の属性

色にも明るさがあります。

[HSVモデル](https://ja.wikipedia.org/wiki/HSV%E8%89%B2%E7%A9%BA%E9%96%93)やHSL色空間、HSB色空間といったモデルで表される色のプロパティには、`V(Value)`や`L(Lightness)`、`B(Brightness)`といった、明度や輝度と呼ばれる明るさを示すスケールがあります。この値が高いほど色は明るく見え、低くなるほど色は暗く見えるようになります。明るい色ほど光は強く、暗い色ほど光は弱くなります。明度は光の波形の最大振幅に類似する、とありますが、光の強さは光の振幅の2乗に比例するそうですから、明度が高い色の光は強くなるようです。

## 目についてざっくりと知る

目は[光の強さに反応する神経](https://ja.wikipedia.org/wiki/%E6%A1%BF%E4%BD%93%E7%B4%B0%E8%83%9E)と[色に反応する神経](https://ja.wikipedia.org/wiki/%E9%8C%90%E4%BD%93%E7%B4%B0%E8%83%9E)で構成され、光の強さに反応する神経は僅かな光でも敏感に反応します。長時間強い光を受けるとそれに順応しますが、その後光が弱い場所に行くと、はっきりとものを捉えるまでに時間を要します(暗順応)。

暗所でスマホやPCのディスプレイを見ている時も、光が集中して目に入ってくるため、一旦暗い場所に目を向けると順応するまでに時間がかかります。

## 暗い場所でも目に優しくするには

目の特徴と色の属性から、明度を下げれば目に優しい(暗順応の時間を低減する)色になりそうですね。

# 周囲の明るさを計測・計算して、UI を切り替える

幾つかの方法がありますが、おおまかにセンサーによる計測と、時間による推測の 2 通りがあります。

## 照度センサーを使った計測

照度センサーで周辺光の照度(Lux)を計測します。その数値を元に、特定の値を下回った場合には夜用の色やスタイル、ないしはテーマを適用することで、目に優しい UI が作れます。

### Android の照度センサー

Android では`SensorManager`を用います。

```java

public class LuxSensorListener implements SensorEventListener {
  private final SensorManager mSensorManager;
  private float mCurrentValue;

  public LuxSensor(Context context) {
    mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
  }

  public void start() {
    List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_LIGHT); // 照度センサーのリスト
    if (seonsors.isEmpty())
      return;
    Sensor sensor = sensors.get(0);
    mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
  }

  public void stop() {
    mSensorManager.unregisterListener(this);
  }

  @Override
  public void onSensorChanged(SensorEvent event) {
    mCurrentValues = event.values[0]; // 照度センサーの値は1つ
  }

  @Override
  public void onAccuracyChanged(Sensor sensor, int accuracy) {

  }
}

```

取得できる値がどのくらいのものなのかを比較する場合は、SensorManager に定義された定数と比較します。

|定数名|値|
|---|---|
|LIGHT_CLOUDY|100.0|
|LIGHT_FULLMOON|0.25|
|LIGHT_NO_MOON|0.01|
|LIGHT_OVERCAST|10000.0|
|LIGHT_SHADE|20000.0|
|LIGHT_SUNLIGHT|110000.0|
|LIGHT_SUNLIGHT_MAX|120000.0|
|LIGHT_SUNRISE|400.0|

### iOS の輝度センサー

[koogawaさんのブログ](http://blog.koogawa.com/entry/2013/11/17/111304)に記事がありました。Android と異なり照度を測るセンサーは付いていないようですが、代わりに画面輝度を使っているようです。

## 日の出・日の入りの計算

こちらは日の出・日の入りの時間を計算で求めることによって、明るい時間帯か暗い時間帯かを判別することが出来ます。日の出に満たない、あるいは、日の入りを過ぎた時間の場合は、夜用の色やスタイル、ないしはテーマを適用するロジックを書いていく事になります。

### 計算式

[Wikipediaに詳細な記事があります](https://en.wikipedia.org/wiki/Sunrise_equation)。

基本的に変数となるのは、緯度経度と日付です。緯度経度は位置情報から簡単に取得でき、日付(タイムゾーン)は端末に設定されたものが簡単に使えます。これらに様々な定数を掛けあわせたり、三角関数を用いることで、指定した日付の日の出と日の入りの時間が算出できます。

このため、Android と iOS で同じようにロジックを作ることができます。

- See also
  - [TwilightCalculator](https://github.com/android/platform_frameworks_base/blob/master/services/core/java/com/android/server/TwilightCalculator.java)
  - [TwilightManager](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v7/appcompat/src/android/support/v7/app/TwilightManager.java)
  - [erndev/EDSunriseSet](https://github.com/erndev/EDSunriseSet)

### 計算の実行

計算自体は頻繁にする必要はなく、一日一回で十分です。そして、画面の色を決定するために必要なのは、その瞬間の時刻と日の出日の入り時刻との比較だけです。

つまり、位置情報を使うとしても一日一回で済みます(タイムゾーンをまたぐ移動があった時は都度計算が必要ですが)。センサーのように頻度の高い計測が必要なわけではないですから、電池にも優しくなります。ただし、位置情報の制度によっては日の出日の入りの時刻の計算が多少前後します。

## UiModeManager(Androidのみ)

Android には、UiModeManager という System Service が存在します。このクラスは、位置情報や照度を元に、夜モードと昼(通常)モードを切り替える役割を持っています。ただし、明示的に車に乗っていることを UiModeManager に伝えない限りこの切替えは働きません。

このクラスを利用する場合、夜モードと通常モードとで使用するリソースを Qualifier によって分離することが出来ます。

照度センサーによるものや日の出日の入りを元に計算するものでは、リソースの Qualifier が使えないのがデメリットとなるでしょう(代わりに夜用テーマ・スタイルを作る)。

# まとめ

OS による画面輝度の自動調整も勿論有用ですが、自分たちが定義している色にも目を向け、夜も安心して使えるアプリを作っていけるとよいですね。

# リファレンス

## potatotips の資料

- [UI optimization for night](https://speakerdeck.com/keithyokoma/ui-optimization-for-night)

## 数学的なもの

- [Sunrise Equation](https://en.wikipedia.org/wiki/Sunrise_equation)

## Android

- [TwilightCalculator](https://github.com/android/platform_frameworks_base/blob/master/services/core/java/com/android/server/TwilightCalculator.java)
- [TwilightManager](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v7/appcompat/src/android/support/v7/app/TwilightManager.java)

## iOS

- [erndev/EDSunriseSet](https://github.com/erndev/EDSunriseSet)