連続N回タップを判定する

tap photo credit: aguscr via photopin cc

Android Lollipopが発表されて、恒例のイースターエッグが話題になりましたね!!

このイースターエッグは以下の方法で表示します。

  • 設定 -> 端末情報 -> Androidバージョンを連続3回タップ

(ちなみに、KitKatまでは連続5回タップでイースターエッグが表示されます)

今回はこの連続タップ判定がどのように実装されているのか調べて、HitDetectorクラスとしてまとめてみました。

設定->端末情報画面のソースコードは/com/android/settings/DeviceInfoSettings.java(GitHub)に定義されており、その中に連続N回タップ判定の処理が含まれています。

具体的には以下のコードです。

・・・省略・・・
    long[] mHits = new long[3];

    @Override
    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
        if (preference.getKey().equals(KEY_FIRMWARE_VERSION)) {
            System.arraycopy(mHits, 1, mHits, 0, mHits.length-1);
            mHits[mHits.length-1] = SystemClock.uptimeMillis();
            if (mHits[0] >= (SystemClock.uptimeMillis()-500)) {
                Intent intent = new Intent(Intent.ACTION_MAIN);
                intent.setClassName("android",
                        com.android.internal.app.PlatLogoActivity.class.getName());
                try {
                    startActivity(intent);
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Unable to start activity " + intent.toString());
                }
            }
        }
        ・・・省略・・・
    }

ポイントは以下です。

  • タップされる度にSystem#arraycopy()を使って配列の中身を前方に一つ分ずらす([1]を[0]に、[2]を[1]に格納)。
  • 空いたmHits[]の末尾([2])にタップされた時刻を格納する。
  • mHits[]の先頭と末尾の時刻を比較して一定の時間以内(本実装だと500ms)かどうかを判定する。

複雑な処理ではないですが、System#arraycopy()を使うことで非常に短く実装されていて良いですよね!!

上記を参考にHitDetectorクラスとして書き出してみました。
https://gist.github.com/smile0520/ff05f5ecd019176b86ce
コンストラクタでタップ回数と閾値時間を設定することが出来ます。

import android.os.SystemClock;
 
import java.util.Arrays;
 
/**
 * 連続N回タップを判定するクラス
 */
public class HitDetector {
 
    private final long[] mHits;
    private final long mThresholdTimeMillis;
 
    /**
     * コンストラクタ
     *
     * @param hitCount            ヒット判定回数
     * @param thresholdTimeMillis hit閾値時間(ms)
     */
    public HitDetector(int hitCount, long thresholdTimeMillis) {
        if (hitCount <= 0 || thresholdTimeMillis <= 0) {
            throw new IllegalArgumentException(
                    "Arguments must be hitCount > 0 and thresholdTimeMillis > 0.");
        }
        mHits = new long[hitCount];
        mThresholdTimeMillis = thresholdTimeMillis;
    }
 
 
    /**
     * 連続ヒット判定を行います
     *
     * @return 連続ヒットされた場合true,それ以外false
     */
    public boolean hit() {
        System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1);
        long uptimeMillis = SystemClock.uptimeMillis();
        mHits[mHits.length - 1] = uptimeMillis;
        boolean detect = mHits[0] >= (uptimeMillis - mThresholdTimeMillis);
 
        // 連続ヒットした場合は今までのヒットをクリアする
        if(detect){
            Arrays.fill(mHits, 0);
        }
        return detect;
    }
}

Android本家の実装を追ってみると、普段自分では思いつかない実装に出会えていいですね!!

以上です。

  1. トラックバックはまだありません。

コメントを残す