ボックスの外側のスタイリング

ここにアプリケーションがあります。真面目で、大きく、大人。事実上スタイルはありませんが、雑然としていません。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



View, . . , . .





, . 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 .





左-半透明。 右側-ライト
- translucent; - light

, 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モジュールのシステム設計からの抽象化という形での利点と、部分的なスタイリングの可能性がはるかに重要です。





スタイリングツールを最大限に活用してください。それは難しいことではありません。読んでくれてありがとう。








All Articles