結構命名など様々な種類があり方針を決めるのは悩みますし時間がかかります。
考えることも重要ですが、標準的なものを真似しておくと素早く開発していくことができます。
出来れば既存のプロジェクトの方針をつまみ食いして開発したいです。
また、自分は毎回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を使ってLinearLayoutにもRippleをつけている(リストのアイテムにつけているみたい)
<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の仕組みを使い変えたりしているようです。
よく使うスペース
<!-- standard metrics -->
<dimen name="spacing_normal">8dp</dimen>
<dimen name="spacing_micro">4dp</dimen>
<dimen name="padding_normal">16dp</dimen>
<!-- standard metrics -->
<dimen name="spacing_normal">12dp</dimen>
<dimen name="spacing_micro">8dp</dimen>
<dimen name="padding_normal">24dp</dimen>
keyline
<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>
<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だけステータスバーに透過で表示する
レイアウトにandroid:fitsSystemWindows="true"をつけると、ステータスバー領域の部分の中に普通に表示されるので、ルートのレイアウトとNavigationDrawerにつけてあげて、透過の表示をしているようです。
http://www.google.com/design/spec/patterns/navigation-drawer.html より
<!-- 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に以下をつける。
android:textStyle="@integer/font_textStyle_medium"
android:fontFamily="@string/font_fontFamily_medium"
Lollipop未満はsans-serifのboldで
以上であればsans-serif-mediumのnormalにするみたい
sans-serif-mediumが使えるのが21からだからかも?
<resources>
<integer name="font_textStyle_medium">1</integer> <!-- bold -->
<string name="font_fontFamily_medium">sans-serif</string>
</resources>
<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"を指定する
<!-- 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があることでしょうか。どこかのマテリアルデザイン動画などでも2つあると言っていたと思います。
<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>
これらの色はテーマから参照しており、基本的にレイアウトからは以下のように参照してテーマの色を使います。
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
テーマは継承できるので、以下の様な構造を作っています。
一番親としてTheme.AppCompat.Light.NoationBarを継承しています。これは白色をベースとしたアクションバーを表示しないAppCompatのテーマです。IOアプリは画面ごとにテーマを作っていますが、共通部分は同じテーマを継承することで共通化を図っています。
おそらく、一番重要なのはどんな場合も使われるTheme.IOSched.Baseでしょう。
どの要素がどこに反映されるかを理解しつつ反映していく必要がありますが、ベースとなるcolorPrimaryやcolorPrimaryDark、colorAccentは設定しておきましょう。
<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>
また独自にstyleの要素を定義していて、それを使っているようです。
例えばactionBarInsetStartはToolbarのコンテンツの横からの位置を設定しています。
これは画面によってこの部分を変えたいということがあり、画面によってその部分をテーマで変更して利用しているようです。(スタイルでもオーバーライドっていうのかな?)
<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
<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管理
アニメーション
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メソッドを用意してそれを使う
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
タスク一覧(Overview Screen)で表示するアイコンを変更する
Google I/Oアプリはランチャーアイコンとタスク一覧で表示するアイコンが異なります。
ランチャーアイコン | タスク一覧でのアイコン |
---|---|
これは以下の様なコードで実現しているようです。
https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/util/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
まとめ
全て合わせる必要はないですが、何か方針や書き方などで悩んだ時は見てみると参考にあると思います。
個人的に気になった部分をまとめてみましたが、他にも実装していく中で気になるところがあったら追記していく予定です。
他にも大量に参考になる部分などあると思いますので、ここは読んだほうが良い、ここが間違っているなどございましたらコメントでよろしくお願いします。
ライセンス