おしおきアプリを作ったら静岡市から賞をもらった話

# はじめに
この記事は、[クソアプリ Advent Calendar ](http://qiita.com/advent-calendar/2016/kusoapp)の22日目の記事です。

ちなみに去年も22日目の担当でした！１年ぶり！！（[ヒャッハーなアプリを作ったら賞をもらった話](http://qiita.com/akitaika_/items/acf06953dd4e4ebb15ea)）

今年は控えめに１つのハッカソンに参加したきりでした。
まぁ、例によってそこでも明るく楽しくクソってきたので、
そのアプリの紹介と中のコードを大雑把に紹介しようと思います。

もちろん、アドベントカレンダーのために。
そして、どういうわけかそれで__静岡市賞をいただいた__ので、ドヤるために。

# やりたいこと
 + 受賞アプリの紹介
 + 中のプログラムを一部解説
 + ドヤる

# アプリ紹介
今回、我ら「おしおき倶楽部」チームがご紹介させていただきますアプリは、
「しずみちinfo クルマと道の未来を描く HACK DAYS 2016」ハッカソンにて開発いたしました…

__おしおき（仮）！！！！！__

<img width="769" alt="スクリーンショット 2016-12-19 7.00.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/90260/debe3058-0c8c-2bd6-ea25-d789f1150f94.png">

今年のテーマが何かは__すっかり忘れてしまいました__が、
「（現世で）召されるなら、（このアプリで）召されないでみせようホトトギス」
という__ノリと勢いと若干のひねくれ感__で作った、
その名の通りの「危険行為をする歩行者を容赦なく”おしおき”するアプリ」でございます。

主な画面は、こちらとなっております。

<img width="242" alt="スクリーンショット 2016-12-22 4.08.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/90260/9991a777-203f-2c71-f14f-7a6feea36768.png">
 
<img width="242" alt="スクリーンショット 2016-12-22 4.09.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/90260/b1f5aa4f-2d48-93bc-9c03-27797b6c6af3.png">

まず、この地図画面の中央でテクテクしているキャラクターは、
__あなた様__（ユーザー）でございます。

![user_1.png](https://qiita-image-store.s3.amazonaws.com/0/90260/f6b8c055-40af-f82f-16d4-2668aaefb1cd.png)

もちろん、あなた様の位置情報を取得して、
__ウンザリさせるほど__あなた様を画面中央に表示させていただくのでございます。ふふふ。

次に、こちらの顔色悪くテクテクしているキャラクターは、__ゾンビ__でございます。

![zombie_1.png](https://qiita-image-store.s3.amazonaws.com/0/90260/9018e546-cf21-9efa-7875-4d7168fb0bc6.png)

__なにか恐ろしい災いの跡が見えますね？__
このゾンビは、「しずみち Info API」からの「災害情報」を元に表示しております。

そして、こちらの道路を徘徊しているのが、__ UFO __となっております。

![car_danger_2.png](https://qiita-image-store.s3.amazonaws.com/0/90260/42080233-a931-a554-651f-4c98502ff586.png)

__うっかり近づいたら、さらわれてしまうかもしれませんね？__
この UFO が徘徊している場所は、
「シズオカ型オープンデータ」からの「ヒヤリハットマップ」を元に表示しております。

そんな感じで、__あらゆるところに危険が潜んでいる__今の世の中なわけでございますが、
このアプリを使って危険地点を認識し、きちんと安全な行動を心がけるようにすれば、
__平穏で、かつ幸せな未来が約束されるのでございます…！__

まず、きちんと安全な行動ができた場合、あなた様には
耳障りが良く素敵で感動的な音楽が聞こえると共に、__徳（しずおかポイント）が進呈されます。__

<img width="243" alt="スクリーンショット 2016-12-22 4.09.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/90260/cb6a4f20-c4a5-0660-edd1-041a707f7663.png">

災い（ゾンビ）からの貴重なデレシーンが見られるのです。
__もう、この時点で少し幸せになれますよね？__
（災害発生地点が移動していいのかなどの細かいことは、目をつぶっていただけると幸いでございます）

この徳でございますが、貯めると
__何かしらのクーポンや何かしらのポイントと引き換えることが可能となっております。__

<img width="244" alt="スクリーンショット 2016-12-22 4.16.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/90260/16c0462a-6c07-1ebd-b466-161243c21d42.png">

おや？何か__不穏な選択肢__も見えますね？

ですが、ご安心ください！
ここで交換できる「ゾンビの餌」とは、我ら「おしおき倶楽部」が抱える
マスコットキャラクター__「オッシーちゃん」__のご飯となっているのです！！

<img width="242" alt="スクリーンショット 2016-12-22 4.10.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/90260/ff113ff8-b200-b29f-6828-11d781bf90fc.png">

__あぁ、日々のストレスが消えていく…。__

とても有用な徳の使い方でございますね。

さて、反対にこの危険地点をスルーするとどうなってしまうのでしょうか？

そう、待っているのは__おしおき__でございます。

<img width="244" alt="スクリーンショット 2016-12-22 4.10.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/90260/7912c9da-bb50-2b62-ed1a-8a062a3ddd8c.png">

画像上でお伝えするのが難しいところもあるのですが、
実は２つのおしおきが実行されているのでございます。

まず、テキストとして表示されております通り、
あなた様の__ご尊父様やご尊母様に強制的にご連絡させていただきます。__

<img width="242" alt="スクリーンショット 2016-12-22 4.24.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/90260/d1a69f81-0fd1-d1ce-ab9f-5d04b43d4b8e.png">

もちろん、年齢など関係ございません。
いい歳した大人だったとしても、
容赦なく__自身の両親に叱られる__という辱めを受けるのです。
あぁ、みっともない…。

そして、もう一つが、__とてつもなく恥ずかしい音楽が大音量で流れる__というものでございます。

発表時にデモで流した音楽は何て言いましたっけ？
…あぁ、そう「に○にぱ音頭」でしたね。
正直、発表していた私めも__身の置き場所に困ってしまいました__が、
そういった所謂「イタイ系の音楽」というものが流れるのでございます。

通勤・通学で多くの人が歩く中で__突如として大音量で鳴り響く、イタイ音楽。__
あぁ、__なんて恥ずかしい…！__

そういった事態にならないためにも、オッシーちゃんを愛でるためにも！
交通ルールを守り、平穏で幸せな未来を「おしおき倶楽部」と共に迎えようじゃありませんか！！

　

...というアプリだったとさ。ﾔﾚﾔﾚ ┐(´ｰ｀)┌ ﾏｲｯﾀﾈ
# プログラム解説
ちょっとうっかり__アプリ紹介に熱が入ってしまいました__が、
アプリの中身事態はビックリするほど大したことないです。

例によって、一部だけ解説しちゃいます。
## SupportMapFragment のレイアウトをカスタムする
GoogleMap の上にボタンを重ねるとかしようと思った場合、普通は
「SupportMapFragment とそのボタンのレイアウトを持ったフラグメントを重ねる」
なーんてことをすることが多いと思うんですけど、
正直、画面としては１つのものなので、__１つのフラグメントにまとめたい__とか思いませんか？

個人的な思想としては、別れてていいよねとは思うんですけど、
せっかくなんで試験的に１つにまとめる試みをしてみました。

その__血と汗と涙と途中で意識なくなったときに垂らしたよだれの結晶__が、以下になります。

```java

public abstract class SimpleMapFragment extends Fragment
        implements OnMapReadyCallback, GoogleMapUtil.UseGoogleMapImpl {

    private boolean mStop;
    private GoogleMap mMap;
    private boolean mFirst;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mFirst = true;
    }

    @Override
    public void onStart() {
        super.onStart();

        mStop = false;

        final Fragment mapFragment = getChildFragmentManager().findFragmentById(R.id.fragment_map);

        if (mapFragment instanceof SupportMapFragment) {
            SupportMapFragment supportMapFragment = (SupportMapFragment) mapFragment;
            supportMapFragment.getMapAsync(this);
        }
    }

    @Override
    public void onStop() {
        mStop = true;

        final Fragment mapFragment = getChildFragmentManager().findFragmentById(R.id.fragment_map);

        if (mapFragment instanceof SupportMapFragment) {
            onMapDestroy();

            final SupportMapFragment supportMapFragment = (SupportMapFragment) mapFragment;
            supportMapFragment.getMapAsync(new OnMapReadyCallback() {
                @Override
                public void onMapReady(GoogleMap googleMap) {
                    // null を設定してると SupportMapFragment の方で
                    // ぬるぽになるので、空のコールバックを設定しておく
                }
            });
            mMap = null;
        }

        super.onStop();
    }

    @Override
    public final void onMapReady(GoogleMap googleMap) {
        if (mStop) {
            return;
        }

        mMap = googleMap;

        if (mFirst) {
            onMapReadyFirst();
            mFirst = false;
        }

        onMapReady();
    }

    @Override
    public GoogleMap getMap() {
        return mMap;
    }

    /**
     * onMapReady のタイミングで呼ばれる（初回のみ）
     */
    protected void onMapReadyFirst() {

    }

    /**
     * onMapReady のタイミングで呼ばれる
     */
    protected void onMapReady() {

    }

    /**
     * GoogleMap のインスタンスを破棄するタイミングで呼ばれる
     */
    protected void onMapDestroy() {

    }

}

```

play-services-maps のバージョンは 9.8.0 を想定しています。

SimpleMapFragment の使い方は、こんな感じです。

 1. res/values 配下に「ids.xml」を作成し、`<item name="fragment_map" type="id"/>`を定義
 1. SimpleMapFragment を継承したフラグメントを作成
 1. 子フラグメントのレイアウトをこんな感じ（※）で定義
 1. 定義したレイアウトをいつも通り onCreateView でインフレート
 1. あとは、マップのオブジェクト取得していじり倒したり、ビューに対してロジック書いたり

※こんな感じ

```xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@id/fragment_map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        class="com.google.android.gms.maps.SupportMapFragment"/>

    <Button
        android:id="@+id/button_pocket_money"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_margin="10dp"
        android:text="@string/label_button_pocket_money"
        android:background="@drawable/selector_shape_square_corner"/>

    <Button
        android:id="@+id/button_pet"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_margin="10dp"
        android:text="@string/label_button_pet"
        android:background="@drawable/selector_shape_square_corner"/>

    <Button
        android:id="@+id/button_point"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_margin="10dp"
        android:text="@string/label_button_point"
        android:background="@drawable/selector_shape_square_corner"/>

</RelativeLayout>

```

こうすると、重ねたビューもマップ周りの処理も子フラグメントの方にまとめられました！
これはこれで使い勝手よかったので、もうちょっといろいろ改良していきたいなぁ…。
## テクテクするマーカー
これ、わりとパワープレイです。(・ω<)
Handler の postDelayed メソッドを駆使して、アイコンを切り替えてました。

```java

/**
 * ユーザーアイコン１に切り替える
 */
private void changeUser1() {
    changeIcon(mUserMarker, R.drawable.user_1,
            new Runnable() {
                @Override
                public void run() {
                    changeUser2();
                }
            }, 1000);
}

/**
 * ユーザーアイコン２に切り替える
 */
private void changeUser2() {
    changeIcon(mUserMarker, R.drawable.user_2,
            new Runnable() {
                @Override
                public void run() {
                    changeUser1();
                }
            }, 1000);
}


/**
 * アイコンの切り替え
 * 
 * @param marker 対象のマーカー
 * @param resourceId 切り替える画像の ID
 * @param nextRunnable 次に実行する処理
 * @param delayMillis ディレイ時間（ミリ秒）
 */
private void changeIcon(final Marker marker, final int resourceId, final Runnable nextRunnable,
                        final long delayMillis) {
    if (isRemoving() || isDetached()) {
        return;
    }

    if (marker == null) {
        return;
    }

    marker.setIcon(BitmapDescriptorFactory.fromResource(resourceId));

    new Handler().postDelayed(nextRunnable, delayMillis);
}

```

フラグメントに実装していたので、
念のため、デタッチ後に処理しないようにしたことが気をつけたことですかね？一応。

　

あとの位置情報の衝突判定とかは、去年の「しずみちひゃはーMAPⅡ」と同じだし、
このアプリとしてのオリジナルはこれくらいかも…？

あれ、思ったより解説できること少ない（；・∀・）
# おしおき倶楽部のチーム構成
そんな感じで、楽しくハックしてサクッと賞をいただいてきたわけですが、
今回、チーム構成がわりと特殊（？）な感じだったので、ちょっとそれも紹介しておきます。

おしおき倶楽部チームは、総勢７名のチームだったわけですが、
そのうち、なんと__４名が学生（しかも、うち３名が高校生！）__のチームでした。

原案からしてすでに「おしおきアプリ」だったのに、よく一緒にやろうと思ったな…
とか密かに思ったりしたのですが、それは置いといて。

そんな学生陣はコーディング経験がまったくない！

__まったくない！__

状態だったわけです。

とはいえ、「じゃあ、ちょっと寝てて！」とは言えないし言いたくもなかったので、
__今できないなら、今始めればいいじゃない！今でしょ！！__
ということで、実際にコーディングも含めて以下のように仕事を振ってみたのです。

 + 女子高生A　 → Android アプリのコーディング（オッシーくんを愛でる画面の作成）
 + 女子高生B　 → 画面遷移・画面デザイン・オッシーくんデザインを考える
 + 男子高生C　 → 徳の交換画面（Web、サーバー側でのポイント管理）
 + 男子大学生D → 発表資料作成、イタイ音楽探し

私は基本的にAちゃんさんにコーディング教えながら、
Bちゃんさんと画面について話し合ってたのですが…。
もうね、__学生ズの活躍がハンパなかった。__

まず、Aちゃんさんには軽く Android について説明した後に
「ボタンを押したら、それに対応したオッシーくんの画像に切り替わる」画面を作ってとお願いしてたんですよ。

そしたら！そしたらですよ！！

なんと、自分でいろいろ調べて画像を切り替えるだけじゃなく、
__オッシーくんにアニメーションをつけてくれた__んですよ！！！
初心者っすよ？今日その時はじめて Android のコーディングしたんですよ？？

__パネェ…。__

いや、ホント大人でもアニメーション嫌いって言ってる人そこそこいるのに、
チャレンジした上にちゃんとハッキングタイム中に完成させる、その飲み込みの良さがヤバかったです。

Bちゃんなんかは、このアプリの要件伝えて
「どういう画面が必要か、どういうパーツが必要かを考えながら画面遷移図を作る」
ことをお願いしたんですけど、__それがこれですよ。__

<img width="302" alt="スクリーンショット 2016-12-22 4.26.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/90260/d1c83ceb-308b-eb2a-2f53-940e86568df0.png">

__マジパネェ…。__

しかも、必要なパーツについては__既存のアプリを参考に__考えたというから、
もうホント__感動で震える__レベルでした。

衝撃すぎて、ちょっと__ガチトーンでウチに入社しないか打診した。__
残念ながら、流されたけど。w

Cくんもサーバー側のエンジニアの方に教えてもらって、自分の担当をやりきっていたし、
Dくんもやたらめったら気合の入ったパワポ作ってくれたし、
ものすごくメンバーに恵まれたチームでした！

ちなみに、おしおき内容は高校生ズがされたら嫌なことを挙げてもらいました。
親へのメールもイタイ音楽再生も、案が出た時点で本当に嫌そうだったので、とても楽しかったです。w
# まとめ
 + 今年も受賞したよ！本当に本当にありがとうございます！
 + 学生ズ、マジパネェ！
 + 雑な敬語を考えるのにやたらめったら時間がかかったよ！

# 参考
 + しずみちinfo クルマと道の未来を描く HACK DAYS 2016 公式ページ  
 http://shizuoka-hackers.space/