ここにアプリケーションがあります。真面目で、大きく、大人。事実上スタイルはありませんが、雑然としていません。AppCompatのウィジェットを使用していますが、マテリアルデザインコンポーネント(MDC)のトピックはすでに厳しくなっており、本格的な移行を検討しています。
そして突然、完全な再設計の課題があります。また、新しい設計には、古い設計と同じビジネスロジックがあります。コンポーネントは新しく、フォントは非標準で、色(企業のものを除く)は異なります。一般的に、MDCに移行する時が来たという認識が生まれます。
しかし、すべてがそれほど単純なわけではありません。
再設計は断片的であると思われます。つまり、アプリケーションには、古い外観と新しい外観の両方の画面が含まれます。
新しいデザインの色とタイポグラフィは、MDCが推奨するものとは異なります。命名の原則は似ていますが
プレゼンテーション層は、個別のUIモジュールに分割されています。そしてそれらのいくつかは別のアプリケーションによって使用されます。スタイルなしで行うことを考えると、そのようなモジュールでのスタイル設定では、色、テキストスタイル、文字列など、一部のプロパティが属性の背後に隠されています。
上記のUIモジュールを操作する方法については確立されたスキームがあります。特に属性について。これは、色、テキストスタイル、文字列なども意味します。そしてMDCではスタイルを使いたい
さらに、これらの問題に対処する方法についての私の経験を共有します。MDCに移行するときに、独立したuiモジュールを使用してAndroidアプリケーションを部分的にスタイル化し、システム設計から抽象化し、何も壊さないようにする方法です。ボーナス-私が遭遇した困難のアドバイスと分析。
UIモジュールについて
UIモジュールがあります。それらはプロジェクトに依存しません。彼とは別に嘘をつく。
各プロジェクト内にルートモジュールがあります。それをコアプレゼンテーションと呼びましょう。これは、このアプリケーションで使用されるuiモジュールによって異なります。モジュールは通常のgradle依存関係として接続されます。
疑問が生じます。何かを様式化する方法は?つまり、属性を使用します。このような各uiモジュール内で、使用される属性が定義されます。これは、アプリケーションテーマによって実装する必要があります。
<resources>
<!-- src -->
<attr name = "someUiModuleBackgroundSrc" format = "reference" />
<!-- string -->
<attr name = "someUiModuleTitleString" format = "reference" />
<attr name = "someUiModuleErrorString" format = "reference" />
<!-- textAppearance -->
<attr name = "someUiModuleTextAppearance1" format = "reference" />
<attr name = "someUiModuleTextAppearance2" format = "reference" />
<attr name = "someUiModuleTextAppearance3" format = "reference" />
<attr name = "someUiModuleTextAppearance4" format = "reference" />
<attr name = "someUiModuleTextAppearance5" format = "reference" />
<attr name = "someUiModuleTextAppearance6" format = "reference" />
<attr name = "someUiModuleTextAppearance7" format = "reference" />
<attr name = "someUiModuleTextAppearance8" format = "reference" />
<!-- color -->
<attr name = "someUiModuleColor1" format = "reference" />
<attr name = "someUiModuleColor2" format = "reference" />
</resources>
:
<androidx.appcompat.widget.AppCompatTextView
android:background = "?someUiModuleBackgroundSrc"
android:text = "?someUiModuleErrorString"
android:textAppearance = "?someUiModuleTextAppearance5"
...
/>
"" ()
. , . , , , .
, :
MDC , MDC. AppCompat'a. framework MDC, :
<TextView ... /><!-- Bad --> <androidx.appcompat.widget.AppCompatTextView ... /><!-- Bad --> <com.google.android.material.textview.MaterialTextView ... /><!-- Good -->
(, , ) ui - (, v2)
- View. , View (
style
xml,defStyleAttr
), . :
<!-- Good --> <com.google.android.material.appbar.MaterialToolbar style = "?toolbarStyleV2" /> <!-- Bad --> <com.google.android.material.appbar.MaterialToolbar android:background = "?primaryColorV2" />
. . :
<item name = "filledTextInputStyleV2">@style/V2.Widget.MyFancyApp.TextInputLayout.Filled</item> <!-- Bad --> <item name = "searchTextInputStyleV2">@style/V2.Widget.MyFancyApp.TextInputLayout.Search</item> <!-- Good --> <item name = "blackOutlinedButtonStyleV2">@style/V2.Widget.MyFancyApp.Button.BlackOutlined</item> <!-- Bad --> <item name = "primaryButtonStyleV2">@style/V2.Widget.MyFancyApp.Button.Primary</item> <!-- Good --> <item name = "secondaryButtonStyleV2">@style/V2.Widget.MyFancyApp.Button.Secondary</item> <!-- Good --> <item name = "textButtonStyleV2">@style/V2.Widget.MyFancyApp.Button.Text</item> <!-- Ok. Based on Figma component name -->
, , core-presentation
:
. ,
UI
ui -
: ; . ?
. , TextView
. ? . . , . TextView
. , MDC , - :
While TextAppearance does support android:textColor, MDC tends to separate concerns by specifying this separately in the main widget styles
:
<item name = "v2TextStyleGiftItemPrice">@style/V2.Widget.MyFancyApp.TextView.GiftItemPrice</item>
<item name = "v2TextStyleGiftItemName">@style/V2.Widget.MyFancyApp.TextView.GiftItemName</item>
...
<style name = "V2.Widget.MyFancyApp.TextView.GiftItemPrice">
<item name = "android:textAppearance">?v2TextAppearanceCaption1</item>
<item name = "android:textColor">?v2ColorOnPrimary</item>
</style>
<style name = "V2.Widget.MyFancyApp.TextView.GiftItemName">
<item name = "android:textAppearance">?v2TextAppearanceCaption1</item>
<item name = "android:textColor">?v2ColorOnPrimary</item>
<item name = "textAllCaps">true</item>
<item name = "android:background">?v2ColorPrimary</item>
</style>
...
<com.google.android.material.textview.MaterialTextView
style = "?v2TextStyleGiftItemPrice"
...
/>
<com.google.android.material.textview.MaterialTextView
style = "?v2TextStyleGiftItemName"
...
/>
, , v2 (, primaryButtonStyleV2
), - (v2TextStyleGiftItemName
). , IDE.
, ui :
<resources>
<!-- -->
<attr name = "cardStyleV2" format = "reference" />
<attr name = "appBarStyleV2" format = "reference" />
<attr name = "toolbarStyleV2" format = "reference" />
<attr name = "primaryButtonStyleV2" format = "reference" />
...
<!-- TextView -->
<attr name = "v2TextStyleGiftCategoryTitle" format = "reference" />
<attr name = "v2TextStyleGiftItemPrice" format = "reference" />
<attr name = "v2TextStyleSearchSuggestion" format = "reference" />
<attr name = "v2TextStyleNoResultsTitle" format = "reference" />
...
<!-- -->
<attr name = "ic16CreditV2" format = "reference" />
<attr name = "ic24CloseV2" format = "reference" />
<attr name = "ic48GiftSentV2" format = "reference" />
...
<!-- -->
<attr name = "shopTitleStringV2" format = "reference" />
<attr name = "shopSearchHintStringV2" format = "reference" />
<attr name = "noResultsStringV2" format = "reference" />
...
<!-- styleable View -->
<declare-styleable name = "ShopPriceSlider">
<attr name = "maxPrice" format = "integer" />
</declare-styleable>
</resources>
. . , .
, TextView
, , ( ).
, , , . .
android:background
, - ? -. . - .
:
<style name = "V2.Widget.MyFancyApp.TextView.GiftItemName">
<item name = "android:textAppearance">?v2TextAppearanceCaption1</item>
<item name = "android:textColor">?v2ColorOnPrimary</item>
</style>
<style name = "V2.Widget.MyFancyApp.Button.Primary" parent = "Widget.MaterialComponents.Button">
...
</style>
<style name = "V2.Widget.MyFancyApp.Button.Primary.Price">
...
<item name = "icon">?ic16CreditV2</item>
</style>
, (android:textAppearance
) . . core-presentation, , , ( @color/
, @style/
, @drawable/
). ?
: . . :
( , ) .
"" (Halloween, Christmas, Easter ). . , , -
, ,
MaterialThemeOverlay
, . android:theme
materialThemeOverlay
, MaterialThemeOverlay.wrap(...)
.
- xml:
<item name = "achievementLevelBarStyleV2">@style/V2.Widget.MyFancyApp.AchievementLevelBar</item>
<style name = "V2.Widget.MyFancyApp.AchievementLevelBar" parent = "">
<item name = "materialThemeOverlay">@style/V2.ThemeOverlay.MyFancyApp.AchievementLevelBar</item>
</style>
View:
class AchievementLevelBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.achievementLevelBarStyleV2
) : LinearLayoutCompat(MaterialThemeOverlay.wrap(context, attrs, defStyleAttr, 0), attrs, defStyleAttr) {
init {
View.inflate(context, R.layout.achievement_level_bar, this)
...
}
...
}
. - , init {}
context
, . : context
. , materialThemeOverlay
, context
getContext()
. MaterialButton
:
public MaterialButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(wrap(context, attrs, defStyleAttr, DEF_STYLE_RES), attrs, defStyleAttr);
// Ensure we are using the correctly themed context rather than the context that was passed in.
context = getContext();
( Kotlin, Lint name shadowing. )
Light status bar
status bar StatusBarView
. , ( edge-to-edge), . , .
, status bar translucent. : - overlay ( ), - . status bar (light): background .
, light status bar translucent StatusBarView
. :
light status bar 23 SDK ( ). , , translucent status bar ( )
Translucent status bar
FLAG_TRANSLUCENT_STATUS
; overlay ( light) -FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
-
fun setLightStatusBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
var flags = window.decorView.systemUiVisibility
flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
window.decorView.systemUiVisibility = flags
}
}
fun clearLightStatusBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
var flags = window.decorView.systemUiVisibility
flags = flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
window.decorView.systemUiVisibility = flags
}
}
FLAG_TRANSLUCENT_STATUS
StatusBarView
status bar. :
class StatusBarView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
init {
...
systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
}
StatusBarView
light status bar,statusBarColor
, light / translucent status bar
StatusBarView
Color State List (CSL)
MDC - CSL. , 23 SDK CSL . android:alpha
. , .
:
color/v2_on_background_20.xml
<selector xmlns:android = "http://schemas.android.com/apk/res/android">
<item android:alpha = "0.20" android:color = "?v2ColorOnBackground" />
</selector>
, , @color/
. , CSL - . v2ColorOnBackground
. CSL v2ColorOnBackground
20% :
<color name = "black">#000000</color> <!-- v2ColorOnBackground -->
<color name = "black_20">#33000000</color> <!-- v2ColorOnBackground 20% opacity -->
, :
, 23 SDK . , MDC 21 . , CSL (, View ), MaterialResources.getColorStateList(). Restricted API
,
CSL
android:background
. :
<style name = "V2.Widget.MyFancyApp.Divider" parent = "">
<item name = "android:background">@drawable/v2_rect</item>
<item name = "android:backgroundTint">@color/v2_on_background_15</item>
...
</style>
android:background
. </shape>
xml. v2_rect.xml - . MDC . .
, ShapeableImageView
( MaterialCardView
)? . :
<com.google.android.material.imageview.ShapeableImageView
style = "?shimmerStyleV2"
...
/>
<item name = "shimmerStyleV2">@style/V2.Widget.MyFancyApp.Shimmer</item>
<style name = "V2.Widget.MyFancyApp.Shimmer">
<item name = "srcCompat">@drawable/v2_rect</item>
<item name = "tint">@color/v2_on_background_15</item>
<item name = "shapeAppearance">@style/V2.ShapeAppearance.MyFancyApp.SmallComponent.Shimmer</item>
</style>
ViewGroup
:
<com.google.android.material.appbar.AppBarLayout
style = "?appBarStyleV2"
...
>
<my.magic.path.StatusBarView
style = "?statusBarStyleV2"
...
/>
<com.google.android.material.appbar.MaterialToolbar
style = "?toolbarStyleV2"
...
/>
</com.google.android.material.appbar.AppBarLayout>
, . , .
. . : ? - , AppBarLayout
( secondaryAppBarStyleV2
). ThemeOverlay:
<item name = "secondaryAppBarStyleV2">@style/V2.Widget.MyFancyApp.AppBarLayout.Secondary</item>
<style name = "V2.Widget.MyFancyApp.AppBarLayout.Secondary">
<item name = "materialThemeOverlay">@style/V2.ThemeOverlay.MyFancyApp.AppBarLayout.Secondary</item>
...
</style>
<style name = "V2.ThemeOverlay.MyFancyApp.AppBarLayout.Secondary" parent = "">
<item name = "statusBarStyleV2">@style/V2.Widget.MyFancyApp.StatusBar.Secondary</item>
<item name = "toolbarStyleV2">@style/V2.Widget.MyFancyApp.Toolbar.Secondary</item>
</style>
, ViewGroup. , View. , - View ( ) ViewGroup, , ThemeOverlay ViewGroup.
MaterialToolbar Toolbar AppCompat
framework inflate MDC. MDC, ( ) framework AppCompat. :
<!-- -->
<Toolbar
...
/>
<!-- -->
<androidx.appcompat.widget.Toolbar
...
/>
- . : MaterialToolbar
, - Toolbar
AppCompat.
. MaterialToolbar
navigationIconTint
. Toolbar
AppCompat. , , navigationIcon Toolbar
- navigationIconTint
. MaterialToolbar
.
Material Design Guidelines, Dense text fields. TextInputLayout
40dp. (Widget.MaterialComponents.TextInputLayout.*.Dense
). ( Guidelines) ( ) ; , .
TextInputLayout
, Dense , start icon ... , Dense . , 40dp. , 0 padding
. .
design_text_input_start_icon.xml
, start icon 48dp. , TextInputLayout
40dp android:layout_height
, .
スタイルを忘れないようにしましょう。密度はスタイルについてです。したがって、android:layout_height
この場合、スタイル内にある必要があります。そして、これは悪いことです。なぜなら、そのTextInputLayout
ようなスタイルで使用する各場所でandroid:layout_height
、マークアップから切り取らなければならないからです(これがなぜであるかという質問への答え):
<item name = "searchTextInputStyleV2">@style/V2.Widget.MyFancyApp.TextInputLayout.Search</item>
<style name = "V2.Widget.MyFancyApp.TextInputLayout.Search" parent = "Widget.MaterialComponents.TextInputLayout.FilledBox.Dense">
<item name = "android:layout_height">40dp</item>
...
</style>
<!-- -->
<com.google.android.material.textfield.TextInputLayout
style = "?searchTextInputStyleV2"
android:layout_width = "match_parent"
android:layout_height = "wrap_content"
/>
<!-- -->
<com.google.android.material.textfield.TextInputLayout
style = "?searchTextInputStyleV2"
android:layout_width = "match_parent"
/>
おそらくこれは単なるバグであり、将来的にはそのような回避策を回避することが可能になるでしょう。
私にとって、それは良い解決策であることがわかりました。これには欠点がありますが、UIモジュールのシステム設計からの抽象化という形での利点と、部分的なスタイリングの可能性がはるかに重要です。
スタイリングツールを最大限に活用してください。それは難しいことではありません。読んでくれてありがとう。