ラベル Material Design の投稿を表示しています。 すべての投稿を表示
ラベル Material Design の投稿を表示しています。 すべての投稿を表示

2022年4月14日木曜日

WindowInsetsControllerCompat を使って status bar と navigation bar の light mode を切り替える

Material Catalog アプリのコードを読んでいて見つけたんですが、
WindowCompat.getInsetsController() で取得した WindowInsetsControllerCompat の setAppearanceLightStatusBars() と setAppearanceLightNavigationBars() を使うことで、status bar と navigation bar の light mode(light mode だとアイコンがグレーになり、dark だと白になる)をコードから切り替えることができます。

このようにアプリ用の MaterialTheme のところで SideEffect を使って切り替え処理をすると、Theme の xml で頑張らなくて良くなるので便利です。 @Composable fun MyAppTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { val view = LocalView.current val context = LocalContext.current SideEffect { val controller = WindowCompat.getInsetsController(context.findActivity().window, view) controller?.isAppearanceLightStatusBars = !darkTheme controller?.isAppearanceLightNavigationBars = !darkTheme } MaterialTheme( colors = if (!darkTheme) LightColorPalette else DarkColorPalette,, typography = Typography, shapes = Shapes, content = content ) } private tailrec fun Context.findActivity(): Activity = when (this) { is Activity -> this is ContextWrapper -> this.baseContext.findActivity() else -> throw IllegalArgumentException("Could not find activity!") }

2020年11月19日木曜日

MaterialAlertDialogBuilder のボタンの色を変更する

non-Bridge なテーマで MaterialAlertDialogBuilder を使うと、ダイアログのボタンの色は colorPrimary になります。

そのため colorPrimary に黒っぽい色を指定した DayNight テーマだと、Dark Mode のときにボタンの文字が見えないという状態になってしまいます。 <resources> <style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <item name="colorPrimary">#212121</item> </style> </resources>




DayNight テーマの colorPrimary は変えずに Dark Mode のときだけダイアログのボタンの色を変えるには、materialAlertDialogTheme 属性を指定します。

res/values/themes.xml <resources> <style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <item name="colorPrimary">#212121</item> <item name="materialAlertDialogTheme">@style/ThemeOverlay.MyApp.MaterialAlertDialog</item> </style> <style name="ThemeOverlay.MyApp.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog" /> </resources> res/values-night-v8/themes.xml <resources> <style name="ThemeOverlay.MyApp.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog"> <item name="colorPrimary">#ffffff</item> </style> </resources>




2020年10月21日水曜日

DevFest 2020 で Material Design Components のカスタマイズについて話してきました。



資料はこちらです。



Android Studio 4.1 についてと、まとめの後のおまけにいくつかページを追加しています。
MDC をカスタマイズするときは便利だと思うので、是非参考にしてください。

2020年3月11日水曜日

dialogCornerRadius でダイアログの角丸具合を指定する

Android Pie(API Level 28)から ?android:attr/dialogCornerRadius でダイアログの角丸具合を指定できるようになりましたが、AppCompat や MaterialComponents では ?attr/dialogCornerRadius としてバックポートされています。 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="dialogCornerRadius">12dp</item> </style> (<item name="android:dialogCornerRadius">12dp</item> だと Android Pie(API Level 28)以降だけ角丸になります。)



<item name="dialogCornerRadius">12dp</item>

API Level 21



API Level 27



API Level 28





<item name="android:dialogCornerRadius">12dp</item>

API Level 21



API Level 27



API Level 28





2020年3月10日火曜日

Material Design Components for Android 1.1.0 でボタンのデフォルトカラーが colorAccent から colorPrimary に変わった

Theme.AppCompat.Light.DarkActionBar



Theme.MaterialComponents.Light.DarkActionBar (1.0.0)



Theme.MaterialComponents.Light.DarkActionBar (1.1.0)





何もしてないのに(MDC の version を 1.1.0 に上げたけど...) 色が!変わった!

ピンクはどこの色かというと colorAccent に指定している色です。では緑はどこの色かというと colorPrimary に指定している色です。

ボタン系のデフォルトカラーが 1.1.0 から colorPrimary に変わったようです。



AlertDialog のボタンの色は

?attr/materialAlertDialogTheme に指定されている
ThemeOverlay.MaterialComponents.MaterialAlertDialog

Base.ThemeOverlay.MaterialComponents.MaterialAlertDialog

Base.V14.ThemeOverlay.MaterialComponents.MaterialAlertDialog の <item name="buttonBarPositiveButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog</item> <item name="buttonBarNegativeButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog</item> <item name="buttonBarNeutralButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog.Flush</item>
Widget.MaterialComponents.Button.TextButton.Dialog の <item name="android:textColor">@color/mtrl_text_btn_text_color_selector</item>
@color/mtrl_text_btn_text_color_selector <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:alpha="1.00" android:color="?attr/colorPrimary" .../> <item android:alpha="0.60" android:color="?attr/colorOnSurface" .../> <item android:alpha="1.00" android:color="?attr/colorPrimary" .../> <item android:alpha="0.38" android:color="?attr/colorOnSurface"/> </selector> あー、colorPrimary と colorOnSurface になったのねぇ。

ちなみに 1.0.0 のときの @color/mtrl_text_btn_text_color_selector では colorAccent 使ってます。 <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="?attr/colorAccent" android:state_enabled="true"/> <item android:color="@color/mtrl_btn_text_color_disabled"/> </selector>


ボタンの色を変えたいときは 「AlertDialog の Negative ボタンの文字色を変える」 と同じ感じでやればOK <style name="ThemeOverlay.MyApp.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog"> <item name="buttonBarPositiveButtonStyle">@style/Widget.MyApp.Button.TextButton.Dialog</item> <item name="buttonBarNegativeButtonStyle">@style/Widget.MyApp.Button.TextButton.Dialog</item> </style> <style name="Widget.MyApp.Button.TextButton.Dialog" parent="Widget.MaterialComponents.Button.TextButton.Dialog"> <item name="android:textColor">#1565C0</item> </style> AlertDialog.Builder(this, R.style.ThemeOverlay_MyApp_MaterialAlertDialog) .setTitle("Title") .setMessage("Message") .setPositiveButton(android.R.string.ok, null) .setNegativeButton(android.R.string.cancel, null) .show()



2020年3月9日月曜日

Material Design Components for Android 1.1.0 から Checkbox で android:button を指定するなら app:useMaterialThemeColors="false" も必要(なことが多い)

Checkbox のマークをカスタマイズするときは android:button に drawable resource を指定します。

例えば以下のような drawable を用意して Checkbox の android:button に指定したのが次のスクリーンショットの下2つです。 <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:bottom="7dp" android:left="7dp" android:right="7dp" android:top="7dp"> <selector> <item android:state_checked="true"> <shape android:shape="oval"> <size android:width="18dp" android:height="18dp" /> <stroke android:width="6dp" android:color="#6666ff" /> </shape> </item> <item> <shape android:shape="oval"> <size android:width="18dp" android:height="18dp" /> <stroke android:width="2dp" android:color="#cccccc" /> </shape> </item> </selector> </item> </layer-list> <CheckBox ... android:button="@drawable/checkbox" ... />



全く同じコードで Activity の theme を Material Design Components for Android 1.0.0 に変えたのが次のスクリーンショットです。



同じようになってますね。

全く同じコードで Activity の theme を Material Design Components for Android 1.1.0 に変えたのが次のスクリーンショットです。



なんということでしょう!android:button で指定した drawable resource が Material Design の theme color で tint されるようになりました。tint で使われる色は colorControlActivated と colorOnSurface です。

この挙動を止めるには app:useMaterialThemeColors="false" を指定します。 <CheckBox ... android:button="@drawable/checkbox" app:useMaterialThemeColors="false" ... />



tint されなくなりました!



2015年3月10日火曜日

Material Design Color メモ

Componentsalphacolorcolor resource
text87%#000000#de000000
subheader54%#000000 or primary color#8a000000
hint50%#000000#80000000
divider12%#000000#1f000000


EditText
statealphacolorcolor resource
error100%#f44336#fff44336
hint text26%#000000#43000000



ちなみに colors_material.xml では次のように定義されている <color name="primary_text_default_material_light">#de000000</color> <color name="secondary_text_default_material_light">#8a000000</color>


2015年1月13日火曜日

AppCompat v7 で tint 処理しているクラスとか

メモ

Android 5.0 で EditText の normal 時の色を独自に変える

以前のエントリ「Android 5.0 でのカラーカスタマイズと属性名の関係」で書いたように、EditText の normal 時の線の色は colorControlNormal になります。

colorControlNormal にはデフォルトでは textColorSecondary が指定されており、Theme.Material では @color/secondary_text_material_dark、Theme.Material.Light では @color/secondary_text_material_light になります。
これらの normal 時の色は最終的に以下になります。 #b3ffffff #8a000000 つまり、Theme.Material.Light では線の色は #8a000000 です。

実際にこの色の EditText を配置してみると、Material Design の Components > Text fields で例としてあげられている画像の色より、かなり濃いことがわかります。

colorControlNormal に薄いグレーを指定すれば、EditText の normal 時の色を変えられますが、この属性はさまざまなところで使われており、例えば CheckBox の normal 時の色も薄くなってしまいます。

colorControlNormal ではなく独自に色を指定したい場合、残念ながら用意されている属性値ではできません。 EditText の背景を独自のリソースに変える必要があります。

EditText の 5.0 でのデフォルト背景は次のようになっています。

android:drawable/edit_text_material.xml <inset xmlns:android="http://schemas.android.com/apk/res/android" android:inset="@dimen/control_inset_material"> <ripple android:color="?attr/colorControlActivated"> <item> <selector> <item android:state_enabled="false"> <nine-patch android:src="@drawable/textfield_default_mtrl_alpha" android:tint="?attr/colorControlNormal" android:alpha="?attr/disabledAlpha" /> </item> <item> <nine-patch android:src="@drawable/textfield_default_mtrl_alpha" android:tint="?attr/colorControlNormal" /> </item> </selector> </item> <item android:id="@+id/mask" android:drawable="@drawable/textfield_activated_mtrl_alpha" /> </ripple> </inset>

これを参考に、AppCompat の画像リソースを利用して次のようにします。

drawable-v21/my_edit_text_material.xml <?xml version="1.0" encoding="utf-8"?> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:inset="@dimen/control_inset_material"> <ripple android:color="?attr/colorControlActivated"> <item> <selector> <item android:state_enabled="false"> <nine-patch android:src="@drawable/abc_textfield_default_mtrl_alpha" android:tint="?attr/colorEditTextNormal" android:alpha="?android:attr/disabledAlpha" /> </item> <item> <nine-patch android:src="@drawable/abc_textfield_default_mtrl_alpha" android:tint="?attr/colorEditTextNormal" /> </item> </selector> </item> <item android:id="@android:id/mask" android:drawable="@drawable/abc_textfield_activated_mtrl_alpha" /> </ripple> </inset> ここでは、?attr/colorControlNormal の代わりに ?attr/colorEditTextNormal を指定しています。

values/attr.xml <?xml version="1.0" encoding="utf-8"?> <resources> <attr name="colorEditTextNormal" format="color|reference" /> </resources> values-v21/styles.xml <?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="editTextBackground">@drawable/my_edit_text_material</item> <item name="colorEditTextNormal">#1f000000</item> </style> </resources> Material Design の Resources > Sticker sheets & icons で配布されている stickersheet_general.ai では、EditText の色は divider と同じ #000000 の alpha 12% でした。


Android 5.0 でのカラーカスタマイズと属性名の関係

CheckBox
  • normal : colorControlNormal
  • checked: colorControlActivated
btn_check_material_anim.xml


RadioButton
  • normal : colorControlNormal
  • checked: colorControlActivated
btn_radio_material_anim.xml


Switch

thumb
  • normal : colorSwitchThumbNormal
  • checked: colorControlActivated
switch_thumb_material_anim.xml

track
  • normal : colorForeground, alpha=0.3
  • checked: colorControlActivated, alpha=0.3
switch_track_material.xml


RatingBar
  • normal : colorControlNormal
  • pressed: colorControlActivated
ratingbar_full_empty_material.xml
ratingbar_full_filled_material.xml


EditText
  • normal : colorControlNormal
  • focused: colorControlActivated
edit_text_material.xml


Text選択
  • ハンドル : colorControlActivated
  • カーソル : colorControlActivated
text_cursor_material.xml
text_select_handle_left_material.xml
text_select_handle_middle_material.xml
text_select_handle_right_material.xml


Button

ベース
  • normal : colorButtonNormal
  • pressed : colorControlHighlight
btn_default_material.xml
btn_borderless_material.xml


ToggleButton

ベース
  • normal : colorButtonNormal
  • pressed : colorControlHighlight
インディケータ
  • normal : colorControlNormal
  • checked: colorControlActivated
btn_toggle_material.xml


ProgressBar

  • くるくる : colorControlActivated
vector_drawable_progress_indeterminate_horizontal.xml

Horizontal
  • ベース : colorControlNormal, alpha=disabledAlpha
  • Progress : colorControlActivated
  • SecondaryProgress : colorControlActivated, alpha=disabledAlpha
progress_horizontal_material.xml


SeekBar
  • ベース : colorControlNormal
  • Progress : colorControlActivated
  • 取手 : colorControlActivated
scrubber_control_selector_material.xml
scrubber_control_material_anim.xml
scrubber_progress_horizontal_material.xml


Spinner
  • normal : colorControlNormal
  • pressed : colorControlActivated
  • checked : colorControlActivated
spinner_background_material.xml


ScrollBar
  • handle : colorControlNormal
scrollbar_handle_material.xml


FastScroll

thumb
  • normal : colorControlNormal
  • pressed : colorControlActivated
fastscroll_thumb_material.xml

track
  • 常時 : colorControlNormal
fastscroll_track_material.xml


TimePicker のヘッダー
  • normal : colorAccent
  • pressed: colorControlHighlight
time_picker_header_material.xml


ExpandableListView
  • expander : colorControlNormal
expander_group_material.xml


selectableItemBackground
  • pressed: colorControlHighlight
item_background_material.xml
item_background_borderless_material.xml


■ 参考 <style name="Theme.Material"> ... <!-- Color palette --> <item name="colorPrimaryDark">@color/primary_dark_material_dark</item> <item name="colorPrimary">@color/primary_material_dark</item> <item name="colorAccent">@color/accent_material_dark</item> <item name="colorEdgeEffect">?attr/colorPrimary</item> <item name="colorControlNormal">?attr/textColorSecondary</item> <item name="colorControlActivated">?attr/colorAccent</item> <item name="colorControlHighlight">@color/ripple_material_dark</item> <item name="colorButtonNormal">@color/btn_default_material_dark</item> <item name="colorSwitchThumbNormal">@color/switch_thumb_material_dark</item> </style> <style name="Theme.Material.Light" parent="Theme.Light"> ... <!-- Color palette --> <item name="colorPrimaryDark">@color/primary_dark_material_light</item> <item name="colorPrimary">@color/primary_material_light</item> <item name="colorAccent">@color/accent_material_light</item> <item name="colorControlNormal">?attr/textColorSecondary</item> <item name="colorControlActivated">?attr/colorAccent</item> <item name="colorControlHighlight">@color/ripple_material_light</item> <item name="colorButtonNormal">@color/btn_default_material_light</item> <item name="colorSwitchThumbNormal">@color/switch_thumb_material_light</item> </style>
  • support/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
  • support/v7/appcompat/src/android/support/v7/internal/widget/TintDrawableWrapper.java



2015年1月2日金曜日

Theme.Material の SeekBar は setProgress() で
secondaryProgress の描画が消えてしまう

SeekBar は ProgressBar を継承しているので、setSecondaryProgress() で2番目のプログレスを設定することができます。

下記の動画は setSecondaryProgress() を呼んだ後に setProgress() を呼んだものです。



Holo テーマのスタイルの SeekBar(一番上)では、setProgress() を呼ぶと secondary progress のバーの描画はそのままで progress のバーがセットされます。

一方、Material テーマのスタイルの SeekBar(真ん中)では、setProgress() を呼ぶと secondary progress のバーの描画が消えてしまいます。ちなみにこの時点で getSecondaryProgress() を呼ぶと、描画は消えていますがセットされている値が返ってきます。

Material テーマのスタイルの ProgressBar(一番下)では、secondary progress のバーの描画は消えません。

そこで、SeekBar に style="@android:style/Widget.Material.ProgressBar.Horizontal" を指定してみたところ、Progress Bar と同じような見た目になり、secondary progress のバーの描画は消えなくなりました。 <SeekBar style="@android:style/Widget.Material.ProgressBar.Horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" /> Widget.Material.ProgressBar.Horizontal は次のようになっており、progressDrawable の指定が作用していると予想しました。 <style name="Widget.Material.ProgressBar.Horizontal" parent="Widget.ProgressBar.Horizontal"> <item name="progressDrawable">@drawable/progress_horizontal_material</item> <item name="indeterminateDrawable">@drawable/progress_indeterminate_horizontal_material</item> <item name="minHeight">16dip</item> <item name="maxHeight">16dip</item> </style> そこで、@android:drawable/progress_horizontal_material をプロジェクト内に持ってきて次のように指定したところ、secondary progress のバーの描画は消えなくなりました。 <SeekBar android:layout_width="match_parent" android:layout_height="wrap_content" android:progressDrawable="@drawable/progress_horizontal_material" /> progress_horizontal_material.xml <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@android:id/background"> <nine-patch android:src="@drawable/progress_mtrl_alpha" android:tint="?attr/colorControlNormal" android:alpha="?android:attr/disabledAlpha" /> </item> <item android:id="@android:id/secondaryProgress"> <scale android:scaleWidth="100%"> <nine-patch android:src="@drawable/progress_mtrl_alpha" android:tint="?attr/colorControlActivated" android:alpha="?android:attr/disabledAlpha" /> </scale> </item> <item android:id="@android:id/progress"> <scale android:scaleWidth="100%"> <nine-patch android:src="@drawable/progress_mtrl_alpha" android:tint="?android:attr/colorControlActivated" /> </scale> </item> </layer-list> Material テーマの SeekBar のデフォルトスタイルは Widget.Material.SeekBar です。このスタイルでは progressDrawable に @android:drawable/scrubber_progress_horizontal_material が指定されています。 <style name="Widget.Material.SeekBar"> <item name="indeterminateOnly">false</item> <item name="progressDrawable">@drawable/scrubber_progress_horizontal_material</item> <item name="indeterminateDrawable">@drawable/scrubber_progress_horizontal_material</item> <item name="thumb">@drawable/scrubber_control_material_anim</item> <item name="splitTrack">true</item> <item name="focusable">true</item> <item name="paddingStart">16dip</item> <item name="paddingEnd">16dip</item> <item name="mirrorForRtl">true</item> <item name="background">?attr/selectableItemBackgroundBorderless</item> </style> scrubber_progress_horizontal_material.xml は progress_horizontal_material.xml と違い、ルートタグが <selector> です。

@android:drawable/scrubber_progress_horizontal_material <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="false"> <nine-patch android:src="@drawable/scrubber_track_mtrl_alpha" android:tint="?attr/colorControlNormal" /> </item> <item> <layer-list> <item android:id="@id/background"> <nine-patch android:src="@drawable/scrubber_track_mtrl_alpha" android:tint="?attr/colorControlNormal" /> </item> <item android:id="@id/secondaryProgress"> <scale android:scaleWidth="100%"> <nine-patch android:src="@drawable/scrubber_primary_mtrl_alpha" android:tint="?attr/colorControlNormal" /> </scale> </item> <item android:id="@id/progress"> <scale android:scaleWidth="100%"> <nine-patch android:src="@drawable/scrubber_primary_mtrl_alpha" android:tint="?attr/colorControlActivated" /> </scale> </item> </layer-list> </item> </selector> これの <layer-list> 部分だけを取り出して progressDrawable に指定したところ、secondary progress のバーの描画が消えなくなりました。
なぜ selector の中に入れると描画が変になるのかよくわかりませんが、style="@android:style/Widget.Material.ProgressBar.Horizontal" を指定するのが手っ取り早そうです。


2014年12月19日金曜日

Android AlertDialog のボタンの色を変える Material Design 編

■ Lollipop

values/styles.xml <?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="Theme.Material.Light.DarkActionBar"> <item name="android:alertDialogTheme">@style/AlertDialogTheme</item> </style> <style name="AlertDialogTheme" parent="Theme.Material.Light.Dialog"> <item name="android:colorAccent">#61a00e</item> </style> </resources>


■ AppCompat

values/styles.xml <?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppThemeBase" parent="Theme.AppCompat.Light.DarkActionBar"> </style> </resources> values-v21/styles.xml <?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="AppThemeBase"> <item name="android:alertDialogTheme">@style/AlertDialogTheme</item> </style> <style name="AlertDialogTheme" parent="Theme.AppCompat.Light.Dialog"> <item name="colorAccent">#61a00e</item> </style> </resources>


2014年12月10日水曜日

MaterialTabHost つくりました。

Android Material Design のタブの仕様まとめ」の Fixed Tabs の3つの種類を簡単に実装できる MaterialTabHost を作りました。 github : yanzm/MaterialTabHost

MaterialTabHost.Type.FullScreenWidth





MaterialTabHost.Type.Centered





MaterialTabHost.Type.LeftOffset





2014年12月9日火曜日

Android Material Design な TabHost + ViewPager に移行する

前回のエントリ「Android Material Design のタブの仕様まとめ」で紹介した Material Design の Fixed Tabs full-screen width を TabHost で実装してみます。

Activity 生成ウィザードの Tabbed Activity で Swipe Views(ViewPager)を選択して作成される Activity に付け足していきます。

*以下の例は minSdkVersion = 21 としています。


0. テーマに色をセット

メインカラーとアクセントカラーをセットしておきます

res/values-v21/styles.xml <?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar"> <item name="android:colorPrimaryDark">#00695C</item> <item name="android:colorPrimary">#00897B</item> <item name="android:colorAccent">#FFD54F</item> </style> </resources>


1. 普通に TabHost を実装する <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <TabHost android:id="@android:id/tabhost" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?android:attr/colorPrimary"> <TabWidget android:id="@android:id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="0dp" android:layout_height="0dp" /> </TabHost> <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Create the adapter that will return a fragment for each of the three // primary sections of the activity. mSectionsPagerAdapter = new SectionsPagerAdapter(getFragmentManager()); // Set up the ViewPager with the sections adapter. mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager.setAdapter(mSectionsPagerAdapter); TabHost tabHost = (TabHost) findViewById(android.R.id.tabhost); tabHost.setup(); for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) { tabHost.addTab(tabHost .newTabSpec(String.valueOf(i)) .setIndicator(mSectionsPagerAdapter.getPageTitle(i)) .setContent(android.R.id.tabcontent)); } tabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() { @Override public void onTabChanged(String tabId) { mViewPager.setCurrentItem(Integer.valueOf(tabId)); } }); mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){ @Override public void onPageSelected(int position) { super.onPageSelected(position); tabHost.setCurrentTab(position); } }); }

基本的なタブ機能が実装できましたが、Material Design に沿ったタブにするにはいくつか修正が必要です。
  • タブ間の区切り線を消す
  • Action Bar と タブの間の影を消す
  • タブの下に影を出す
  • タブの文字を仕様に合わせる
  • ViewPager のスクロールに応じてインディケータもスクロールさせる


2. タブ間の区切り線を消す @Override protected void onCreate(Bundle savedInstanceState) { ... TabWidget tabWidget = (TabWidget) findViewById(android.R.id.tabs); // タブ間の区切り線を消す tabWidget.setStripEnabled(false); tabWidget.setShowDividers(LinearLayout.SHOW_DIVIDER_NONE); }


3. Action Bar と タブの間の影を消す @Override protected void onCreate(Bundle savedInstanceState) { ... // Action Bar と タブの間の影を消す getActionBar().setElevation(0); }


4. タブの下に影を出す @Override protected void onCreate(Bundle savedInstanceState) { ... // タブの下に影を出す // App bar の elevation は 4dp // http://www.google.com/design/spec/what-is-material/objects-in-3d-space.html#objects-in-3d-space-elevation float elevation = 4 * getResources().getDisplayMetrics().density; tabHost.setElevation(elevation); }


5. タブの文字を仕様に合わせる

res/color/tab_widget_text.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="#ffffffff" android:state_selected="true" /> <item android:color="#99ffffff" /> </selector> res/layout/tab_widget.xml <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="bottom|center_horizontal" android:paddingBottom="16dp" android:textColor="@color/tab_widget_text" android:textSize="14sp" tools:layout_width="match_parent" tools:text="group 1 ITEM TWO" /> @Override protected void onCreate(Bundle savedInstanceState) { ... LayoutInflater inflater = LayoutInflater.from(this); for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) { TextView tv = (TextView) inflater.inflate(R.layout.tab_widget, tabWidget, false); tv.setText(mSectionsPagerAdapter.getPageTitle(i)); tabHost.addTab(tabHost .newTabSpec(String.valueOf(i)) .setIndicator(tv) .setContent(android.R.id.tabcontent)); } ... }


6. ViewPager のスクロールに応じてインディケータもスクロールさせる <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <TabHost android:id="@android:id/tabhost" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?android:attr/colorPrimary"> ... <View android:id="@+id/indicator" android:layout_width="match_parent" android:layout_height="2dp" android:layout_gravity="bottom" android:background="?android:attr/colorControlActivated" /> </TabHost> ... </LinearLayout> View indicator; TabWidget tabWidget; @Override protected void onCreate(Bundle savedInstanceState) { ... tabWidget = (TabWidget) findViewById(android.R.id.tabs); ... indicator = findViewById(R.id.indicator); mViewPager.setOnPageChangeListener(new PageChangeListener()); } private class PageChangeListener implements ViewPager.OnPageChangeListener { private int scrollingState = ViewPager.SCROLL_STATE_IDLE; @Override public void onPageSelected(int position) { // スクロール中はonPageScrolled()で描画するのでここではしない if (scrollingState == ViewPager.SCROLL_STATE_IDLE) { updateIndicatorPosition(position, 0); } tabWidget.setCurrentTab(position); } @Override public void onPageScrollStateChanged(int state) { scrollingState = state; } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { updateIndicatorPosition(position, positionOffset); } private void updateIndicatorPosition(int position, float positionOffset) { View tabView = tabWidget.getChildTabViewAt(position); int indicatorWidth = tabView.getWidth(); int indicatorLeft = (int) ((position + positionOffset) * indicatorWidth); final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) indicator.getLayoutParams(); layoutParams.width = indicatorWidth; layoutParams.setMargins(indicatorLeft, 0, 0, 0); indicator.setLayoutParams(layoutParams); } }


Android Material Design のタブの仕様まとめ

Tabs - Components - Google design guidelines

共通の仕様

  • タブの高さ : 48dp
  • indicatorの高さ : 2dp
  • タブ間に区切り線なし
  • 文字サイズ : 14sp Roboto Medium
  • 文字位置 : ベースライン = タブ下端から20dp
  • 文字色 :
    • 選択・非選択で同じ色 : 選択 = #ffffff、非選択 = #99ffffff
    • 選択・非選択で別の色 : 選択 = indicatorの色、非選択 = 無彩色

http://material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7OWxWU0JiNjA4Vzg/components_tabs_usage_specs1.png

Fixed Tabs

■ full-screen width
  • タブ幅 : 画面幅 / タブ数
  • タブ数 : せいぜい4つまで。タブレットではタブが横に広がりすぎるので centered か left offset の方がよい


■ centered
  • タブ幅 : 12dp + 最も長いラベル幅 + 12dp(タブレットでは24dp)
  • タブ数 : タブ幅 x タブ数が画面幅を超えない数まで可能
  • タブは中央に寄せる


■ left offset
  • タブ幅 : 12dp + ラベル幅 + 12dp(タブレットでは24dp)
  • タブ数 : オフセット + タブ幅の和が画面幅を超えない数まで可能
  • 画面左端から選択されているタブのラベル左端までのオフセット = 72dp(タブレットでは 80dp)


Types of tabs の Fixed tabs に「Fixed tabs have equal width, based on the widest tab label.」とあるので、それに従ったのが centered、しかし隣の画像をみると Scrollable と同じようにタブによって幅が変わっているのでそれに従ったのが left offset とした。

Scrollable

  • タブ幅 : 12dp + ラベル幅 + 12dp(タブレットでは24dp)
  • 画面左端から選択されているタブのラベル左端までのオフセット = 72dp(タブレットでは 80dp)


2014年12月4日木曜日

Android Activity Transitions の対象から、Navigation Bar と Status Bar を外す(Activity Transitions を実装する その2)

Android Activity Transitions を実装する」の続きです。

Activity Transitions の対象から Navigation Bar と Status Bar を外すには、Transition.excludeTarget() を使います。

getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); Transition transition = new Fade(); // Navigation Bar の背景を対象から外す transition.excludeTarget(android.R.id.navigationBarBackground, true); // Status Bar の背景を対象から外す transition.excludeTarget(android.R.id.statusBarBackground, true); getWindow().setExitTransition(transition); getWindow().setEnterTransition(transition);

↓ 開発者オプションでアニメーションの速度をx5にしています。



Android Activity Transitions を実装する

Activity Transitions のドキュメントを読んだのですが、よくわからなかったので実際に試してみました。

1. window content transitions を有効にする

Activity Transitions を使うには、window content transitions を有効にする必要があります。

コードで有効にする場合 getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); テーマで有効にする場合 <style name="BaseAppTheme" parent="android:Theme.Material"> <item name="android:windowContentTransitions">true</item> </style>

2. transition を指定する

切り替え時のアニメーションを指定します。用意されているクラスは Explode, Fade, Slide です。
自分で transition を作るときは、これらの親クラスの Visibility を継承するのがよさそうです。

コードで指定する場合 getWindow().setExitTransition(new Explode()); Explode, Fade, Slide に対応するリソースは
@android:transition/explode
@android:transition/fade
@android:transition/slide
です。

テーマで指定する場合 <style name="BaseAppTheme" parent="android:Theme.Material"> <item name="android:windowEnterTransition">@android:transition/explode</item> <item name="android:windowExitTransition">@android:transition/explode</item> </style>

3. ActivityOptions を指定して実行

ActivityOptions.makeSceneTransitionAnimation() を使います。
Android Developers のドキュメントでは、引数に this しか渡していませんが、引数が1つのメソッドはありません!(2014年12月4日現在)
次のように第2引数に null を指定します。 startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, null).toBundle()); 全体としてこんな感じになります。 public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); getWindow().setExitTransition(new Explode()); setContentView(R.layout.activity_main); findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { gotoSub(); } }); } private void gotoSub() { Intent intent = new Intent(this, SubActivity.class); startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, null).toBundle()); } } Explode


Fade


Slide




4. sharedElement を指定する

遷移前の画面の一部が拡大などして、遷移後の画面の一部になるような transition が可能です。
そのためには、どの View が共通として見なせるのか指定する必要があります。

遷移前後両方のActivityで、一意の名前を指定します。
レイアウトで指定する場合は android:transitionName を使います。

以下では、それぞれの Activity のレイアウトで ImageView に android:transitionName="image" を指定しています。

遷移前(activity_main.xml) <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" ...> <ImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#cccccc" android:src="@drawable/ic_launcher" android:transitionName="image" /> <Button android:id="@+id/button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:text="go to SubActivity" /> </RelativeLayout> 遷移後(activity_sub.xml) <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" ...> <ImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="200dp" android:background="#cccccc" android:src="@drawable/gopher" android:transitionName="image" /> </RelativeLayout> * わかりやすさのために、遷移前後の ImageView の画像を変えています。


android:transitionName に指定した名前を makeSceneTransitionAnimation() でも指定する必要があります。 public class MainActivity extends Activity { ... private void gotoSub() { ImageView iv = (ImageView) findViewById(R.id.image); Intent intent = new Intent(this, SubActivity.class); startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, iv, "image").toBundle()); } }




android:transitionName を使わずにコードで指定することもできます。 public class MainActivity extends Activity { ... private void gotoSub() { ImageView iv = (ImageView) findViewById(R.id.image); String sharedElementName = "image"; iv.setTransitionName(sharedElementName); Intent intent = new Intent(this, SubActivity.class); startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, iv, sharedElementName) .toBundle()); } } public class SubActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sub); String sharedElementName = "image"; findViewById(R.id.image).setTransitionName(sharedElementName); } }

5. 複数の sharedElement を指定する

複数の View を共通のエレメントとして指定することができます。 public class MainActivity extends Activity { ... private void gotoSub() { ImageView iv = (ImageView) findViewById(R.id.image); String sharedElementName = "image"; iv.setTransitionName(sharedElementName); ImageView iv2 = (ImageView) findViewById(R.id.image2); String sharedElementName2 = "image2"; iv2.setTransitionName(sharedElementName2); Intent intent = new Intent(this, SubActivity.class); startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, new Pair<View, String>(iv, sharedElementName), new Pair<View, String>(iv2, sharedElementName2)) .toBundle()); } } public class SubActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sub); String sharedElementName = "image"; findViewById(R.id.image).setTransitionName(sharedElementName); String sharedElementName2 = "image2"; findViewById(R.id.image2).setTransitionName(sharedElementName2); } }




6. sharedElement の transition を指定する

sharedElement の transition は setSharedElementEnterTransition()setSharedElementExitTransition() で指定します。その他の transition には setExitTransition() や setEnterTransition() で指定したものが適用されます。

コードで指定する場合 Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.custom); getWindow().setSharedElementExitTransition(transition); getWindow().setSharedElementEnterTransition(transition); XMLで用意した transition リソースから Transition オブジェクトを作るには TransitionInflater を使います。

テーマで指定する場合 sharedElement のデフォルトの transition は Theme.Material で指定されており、@android:transition/move になっています。
このファイルの中身は次のようになっています。

@android:transition/move <transitionSet xmlns:android="http://schemas.android.com/apk/res/android"> <changeBounds/> <changeTransform/> <changeClipBounds/> <changeImageTransform/> </transitionSet> changeBounds は View の大きさをアニメーションさせ、changeImageTransform は ImageView にセットされている画像サイズをアニメーションさせます。

transition リソースの書き方は Transition クラスに概要があります。

例えば、@android:transition/move の <transitionSet> に android:transitionOrdering="sequential" を指定すると、各アニメーションが順番に行われます。
res/transition/custom.xml <?xml version="1.0" encoding="utf-8"?> <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="sequential"> <changeBounds /> <changeTransform /> <changeClipBounds /> <changeImageTransform /> </transitionSet>





おまけ: デフォルト設定

2014年7月28日月曜日

Material Design のハンバーガーアイコン

ハンバーガーアイコンは Navigation Drawer を使うときに左上に表示する三本線のアイコンのことです。



Icons - Style - Google design guidelines にも Resources - Google design guidelines にもアイコンリソースが無かったので、Icons のページにあった絵と線の太さが2dpであるという情報からハンバーガーアイコンをaiファイルに起こしました。

ちなみに設計図は次のようになります。1マスが1dpです。



画像のサイズは 48dp x 48dp で、アイコン部分は 24dp x 24dp に収まるようにします。

aiファイルも置いておきます。