[Android] Google I/Oアプリの実装方針を参考にして爆速開発したい

結構命名など様々な種類があり方針を決めるのは悩みますし時間がかかります。
考えることも重要ですが、標準的なものを真似しておくと素早く開発していくことができます。
出来れば既存のプロジェクトの方針をつまみ食いして開発したいです。
また、自分は毎回Google I/Oアプリはどうやっていたっけ？って確認しにいってしまっていて面倒でした。
なので、Google I/Oアプリがどんな感じでやっているのかをまとめてみました。

# プロジェクト基本構成
minSdkVersion 14
targetSdkVersion 22

Android 4.0から対応で targetSdkは5.1と普通な感じですね

targetSdkが23未満なのでランタイムパーミッションは対応してないようです。


# 大きな方針
* AppCompatActivityを継承したActivityを使う  
    * AppCompatActivityを利用することで、android:theme属性を利用することができるようになったり、自動的にtint(色をつけること)に対応したAppCompat系のViewが利用できるようになります。
* それぞれAndroid ManifestでAppCompat系のテーマを継承したテーマをつける
    * AppCompatActivityを利用するのでAppCompat系のテーマを付ける必要があります。これによりprimaryColorなどのテーマの色が全体的に使われるようになります。
* デフォルトのActionBarは使わない
    * サポートライブラリのToolBarを代わりに利用します。(拡張性が高いためだと思われます。)
* フラグメントは使う

基本的にデフォルトのAndroid Studioで作られるプロジェクトと一緒ですね。

# パッケージのわけ方
機能ごとにパッケージを分けてその中にActivityやFragmentがごちゃってある感じです。
具体的にはsessionやwelcomeなどといったパッケージがたり、その中にActivityなどがある感じです。
開発するときは関連するクラスが近くにあるので、この分け方のほうが楽そう。
https://github.com/google/iosched/tree/master/android/src/main/java/com/google/samples/apps/iosched


# レイアウトの方針

## レイアウトのファイル名
参考
https://github.com/google/iosched/tree/master/android/src/main/res/layout

・アンダーバー区切り
activity_welcome.xmlなど

・アクティビティの場合はactivity_から始める
例: activity_welcome.xml ActivityはWelcomeActivityになっている。

・フラグメントの中のレイアウトはfragment_から始める
例: fragment_my_schedule FragmentはMyScheduleFragmentになっている。


ただ古いソースコードはsession_detail_act.xmlがSessionDetailActivityになっていたりするので、完全にルールを守りきれているわけではなさそう

## レイアウトのid
アンダーバー区切りで記述する
結構自由につけている
レイアウトの中のコンテンツっぽいレイアウトにmain_contentってつけるのが多い気がする。

## Ripple対応
基本的にはbackgroundなどに
?android:selectableItemBackgroundをつけるだけ

[ForegroundLinearLayout](https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/widget/ForegroundLinearLayout.java#L34)を使ってLinearLayoutにもRippleをつけている(リストのアイテムにつけているみたい)

```xml:explore_io_keynote_stream_item.xml
<com.google.samples.apps.iosched.ui.widget.ForegroundLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/explore_io_clickable_item"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true"
    android:focusable="true"
    android:onClick="sessionDetailItemClicked"
    android:orientation="vertical"
    android:foreground="?android:selectableItemBackground">
...
```

##レイアウトのスペース
https://github.com/google/iosched/blob/master/android/src/main/res/values/dimens.xml
端末の横幅によってAlternative resourceの仕組みを使い変えたりしているようです。


よく使うスペース

```xml:values/dimens.xml
    <!-- standard metrics -->
    <dimen name="spacing_normal">8dp</dimen>
    <dimen name="spacing_micro">4dp</dimen>
    <dimen name="padding_normal">16dp</dimen>
```

```xml:values-sw600dp/dimens.xml
    <!-- standard metrics -->
    <dimen name="spacing_normal">12dp</dimen>
    <dimen name="spacing_micro">8dp</dimen>
    <dimen name="padding_normal">24dp</dimen>
```

keyline

```xml:values/dimens.xml
    <dimen name="keyline_1">16dp</dimen>
    <dimen name="keyline_1_minus_8dp">8dp</dimen>
    <dimen name="keyline_2">72dp</dimen>
    <dimen name="keyline_2_minus_16dp">56dp</dimen>
    <dimen name="keyline_2_session_detail">@dimen/keyline_2</dimen>
```

```xml:values-sw600dp/dimens.xml
    <dimen name="keyline_1">24dp</dimen>
    <dimen name="keyline_1_minus_8dp">16dp</dimen>
    <dimen name="keyline_2">80dp</dimen>
    <dimen name="keyline_2_minus_16dp">64dp</dimen>
    <dimen name="keyline_2_session_detail">104dp</dimen>
```

keylineはよく分かっていないのですが、標準となる横からのマージンみたいです
http://wazanova.jp/items/1462

## レイアウトでNavigationDrawerだけステータスバーに透過で表示する
https://github.com/google/iosched/blob/master/android/src/main/res/layout/explore_io_act.xml

レイアウトにandroid:fitsSystemWindows="true"をつけると、ステータスバー領域の部分の中に普通に表示されるので、ルートのレイアウトとNavigationDrawerにつけてあげて、透過の表示をしているようです。

![image](https://qiita-image-store.s3.amazonaws.com/0/27388/8ae58cc9-aff9-ba1d-3bbd-68a463b679cb.png)

http://www.google.com/design/spec/patterns/navigation-drawer.html より

```xml:explore_io_act.xml
<!-- ExploreIOActivity layout seen when users enter the app after the WelcomeActivity. -->
<android.support.v4.widget.DrawerLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".explore.ExploreIOActivity"
        android:fitsSystemWindows="true">

    <!-- Main layout fitsystemWindowsをつけない -->
    <com.google.samples.apps.iosched.ui.widget.DrawShadowFrameLayout />
    <!--省略-->
    <!--  fitsystemWindowsをつける -->
    <com.google.samples.apps.iosched.ui.widget.ScrimInsetsScrollView
        android:fitsSystemWindows="true"
        app:appInsetForeground="#4000">
    <!--省略-->
    </com.google.samples.apps.iosched.ui.widget.ScrimInsetsScrollView>
</android.support.v4.widget.DrawerLayout>
```


# テキスト 
## フォント
ButtonやTextViewに以下をつける。

```xml:layout.xml
            android:textStyle="@integer/font_textStyle_medium"
            android:fontFamily="@string/font_fontFamily_medium"
```

Lollipop未満はsans-serifのboldで
以上であればsans-serif-mediumのnormalにするみたい
sans-serif-mediumが使えるのが21からだからかも？

```xml:values/fonts.xml
<resources>
    <integer name="font_textStyle_medium">1</integer> <!-- bold -->
    <string name="font_fontFamily_medium">sans-serif</string>
</resources>
```

```xml:values-v21/fonts.xml

<resources>
    <integer name="font_textStyle_medium">0</integer> <!-- normal -->
    <string name="font_fontFamily_medium" translatable="false">sans-serif-medium</string>
</resources>
```

## テキストサイズ
text_size_mediumをよく使うみたい？
TextViewなどに
android:textSize="@dimen/app_text_size_medium"を指定する

```xml:dimens.xml
    <!-- App Primary Text Sizes -->
    <dimen name="app_text_size_xsmall">11sp</dimen>
    <dimen name="app_text_size_small">12sp</dimen>
    <dimen name="app_text_size_medium">14sp</dimen>
    <dimen name="app_text_size_large">18sp</dimen>
    <dimen name="app_text_size_xlarge">20sp</dimen>
    <dimen name="app_text_size_diff_large_small">6sp</dimen>
```

# 色
以下のように定義されています。 
基本的にテーマで利用される色が定義されている感じです。
注目はtheme_accent_2があることでしょうか。どこかのマテリアルデザイン動画などでも２つあると言っていたと思います。

![image](https://qiita-image-store.s3.amazonaws.com/0/27388/983c9ecd-65c5-0978-6a66-67784027f807.png)


```xml
    <color name="theme_primary">@color/app_primary_accent</color>
    <color name="theme_primary_dark">#00BCD4</color>
    <color name="theme_primary_light">#7986cb</color>

    <color name="theme_accent_1">#00b0ff</color>
    <color name="theme_accent_1_light">#8ad4fa</color>

    <color name="theme_accent_2">#e91e63</color>
```

これらの色はテーマから参照しており、基本的にレイアウトからは以下のように参照してテーマの色を使います。

```xml
        android:background="?colorPrimary"
```

他にも他の場所で必要になった色なども定義されています。
https://github.com/google/iosched/blob/master/android/src/main/res/values/colors.xml


# 画像
ファイル名は_区切り
drawableフォルダを利用する
アプリアイコンのみmipmapディレクトリを使う

## アイコン
ic_から始める
xxhdpiのpng画像とvectorリソースを用意していたりしていなかったり、、
https://github.com/google/iosched/blob/master/android/src/main/res/drawable-v21/ic_submit_feedback.xml#L1
https://github.com/google/iosched/blob/master/android/src/main/res/drawable-xxhdpi/ic_submit_feedback.png

# Theme

[テーマは継承できる](http://qiita.com/takahirom/items/97f1bde4e89a6fe8ec7f)ので、以下の様な構造を作っています。
一番親としてTheme.AppCompat.Light.NoationBarを継承しています。これは白色をベースとしたアクションバーを表示しないAppCompatのテーマです。IOアプリは画面ごとにテーマを作っていますが、共通部分は同じテーマを継承することで共通化を図っています。

![iosched.png](https://qiita-image-store.s3.amazonaws.com/0/27388/1bf02281-4512-fe96-0287-39e98e64c427.png)

おそらく、一番重要なのはどんな場合も使われるTheme.IOSched.Baseでしょう。
どの要素がどこに反映されるかを理解しつつ反映していく必要がありますが、ベースとなるcolorPrimaryやcolorPrimaryDark、colorAccentは設定しておきましょう。

```xml:values/styles.xml
    <style name="Theme.IOSched.Base" parent="Theme">
        <item name="actionBarIconColor">#fff</item>
        <item name="actionBarInsetStart">@dimen/keyline_2</item>
        <item name="homeAsUpIndicator">@drawable/ic_up</item>
        <item name="spinnerBarInsetStart">@dimen/keyline_2_minus_16dp</item>
        <item name="popupItemBackground">?android:selectableItemBackground</item>
        <item name="photoItemForeground">?android:selectableItemBackground</item>
        <item name="photoItemForegroundBorderless">?android:selectableItemBackground</item>

        <item name="colorPrimary">@color/theme_primary</item>
        <item name="colorPrimaryDark">@color/theme_primary_dark</item>
        <item name="colorAccent">@color/theme_primary</item>

        <item name="android:textColorLink">@color/flat_button_text</item>

        <item name="windowActionBar">false</item>
        <item name="android:windowNoTitle">true</item>

        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowBackground">@android:color/white</item>
        <item name="android:homeAsUpIndicator">@drawable/ic_up</item>

        <item name="android:popupMenuStyle">@style/Widget.IOSched.PopupMenu</item>
        <item name="android:listPopupWindowStyle">@style/Widget.IOSched.PopupMenu</item>
        <item name="android:dropDownListViewStyle">@style/Widget.IOSched.ListView.DropDown</item>
        <item name="android:textAppearanceLargePopupMenu">@style/TextAppearance.LargePopupMenu</item>

        <item name="imageItemBackground">?android:selectableItemBackground</item>
        <item name="android:borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
    </style>
```

https://github.com/google/iosched/blob/master/android/src/main/res/values/styles.xml

また独自にstyleの要素を定義していて、それを使っているようです。
例えば[actionBarInsetStartはToolbarのコンテンツの横からの位置を設定](https://github.com/google/iosched/blob/master/android/src/main/res/layout/toolbar_actionbar_with_headerbar.xml#L24)しています。
これは画面によってこの部分を変えたいということがあり、画面によってその部分をテーマで変更して利用しているようです。(スタイルでもオーバーライドっていうのかな？)

https://github.com/google/iosched/blob/master/android/src/main/res/values/attrs.xml

```xml:attrs.xml
    <declare-styleable name="BaseTheme">
        <attr name="actionBarIconColor" format="color" />
        <attr name="actionBarInsetStart" format="dimension" />
        <attr name="spinnerBarInsetStart" format="dimension" />
        <attr name="popupItemBackground" format="reference" />
        <attr name="photoItemForeground" format="reference" />
        <attr name="photoItemForegroundBorderless" format="reference" />
        <attr name="imageItemBackground" format="reference"/>
    </declare-styleable>
```

セッション画面のTheme

```xml:values/styles.xml
    <style name="Theme.IOSched.Sessions" parent="Theme.IOSched.WithNavDrawer">
        <item name="actionBarInsetStart">@dimen/keyline_2_minus_16dp</item>
        <item name="spinnerBarInsetStart">@dimen/keyline_2_minus_16dp</item>
        <item name="android:windowBackground">@color/grey_background</item>
    </style>
```


# Preference管理
何も考えていない方式みたい
https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/settings/SettingsUtils.java

# アニメーション
5系未満が対応していないアニメーションは5系以上だけアニメーションを行ったりが多いです。
ここは検索のところを開いた時に円で広がるアニメーションのところだと思います。
参考になりそう
https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/SearchActivity.java#L203


# NavigationDrawer関連の処理をBaseActivityにして継承させる
人によって是非が分かれそうですが、、BaseActivityに書いて共通化させてるみたいです。
https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/BaseActivity.java

# Fragment生成時にはFrament#newInstanceメソッドを用意してそれを使う
```java:WiFiUtils.java
        public static WiFiDialog newInstance(boolean wiFiEnabled) {
            WiFiDialog wiFiDialogFragment = new WiFiDialog();

            Bundle args = new Bundle();
            args.putBoolean(ARG_WIFI_ENABLED, wiFiEnabled);
            wiFiDialogFragment.setArguments(args);

            return wiFiDialogFragment;
        }
```

これですね
ActivityのExtraやFragmentのArgumentを設定する責務は、呼び出される側に持たせたほうがいいんじゃねーのという提案
http://qiita.com/Nkzn/items/f6c4582a92862b3b6f45

https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/util/WiFiUtils.java#L183

# タスク一覧(Overview Screen)で表示するアイコンを変更する
Google I/Oアプリはランチャーアイコンとタスク一覧で表示するアイコンが異なります。

| ランチャーアイコン | タスク一覧でのアイコン |
|---|---|
| ![Screenshot_20151230-000844.png](https://qiita-image-store.s3.amazonaws.com/0/27388/44f33e06-5182-b049-d10d-7c26ec2bfc4a.png)| ![Screenshot_20151230-000831.png](https://qiita-image-store.s3.amazonaws.com/0/27388/08195305-7172-e420-6e63-27f79db417d1.png) |

これは以下の様なコードで実現しているようです。
https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/util/RecentTasksStyler.java

```java:RecentTasksStyler.java
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static void styleRecentTasksEntry(Activity activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            return;
        }

        Resources resources = activity.getResources();
        String label = resources.getString(activity.getApplicationInfo().labelRes);
        int colorPrimary = resources.getColor(R.color.theme_primary);

        if (sIcon == null) {
            // Cache to avoid decoding the same bitmap on every Activity change
            sIcon = BitmapFactory.decodeResource(resources, R.drawable.ic_stat_notification);
        }

        activity.setTaskDescription(new ActivityManager.TaskDescription(label, sIcon, colorPrimary));
    }
}
```

こちらの記事が詳しく解決してくれています。
http://qiita.com/pside/items/e28dfdd1f42f7a2cb9f2

# 通信して画像読み込み
Glideを使う
読み込み終わった時にフェードインアニメーションさせる処理などが書かれていて参考になりそう。
https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/util/ImageLoader.java#L106

## Gradle関連
「有名なAndroid オープンソースアプリ2つのbuild.gradleを読む」の中で紹介しています。
http://qiita.com/takahirom/items/9919697580fa3919df88

## まとめ
全て合わせる必要はないですが、何か方針や書き方などで悩んだ時は見てみると参考にあると思います。
個人的に気になった部分をまとめてみましたが、他にも実装していく中で気になるところがあったら追記していく予定です。
他にも大量に参考になる部分などあると思いますので、ここは読んだほうが良い、ここが間違っているなどございましたらコメントでよろしくお願いします。

ライセンス
---
https://github.com/google/iosched/blob/master/LICENSE.txt