Harmonisasi Warna Dasar di Tampilan Android

1. Sebelum memulai

Dalam codelab ini, Anda akan mempelajari cara menyelaraskan warna kustom dengan warna yang dihasilkan oleh tema dinamis.

Prasyarat

Pengembang seharusnya

  • Pemahaman tentang konsep tema dasar di Android
  • Nyaman bekerja dengan Tampilan widget Android dan propertinya

Yang akan Anda pelajari

  • Cara menggunakan harmonisasi warna di aplikasi Anda menggunakan beberapa metode
  • Cara kerja harmonisasi dan perubahan warnanya

Yang Anda butuhkan

  • Komputer yang dilengkapi Android jika Anda ingin mengikutinya.

2. Ringkasan Aplikasi

Voya lapisani adalah aplikasi transportasi umum yang sudah menggunakan tema dinamis. Untuk banyak sistem transportasi umum, warna merupakan indikator penting untuk kereta, bus, atau trem, dan warna ini tidak dapat diganti dengan warna primer, sekunder, atau tersier dinamis apa pun yang tersedia. Kita akan memfokuskan pekerjaan pada RecyclerView kartu transportasi umum berwarna.

62ff4b2fb6c9e14a.pngS

3. Membuat Tema

Sebaiknya gunakan alat Material Theme Builder kami sebagai perhentian pertama Anda untuk membuat tema Material3. Pada tab khusus, sekarang Anda dapat menambahkan lebih banyak warna ke tema Anda. Di sebelah kanan, Anda akan melihat peran warna dan palet tonal untuk warna-warna tersebut.

Di bagian warna yang diperluas, Anda dapat menghapus atau mengganti nama warna.

20cc2cf72efef213.png

Menu ekspor akan menampilkan sejumlah kemungkinan opsi ekspor. Pada saat penulisan, penanganan khusus setelan harmonisasi Material Theme Builder hanya tersedia di Tampilan Android

6c962ad528c09b4.pngS

Memahami nilai ekspor baru

Agar Anda dapat menggunakan warna ini dan peran warna yang terkait dalam tema Anda, terlepas dari apakah Anda memilih untuk harmonisasi atau tidak, download yang diekspor kini menyertakan file attrs.xml yang berisi nama peran warna untuk setiap warna kustom.

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

Di themes.xml, kita telah membuat empat peran warna untuk setiap warna kustom (color<name>, colorOn<name>, color<name>Container, and colorOn<nameContainer>). Properti harmonize<name> menunjukkan apakah developer telah memilih opsi di Builder Tema Material. Warnanya tidak akan berubah dalam tema inti.

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

Dalam file colors.xml, warna seed yang digunakan untuk menghasilkan peran warna yang tercantum di atas ditentukan bersama dengan nilai boolean untuk mengetahui apakah palet warna akan digeser atau tidak.

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

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

4. Memeriksa Warna Kustom

Dengan memperbesar panel samping Material Theme Builder, kita dapat melihat bahwa menambahkan warna kustom akan memunculkan panel dengan empat peran warna utama dalam palet terang dan gelap.

c6ee942b2b93cd92.png

Di Android View, kita mengekspor warna-warna ini untuk Anda, tetapi di balik layar, warna-warna ini dapat diwakili oleh instance objek ColorRoles.

Class ColorRoles memiliki empat properti, accent, onAccent, accentContainer, dan onAccentContainer. Properti ini adalah representasi bilangan bulat dari empat warna heksadesimal.

public final class ColorRoles {

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

  // truncated code

}

Anda dapat mengambil empat peran warna utama dari warna arbitrer saat runtime menggunakan getColorRoles di class MaterialColors yang disebut getColorRoles yang memungkinkan Anda membuat kumpulan empat peran warna tersebut saat runtime dengan warna seed tertentu.

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

Demikian juga nilai output adalah nilai warna sebenarnya, BUKAN pointer ke nilai tersebut.**

5. Apa itu Harmonisasi Warna?

Sistem warna baru Material dirancang berdasarkan algoritma, yang menghasilkan warna primer, sekunder, tersier, dan netral dari warna benih tertentu. Salah satu poin kekhawatiran yang banyak kami terima ketika berbicara dengan partner internal dan eksternal adalah bagaimana menggunakan warna dinamis sambil tetap mengontrol beberapa warna.

Warna-warna ini sering kali memiliki makna atau konteks tertentu dalam aplikasi yang akan hilang jika diganti dengan warna acak. Atau, jika dibiarkan apa adanya, warna-warna ini mungkin terlihat berantakan atau tidak pada tempatnya.

Warna pada Material You dijelaskan melalui rona, kroma, dan nuansa. Rona warna berkaitan dengan persepsi seseorang tentang warna tersebut sebagai bagian dari satu rentang warna versus yang lain. Tone menggambarkan bagaimana terang atau gelap muncul dan kroma adalah intensitas warna. Persepsi terhadap hue dapat dipengaruhi oleh faktor budaya dan linguistik, seperti kurangnya kata untuk biru dalam budaya kuno, tetapi tidak terlihat di keluarga yang sama dengan warna hijau.

57c46d9974c52e4a.pngSRona tertentu dapat dianggap hangat atau sejuk bergantung pada posisinya di spektrum hue. Pergeseran ke rona merah, oranye, atau kuning umumnya dianggap membuat warnanya lebih hangat dan ke arah biru, hijau, atau ungu dikatakan membuat warnanya lebih dingin. Meski dalam warna hangat atau dingin, Anda akan memiliki nuansa hangat dan dingin. Di bawah ini, "lebih hangat" kuning lebih berwarna oranye sedangkan "lebih dingin" kuning lebih dipengaruhi oleh warna hijau. 597c6428ff6b9669.pngS

Algoritma harmonisasi warna memeriksa hue dari warna yang tidak bergeser dan warna yang harus diselaraskan untuk menemukan hue yang harmonis tetapi tidak mengubah kualitas warna yang mendasarinya. Pada grafik pertama, terdapat rona hijau, kuning, dan oranye yang kurang harmonis yang diplot pada spektrum. Pada grafik berikutnya, hijau dan oranye telah diselaraskan dengan rona kuning. Warna hijau baru lebih hangat dan oranye baru lebih keren.

Rona telah bergeser menjadi oranye dan hijau, tetapi masih dapat dianggap sebagai oranye dan hijau.

766516c321348a7c.pngS

Jika Anda ingin mempelajari lebih lanjut tentang beberapa keputusan desain, eksplorasi, dan pertimbangan, rekan saya Ayan Daniels dan Andrew Lu telah menulis postingan blog yang sedikit lebih mendalam daripada bagian ini.

6. Menyelaraskan warna secara manual

Untuk menyelaraskan nada tunggal, ada dua fungsi di MaterialColors, harmonize, dan harmonizeWithPrimary.

harmonizeWithPrimary menggunakan Context sebagai cara untuk mengakses tema saat ini dan kemudian warna utama darinya.

@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);
  }

Untuk mengambil set empat nada, kita perlu melakukan lebih banyak lagi.

Jika sudah memiliki warna sumber, kita harus:

  1. menentukan apakah harus diselaraskan,
  2. menentukan apakah kita berada dalam mode gelap, dan,
  3. menampilkan objek ColorRoles yang diselaraskan atau tidak diselaraskan.

Menentukan apakah akan menyelaraskan

Dalam tema yang diekspor dari Material Theme Builder, kami menyertakan atribut boolean menggunakan nomenklatur harmonize<Color>. Berikut adalah fungsi kenyamanan untuk mengakses nilai tersebut.

Jika ditemukan, kode akan menampilkan nilainya; yang lainnya, ia menentukan bahwa warna tidak seharusnya selaras.

// 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
}

Membuat objek ColorRoles Harmonized

retrieveHarmonizedColorRoles adalah fungsi praktis lain yang menggabungkan semua langkah yang disebutkan di atas: mengambil nilai warna untuk resource yang dinamai, mencoba me-resolve atribut boolean untuk menentukan harmonisasi, dan menampilkan objek ColorRoles yang berasal dari warna asli atau campuran (berdasarkan skema terang atau gelap).

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. Mengisi kartu multi-trip

Seperti yang disebutkan sebelumnya, kita akan menggunakan RecyclerView dan adaptor untuk mengisi dan mewarnai kumpulan kartu transportasi umum.

e4555089b065b5a7.png

Menyimpan data Transportasi Umum

Untuk menyimpan data teks dan informasi warna untuk kartu transportasi umum, kita menggunakan class data yang menyimpan nama, tujuan, dan ID resource warna.

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)
)

Kita akan menggunakan warna ini untuk menghasilkan nada yang kita perlukan secara real-time.

Anda dapat melakukan harmonisasi saat runtime dengan fungsi onBindViewHolder berikut.

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. Menyelaraskan warna secara otomatis

Sebagai alternatif untuk menangani harmonisasi secara manual, Anda dapat melakukannya sendiri. HarmonizedColorOptions adalah class builder yang memungkinkan Anda menentukan banyak hal yang telah kita lakukan sejauh ini secara manual.

Setelah mengambil konteks saat ini sehingga Anda memiliki akses ke skema dinamis saat ini, Anda perlu menentukan warna dasar yang ingin diselaraskan dan membuat konteks baru berdasarkan objek HarmonizedColorOptions dan konteks yang mengaktifkan DynamicColors.

Jika Anda tidak ingin menyelaraskan warna, cukup jangan sertakan dalam harmonizedOptions.

val newContext = DynamicColors.wrapContextIfAvailable(requireContext())


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

harmonizedContext =
 HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)

Karena warna dasar yang diselaraskan sudah ditangani, Anda dapat memperbarui onBindViewHolder untuk memanggil MaterialColors.getColorRoles serta menentukan apakah peran yang ditampilkan harus terang atau gelap.

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. Menyelaraskan atribut tema secara otomatis

Metode yang ditampilkan hingga saat ini bergantung pada pengambilan peran warna dari masing-masing warna. Cara ini sangat bagus untuk menunjukkan bahwa nada yang sebenarnya sedang dibuat, tetapi tidak realistis untuk sebagian besar aplikasi yang sudah ada. Anda mungkin tidak akan mendapatkan warna secara langsung, tetapi menggunakan atribut tema yang sudah ada.

Sebelumnya dalam codelab ini, kita telah membahas cara mengekspor atribut tema.

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

Mirip dengan metode otomatis pertama, kita dapat memberikan nilai ke HarmonizedColorOptions dan menggunakan HarmonizedColors untuk mengambil Context dengan warna yang diselaraskan. Ada satu perbedaan utama antara kedua metode tersebut. Kita juga perlu menyediakan overlay tema yang berisi kolom yang akan diselaraskan.

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)

Adaptor Anda akan menggunakan konteks yang selaras. Nilai dalam overlay tema harus merujuk pada varian terang atau gelap yang tidak harmonis.

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

Di dalam file tata letak XML, kita dapat menggunakan atribut yang diselaraskan tersebut seperti biasa.

<?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. Kode Sumber

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. Contoh UI

Tema Default dan Warna Kustom tanpa Harmonisasi

a5a02a72aef30529.png

Warna Khusus yang Terselaraskan

4ac88011173d6753.pngS d5084780d2c6b886.png

dd0c8b90eccd8bef.png c51f8a677b22cd54.png

12. Ringkasan

Dalam codelab ini, Anda telah mempelajari:

  • Dasar-dasar algoritma harmonisasi warna kami
  • Cara membuat peran warna dari warna tertentu yang terlihat.
  • Cara menyelaraskan warna secara selektif dalam antarmuka pengguna.
  • Cara menyelaraskan kumpulan atribut dalam tema.

Jika ada pertanyaan, silakan hubungi kami kapan saja menggunakan @MaterialDesign di Twitter.

Nantikan konten desain dan tutorial lainnya di youtube.com/MaterialDesign