Android 뷰의 기본 색 조정

1. 시작하기 전에

이 Codelab에서는 맞춤 색상을 동적 테마로 생성된 색상과 조화를 이루는 방법을 알아봅니다.

기본 요건

개발자는

  • Android의 기본 테마 설정 개념 숙지
  • Android 위젯 뷰와 그 속성을 익숙하게 사용하기

학습할 내용

  • 여러 방법으로 애플리케이션에서 색상 조화를 사용하는 방법
  • 조화 작동 방식 및 색상 변경 방식

필요한 항목

  • Android가 설치되어 있는 컴퓨터를 준비해야 합니다.

2. 앱 개요

Voyazhi는 이미 동적 테마를 사용하는 대중교통 애플리케이션입니다. 많은 대중교통 시스템에서 색상은 기차, 버스, 트램을 나타내는 중요한 지표이므로 동적 기본, 보조 또는 3차 색상으로 바꿀 수 없습니다. 색상이 있는 교통카드의 RecyclerView에 집중해 보겠습니다.

62ff4b2fb6c9e14a.png

3. 테마 생성

Material 3 테마를 만들기 위한 첫 번째 단계로 Material 테마 빌더 도구를 사용하는 것이 좋습니다. 이제 맞춤 탭에서 테마에 색상을 더 추가할 수 있습니다. 오른쪽에 해당 색상의 색상 역할과 색조 팔레트가 표시됩니다.

확장된 색상 섹션에서 색상을 삭제하거나 이름을 바꿀 수 있습니다.

20cc2cf72efef213.png

내보내기 메뉴에는 여러 가지 내보내기 옵션이 표시됩니다. 이 문서를 작성하는 시점에 Material 테마 빌더의 특별한 조화 설정 처리는 Android 뷰에서만 사용할 수 있습니다.

6c962ad528c09b4.png

새 내보내기 값 이해하기

테마에서 이러한 색상과 관련 색상 역할을 조율 여부를 선택할 수 있도록 이제 내보낸 다운로드에는 각 맞춤 색상의 색상 역할 이름이 포함된 attrs.xml 파일이 포함됩니다.

<resources>
   <attr name="colorCustom1" format="color" />
   <attr name="colorOnCustom1" format="color" />
   <attr name="colorCustom1Container" format="color" />
   <attr name="colorOnCustom1Container" format="color" />
   <attr name="harmonizeCustom1" format="boolean" />

   <attr name="colorCustom2" format="color" />
   <attr name="colorOnCustom2" format="color" />
   <attr name="colorCustom2Container" format="color" />
   <attr name="colorOnCustom2Container" format="color" />
   <attr name="harmonizeCustom2" format="boolean" />
</resources>

theme.xml에서 각 맞춤 색상 (color<name>, colorOn<name>, color<name>Container, and colorOn<nameContainer>)의 색상 역할 4개를 생성했습니다. harmonize<name> 속성은 개발자가 머티리얼 테마 빌더에서 옵션을 선택했는지 여부를 반영합니다. 핵심 테마의 색상은 바뀌지 않습니다.

<resources>
   <style name="AppTheme" parent="Theme.Material3.Light.NoActionBar">
       <!--- Normal theme attributes ... -->

       <item name="colorCustom1">#006876</item>
       <item name="colorOnCustom1">#ffffff</item>
       <item name="colorCustom1Container">#97f0ff</item>
       <item name="colorOnCustom1Container">#001f24</item>
       <item name="harmonizeCustom1">false</item>

       <item name="colorCustom2">#016e00</item>
       <item name="colorOnCustom2">#ffffff</item>
       <item name="colorCustom2Container">#78ff57</item>
       <item name="colorOnCustom2Container">#002200</item>
       <item name="harmonizeCustom2">false</item>
   </style>
</resources>

colors.xml 파일에서 위에 나열된 색상 역할을 생성하는 데 사용된 시드 색상이 색상 팔레트의 변경 여부에 관한 불리언 값과 함께 지정됩니다.

<resources>
   <!-- other colors used in theme -->

   <color name="custom1">#1AC9E0</color>
   <color name="custom2">#32D312</color>
</resources>

4. 커스텀 색상 검사 중

Material Theme Builder의 측면 패널을 확대하면 맞춤 색상을 추가하면 네 가지 주요 색상 역할이 있는 패널이 밝은 색상과 어두운 팔레트로 표시됩니다.

c6ee942b2b93cd92.png

Android 뷰에서는 이러한 색상을 내보내지만 백그라운드에서 ColorRoles 객체의 인스턴스로 표현할 수 있습니다.

ColorRoles 클래스에는 accent, onAccent, accentContainer, onAccentContainer의 4가지 속성이 있습니다. 이러한 속성은 네 가지 16진수 색상의 정수 표현입니다.

public final class ColorRoles {

  private final int accent;
  private final int onAccent;
  private final int accentContainer;
  private final int onAccentContainer;

  // truncated code

}

특정 시드 색상을 고려하여 런타임에 4가지 색상 역할 세트를 만들 수 있는 getColorRoles이라는 MaterialColors 클래스의 getColorRoles를 사용하여 런타임 시 임의의 색상에서 네 가지 주요 색상 역할을 가져올 수 있습니다.

public static ColorRoles getColorRoles(
    @NonNull Context context,
    @ColorInt int color
) { /* implementation */ }

마찬가지로 출력 값은 실제 색상 값이며 이에 대한 포인터가 아닙니다.**

5. 색상 조화란 무엇인가요?

Material의 새로운 색상 시스템은 설계상 알고리즘 방식으로 되어 있으며, 지정된 시드 색상에서 기본 색상, 보조 색상, 3차 색상, 중성 색상을 생성합니다. 내부 및 외부 파트너와 논의하면서 많은 우려사항을 제기한 한 가지 우려사항은 일부 색상을 제어하면서 역동적인 색상을 수용하는 방법에 관한 것이었습니다.

이러한 색상은 임의의 색상으로 대체되면 손실될 수 있는 애플리케이션에서 특정 의미나 컨텍스트를 지니는 경우가 많습니다. 또는 그대로 두면 이러한 색상이 시각적으로 부자연스럽게 보이거나 어울리지 않을 수 있습니다.

Material You의 색상은 색조, 크로마, 색조로 설명할 수 있습니다. 색상의 색조는 한 색상 범위에 다른 색상 범위의 일부로 인식하는 것과 관련이 있습니다. 색조는 밝기를 나타내며 어두운 정도를 나타내며 크로마는 색상의 강도입니다. 색조에 대한 인식은 문화적, 언어적 요인의 영향을 받을 수 있습니다. 예를 들어 고대 문화에서는 파란색이 녹색과 같은 계통에서 파란색을 뜻하는 단어가 부족하다고 언급된 경우가 많았습니다.

57c46d9974c52e4a.png특정 색조는 어떤 색조 스펙트럼에 나타나는지에 따라 따뜻하거나 차가운 것으로 간주될 수 있습니다. 빨간색, 주황색 또는 노란색 색조로 바뀌면 일반적으로 더 따뜻해지는 것으로 간주되며 파란색, 녹색 또는 보라색으로 바꾸면 더 시원해집니다. 따뜻하거나 시원한 색상에서도 따뜻하고 시원한 색조를 느낄 수 있습니다. 아래에서는 노란색은 주황색이지만 노란색이 녹색의 영향을 더 많이 받습니다 597c6428ff6b9669.png

색상 조화 알고리즘은 변하지 않은 색상의 색조와 조화되어야 하는 색상을 검토하여 조화롭지만 기본 색상 품질을 변경하지 않는 색조를 찾습니다. 첫 번째 그래픽에서는 스펙트럼에 따라 덜 조화로운 녹색, 노란색 및 주황색 색조가 있습니다. 다음 그래픽에서는 녹색과 주황색이 노란색 색조와 조화를 이루었습니다. 새로운 초록색은 더 따뜻하고 새 주황색은 더 시원합니다.

주황색과 녹색의 색조가 바뀌었지만 여전히 주황색과 녹색으로 인식될 수 있습니다.

766516c321348a7c.png

제 동료인 Ayan Daniels와 Andrew Lu가 디자인 결정, 탐색 및 고려사항에 대해 자세히 알아보려면 이 섹션보다 더 자세히 다룬 블로그 게시물을 참고하세요.

6. 수동으로 색상 조화

단일 톤을 조율하기 위해 MaterialColors, harmonize, harmonizeWithPrimary에 두 가지 함수가 있습니다.

harmonizeWithPrimaryContext를 사용하여 현재 테마에 액세스하고 이후 테마의 기본 색상에 액세스합니다.

@ColorInt
public static int harmonizeWithPrimary(@NonNull Context context, @ColorInt int colorToHarmonize) {
    return harmonize(
        colorToHarmonize,
        getColor(context, R.attr.colorPrimary, MaterialColors.class.getCanonicalName()));
  }


@ColorInt
  public static int harmonize(@ColorInt int colorToHarmonize, @ColorInt int colorToHarmonizeWith) {
    return Blend.harmonize(colorToHarmonize, colorToHarmonizeWith);
  }

네 가지 톤으로 구성된 세트를 가져오려면 조금 더 해야 합니다.

이미 소스 색상이 있으므로 다음을 실행해야 합니다.

  1. 조율해야 하는지 결정하고
  2. 어두운 모드인지 확인할 수 있습니다.
  3. 조율되거나 조화되지 않은 ColorRoles 객체를 반환합니다.

조율 여부 결정

Material 테마 빌더에서 내보낸 테마에는 명명법 harmonize<Color>를 사용하여 불리언 속성을 포함했습니다. 다음은 이 값에 액세스하는 편의 함수입니다.

값을 찾으면 값을 반환합니다. 색상이 조화를 이루지 않아야 한다고 결정합니다.

// Looks for associated harmonization attribute based on the color id
// custom1 ===> harmonizeCustom1
fun shouldHarmonize(context: Context, colorId: Int): Boolean {
   val root = context.resources.getResourceEntryName(colorId)
   val harmonizedId = "harmonize" + root.replaceFirstChar { it.uppercaseChar() }
   
   val identifier = context.resources.getIdentifier(
           harmonizedId, "bool", context.packageName)
   
   return if (identifier != 0) context.resources.getBoolean(identifier) else false
}

조정된 ColorRoles 객체 만들기

retrieveHarmonizedColorRoles는 앞서 언급한 모든 단계를 조인하는 또 다른 편의 함수입니다. 이름이 지정된 리소스의 색상 값을 가져오고, 불리언 속성을 결정하여 조화를 결정하며, 원래 색상이나 혼합 색상 (밝은 스킴 또는 어두운 스킴)에서 파생된 ColorRoles 객체를 반환합니다.

fun retrieveHarmonizedColorRoles(
   view: View,
   customId: Int,
   isLight: Boolean
): ColorRoles {
   val context = view.context
   val custom = context.getColor(customId);
  
   val shouldHarmonize = shouldHarmonize(context, customId)
   if (shouldHarmonize) {
       val blended = MaterialColors.harmonizeWithPrimary(context, custom)
       return MaterialColors.getColorRoles(blended, isLight)
   } else return MaterialColors.getColorRoles(custom, isLight)
}

7. 교통카드 입력 중

앞서 언급했듯이 RecyclerView 및 어댑터를 사용하여 교통카드 컬렉션을 채우고 색상을 지정합니다.

e4555089b065b5a7.png

대중교통 데이터 저장

교통카드의 텍스트 데이터와 색상 정보를 저장하기 위해 이름, 목적지, 색상 리소스 ID를 저장하는 데이터 클래스를 사용합니다.

data class TransitInfo(val name: String, val destination: String, val colorId: Int)

/*  truncated code */

val transitItems = listOf(
   TransitInfo("53", "Irvine", R.color.custom1),
   TransitInfo("153", "Brea", R.color.custom1),
   TransitInfo("Orange County Line", "Oceanside", R.color.custom2),
   TransitInfo("Pacific Surfliner", "San Diego", R.color.custom2)
)

이 색상을 사용하여 실시간으로 필요한 색조를 생성합니다.

다음 onBindViewHolder 함수로 런타임 시 조정할 수 있습니다.

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
   val transitInfo = list.get(position)
   val color = transitInfo.colorId
   if (!colorRolesMap.containsKey(color)) {

       val roles = retrieveHarmonizedColorRoles(
           holder.itemView, color,
           !isNightMode(holder.itemView.context)
       )
       colorRolesMap.put(color, roles)
   }

   val card = holder.card
   holder.transitName.text = transitInfo.name
   holder.transitDestination.text = transitInfo.destination

   val colorRoles = colorRolesMap.get(color)
   if (colorRoles != null) {
       holder.card.setCardBackgroundColor(colorRoles.accentContainer)
       holder.transitName.setTextColor(colorRoles.onAccentContainer)
       holder.transitDestination.setTextColor(colorRoles.onAccentContainer)
   }
}

8. 자동으로 색상 조화

조화를 수동으로 처리하는 대신 처리되도록 할 수 있습니다. HarmonizedColorOptions는 지금까지 수행한 작업의 대부분을 지정할 수 있는 빌더 클래스입니다.

현재 동적 스킴에 액세스할 수 있도록 현재 컨텍스트를 가져온 후에는 조율하려는 기본 색상을 지정하고 해당 HarmonizedColorOptions 객체와 DynamicColors가 사용 설정된 컨텍스트를 기반으로 새 컨텍스트를 만들어야 합니다.

색상의 조화를 원하지 않는 경우 HaronizedOptions에 포함하지 마세요.

val newContext = DynamicColors.wrapContextIfAvailable(requireContext())


val harmonizedOptions = HarmonizedColorsOptions.Builder()
 .setColorResourceIds(intArrayOf(R.color.custom1, R.color.custom2))
 .build();

harmonizedContext =
 HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)

조율된 기본 색상이 이미 처리되었으므로 onBindViewHolder를 업데이트하여 단순히 MaterialColors.getColorRoles를 호출하고 반환된 역할이 밝은지 어두워야 하는지를 지정할 수 있습니다.

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
   /*...*/
   val color = transitInfo.colorId
   if (!colorRolesMap.containsKey(color)) {

       val roles = MaterialColors.getColorRoles(context.getColor(color), !isNightMode(context))

       )
       colorRolesMap.put(color, roles)
   }

   val card = holder.card
   holder.transitName.text = transitInfo.name
   holder.transitDestination.text = transitInfo.destination

   val colorRoles = colorRolesMap.get(color)
   if (colorRoles != null) {
       holder.card.setCardBackgroundColor(colorRoles.accentContainer)
       holder.transitName.setTextColor(colorRoles.onAccentContainer)
       holder.transitDestination.setTextColor(colorRoles.onAccentContainer)
   }
}

9. 테마 속성 자동 조정

지금까지 표시된 메서드는 개별 색상에서 색상 역할을 검색하는 데 의존합니다. 이는 실제 톤이 생성되고 있지만 대부분의 기존 애플리케이션에는 현실적이지 않음을 보여주는 데 유용합니다. 색상을 직접 가져오는 대신 기존 테마 속성을 사용할 가능성이 높습니다.

이 Codelab 앞부분에서 테마 속성 내보내기에 관해 이야기했습니다.

<resources>
   <style name="AppTheme" parent="Theme.Material3.Light.NoActionBar">
       <!--- Normal theme attributes ... -->

       <item name="colorCustom1">#006876</item>
       <item name="colorOnCustom1">#ffffff</item>
       <item name="colorCustom1Container">#97f0ff</item>
       <item name="colorOnCustom1Container">#001f24</item>
       <item name="harmonizeCustom1">false</item>

       <item name="colorCustom2">#016e00</item>
       <item name="colorOnCustom2">#ffffff</item>
       <item name="colorCustom2Container">#78ff57</item>
       <item name="colorOnCustom2Container">#002200</item>
       <item name="harmonizeCustom2">false</item>
   </style>
</resources>

첫 번째 자동 메서드와 마찬가지로 HarmonizedColorOptions에 값을 제공하고 HarmonizedColors를 사용하여 조율된 색상이 있는 컨텍스트를 검색할 수 있습니다. 두 방법 간에는 한 가지 주요 차이점이 있습니다. 또한 조율할 필드가 포함된 테마 오버레이를 제공해야 합니다.

val dynamicColorsContext = DynamicColors.wrapContextIfAvailable(requireContext())

// Harmonizing individual attributes
val harmonizedColorAttributes = HarmonizedColorAttributes.create(
 intArrayOf(
   R.attr.colorCustom1,
   R.attr.colorOnCustom1,
   R.attr.colorCustom1Container,
   R.attr.colorOnCustom1Container,
   R.attr.colorCustom2,
   R.attr.colorOnCustom2,
   R.attr.colorCustom2Container,
   R.attr.colorOnCustom2Container
 ), R.style.AppTheme_Overlay
)
val harmonizedOptions =
 HarmonizedColorsOptions.Builder().setColorAttributes(harmonizedColorAttributes).build()

val harmonizedContext =
 HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)

어댑터는 조율된 컨텍스트를 사용합니다. 테마 오버레이의 값은 조화되지 않은 밝은 변형이나 어두운 변형을 참조해야 합니다.

<style name="AppTheme.Overlay" parent="AppTheme">
   <item name="colorCustom1">@color/harmonized_colorCustom1</item>
   <item name="colorOnCustom1">@color/harmonized_colorOnCustom1</item>
   <item name="colorCustom1Container">@color/harmonized_colorCustom1Container</item>
   <item name="colorOnCustom1Container">@color/harmonized_colorOnCustom1Container</item>

   <item name="colorCustom2">@color/harmonized_colorCustom2</item>
   <item name="colorOnCustom2">@color/harmonized_colorOnCustom2</item>
   <item name="colorCustom2Container">@color/harmonized_colorCustom2Container</item>
   <item name="colorOnCustom2Container">@color/harmonized_colorOnCustom2Container</item>
</style>

XML 레이아웃 파일 내에서 이러한 조율된 속성을 정상적으로 사용할 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   style="?attr/materialCardViewFilledStyle"
   android:id="@+id/card"
   android:layout_width="80dp"
   android:layout_height="100dp"
   android:layout_marginStart="8dp"
   app:cardBackgroundColor="?attr/colorCustom1Container"
   >

   <androidx.constraintlayout.widget.ConstraintLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_margin="8dp">

       <TextView
           android:id="@+id/transitName"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:textSize="28sp"
           android:textStyle="bold"
           android:textColor="?attr/colorOnCustom1Container"
           app:layout_constraintTop_toTopOf="parent" />

       <TextView
           android:id="@+id/transitDestination"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_marginBottom="4dp"
           android:textColor="?attr/colorOnCustom1Container"
           app:layout_constraintBottom_toBottomOf="parent" />
   </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

10. 소스 코드

package com.example.voyagi.harmonization.ui.dashboard

import android.content.Context
import android.content.res.Configuration
import android.graphics.Typeface
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.voyagi.harmonization.R
import com.example.voyagi.harmonization.databinding.FragmentDashboardBinding
import com.example.voyagi.harmonization.ui.home.TransitCardAdapter
import com.example.voyagi.harmonization.ui.home.TransitInfo
import com.google.android.material.card.MaterialCardView
import com.google.android.material.color.ColorRoles
import com.google.android.material.color.DynamicColors
import com.google.android.material.color.HarmonizedColorAttributes
import com.google.android.material.color.HarmonizedColors
import com.google.android.material.color.HarmonizedColorsOptions
import com.google.android.material.color.MaterialColors


class DashboardFragment : Fragment() {

 enum class TransitMode { BUS, TRAIN }
 data class TransitInfo2(val name: String, val destination: String, val mode: TransitMode)

 private lateinit var dashboardViewModel: DashboardViewModel
 private var _binding: FragmentDashboardBinding? = null

 // This property is only valid between onCreateView and
 // onDestroyView.
 private val binding get() = _binding!!

 override fun onCreateView(
   inflater: LayoutInflater,
   container: ViewGroup?,
   savedInstanceState: Bundle?
 ): View? {
   dashboardViewModel =
     ViewModelProvider(this).get(DashboardViewModel::class.java)

   _binding = FragmentDashboardBinding.inflate(inflater, container, false)
   val root: View = binding.root


   val recyclerView = binding.recyclerView

   val transitItems = listOf(
     TransitInfo2("53", "Irvine", TransitMode.BUS),
     TransitInfo2("153", "Brea", TransitMode.BUS),
     TransitInfo2("Orange County Line", "Oceanside", TransitMode.TRAIN),
     TransitInfo2("Pacific Surfliner", "San Diego", TransitMode.TRAIN)
   )
  
   val dynamicColorsContext = DynamicColors.wrapContextIfAvailable(requireContext())

   // Harmonizing individual attributes
   val harmonizedColorAttributes = HarmonizedColorAttributes.create(
     intArrayOf(
       R.attr.colorCustom1,
       R.attr.colorOnCustom1,
       R.attr.colorCustom1Container,
       R.attr.colorOnCustom1Container,
       R.attr.colorCustom2,
       R.attr.colorOnCustom2,
       R.attr.colorCustom2Container,
       R.attr.colorOnCustom2Container
     ), R.style.AppTheme_Overlay
   )
   val harmonizedOptions =
     HarmonizedColorsOptions.Builder().setColorAttributes(harmonizedColorAttributes).build()

   val harmonizedContext =
     HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)


   val adapter = TransitCardAdapterAttr(transitItems, harmonizedContext)
   recyclerView.adapter = adapter
   recyclerView.layoutManager =
     LinearLayoutManager(harmonizedContext, RecyclerView.HORIZONTAL, false)

   return root
 }

 override fun onDestroyView() {
   super.onDestroyView()
   _binding = null
 }
}

class TransitCardAdapterAttr(val list: List<DashboardFragment.TransitInfo2>, context: Context) :
 RecyclerView.Adapter<RecyclerView.ViewHolder>() {
 val colorRolesMap = mutableMapOf<Int, ColorRoles>()
 private var harmonizedContext: Context? = context

 override fun onCreateViewHolder(
   parent: ViewGroup,
   viewType: Int
 ): RecyclerView.ViewHolder {
   return if (viewType == DashboardFragment.TransitMode.BUS.ordinal) {
     BusViewHolder(LayoutInflater.from(harmonizedContext).inflate(R.layout.transit_item_bus, parent, false))
   } else TrainViewHolder(LayoutInflater.from(harmonizedContext).inflate(R.layout.transit_item_train, parent, false))
 }

 override fun getItemCount(): Int {
   return list.size
 }

 override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
   val item = list[position]
   if (item.mode.ordinal == DashboardFragment.TransitMode.BUS.ordinal) {
     (holder as BusViewHolder).bind(item)
     (holder as TransitBindable).adjustNameLength()
   } else {
       (holder as TrainViewHolder).bind(item)
       (holder as TransitBindable).adjustNameLength()
   }
 }

 override fun getItemViewType(position: Int): Int {
   return list[position].mode.ordinal
 }

 interface TransitBindable {
   val card: MaterialCardView
   var transitName: TextView
   var transitDestination: TextView

   fun bind(item: DashboardFragment.TransitInfo2) {
     transitName.text = item.name
     transitDestination.text = item.destination
   }
   fun Float.toDp(context: Context) =
     TypedValue.applyDimension(
       TypedValue.COMPLEX_UNIT_DIP,
       this,
       context.resources.displayMetrics
     )
   fun adjustNameLength(){
     if (transitName.length() > 4) {
       val layoutParams = card.layoutParams
       layoutParams.width = 100f.toDp(card.context).toInt()
       card.layoutParams = layoutParams
       transitName.setTypeface(Typeface.DEFAULT_BOLD);

       transitName.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16.0f)
     }
   }
 }

 inner class BusViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), TransitBindable {
   override val card: MaterialCardView = itemView.findViewById(R.id.card)
   override var transitName: TextView = itemView.findViewById(R.id.transitName)
   override var transitDestination: TextView = itemView.findViewById(R.id.transitDestination)
 }
 inner class TrainViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), TransitBindable {
   override val card: MaterialCardView = itemView.findViewById(R.id.card)
   override var transitName: TextView = itemView.findViewById(R.id.transitName)
   override var transitDestination: TextView = itemView.findViewById(R.id.transitDestination)
 }
}

11. UI 예시

조정 기능이 없는 기본 테마 설정 및 맞춤 색상

a5a02a72aef30529.png

조화로운 커스텀 색상

4ac88011173d6753.png d5084780d2c6b886.png

dd0c8b90eccd8bef.png c51f8a677b22cd54.png

12. 요약

이 Codelab에서 알아본 내용은 다음과 같습니다.

  • Google 색상 조화 알고리즘의 기본 사항
  • 표시된 색상에서 색상 역할을 생성하는 방법
  • 사용자 인터페이스에서 색상을 선택적으로 조율하는 방법
  • 테마에서 속성 집합을 조정하는 방법

궁금한 사항은 언제든지 Twitter의 @MaterialDesign으로 문의해 주세요.

youtube.com/MaterialDesign에서 더 많은 디자인 콘텐츠 및 튜토리얼을 기대해 주세요.