Armonización básica de colores en vistas de Android

1. Antes de comenzar

En este codelab, aprenderás a armonizar tus colores personalizados con los que genera un tema dinámico.

Requisitos previos

Los desarrolladores deben

  • Conocer los conceptos básicos de los temas en Android
  • Te sientes cómodo con las vistas de widgets de Android y sus propiedades.

Qué aprenderás

  • Cómo usar la armonización de colores en tu aplicación con varios métodos
  • Cómo funciona la armonización y cómo cambia el color

Requisitos

  • Una computadora que tenga Android instalado si quieres seguir las instrucciones

2. Descripción general de la app

Voyaĝi es una aplicación de transporte público que ya utiliza un tema dinámico. En muchos sistemas de transporte público, el color es un indicador importante de los trenes, autobuses o tranvías, y estos no se pueden reemplazar por los colores primarios, secundarios o terciarios dinámicos que estén disponibles. Nos enfocaremos en la RecyclerView de tarjetas de transporte público de colores.

62ff4b2fb6c9e14a.png

3. Cómo generar un tema

Te recomendamos que uses nuestra herramienta Material Theme Builder como primer recurso para crear un tema de Material 3. En la pestaña Personalizar, ahora puedes agregar más colores a tu tema. A la derecha, se mostrarán los roles de color y las paletas tonales de esos colores.

En la sección extendida de colores, puedes quitar colores o cambiarles el nombre.

20cc2cf72efef213.png

Se mostrarán varias opciones de exportación posibles en el menú Exportar. Al momento de la redacción, el manejo especial de Material Theme Builder de la configuración de armonización solo está disponible en las vistas de Android.

6c962ad528c09b4.png

Información sobre los nuevos valores de exportación

Para que puedas usar estos colores y los roles de color asociados en tus temas, sin importar si eliges armonizar o no, la descarga exportada ahora incluye un archivo attrs.xml que contiene los nombres de roles de color para cada color personalizado.

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

En topics.xml, generamos las cuatro funciones de color para cada color personalizado (color<name>, colorOn<name>, color<name>Container, and colorOn<nameContainer>). Las propiedades de harmonize<name> reflejan si el desarrollador seleccionó la opción en Material Theme Builder. No cambiará el color del tema principal.

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

En el archivo colors.xml, los colores de origen que se usan para generar los roles de color enumerados anteriormente se especifican junto con valores booleanos para determinar si se cambiará la paleta de colores o no.

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

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

4. Cómo examinar el color personalizado

Si acercamos el panel lateral de Material Theme Builder, podemos ver que agregar un color personalizado muestra un panel con los cuatro roles de color clave en una paleta clara y oscura.

c6ee942b2b93cd92.png

En las vistas de Android, exportamos estos colores por ti, pero, en segundo plano, se pueden representar mediante una instancia del objeto ColorRoles.

La clase ColorRoles tiene cuatro propiedades: accent, onAccent, accentContainer y onAccentContainer. Estas propiedades son la representación de números enteros de los cuatro colores hexadecimales.

public final class ColorRoles {

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

  // truncated code

}

Puedes recuperar los cuatro roles de color clave de un color arbitrario en el tiempo de ejecución usando getColorRoles en la clase MaterialColors llamada getColorRoles, que te permite crear ese conjunto de cuatro roles de color en el tiempo de ejecución con un color de origen específico.

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

Del mismo modo, los valores de salida son los valores de color reales, NO los punteros a ellos.**

5. ¿Qué es la unificación de colores?

El nuevo sistema de colores de Material es algorítmico y genera colores primarios, secundarios, terciarios y neutros a partir de un color de origen determinado. Un tema que nos preocupa mucho al hablar con socios internos y externos era cómo adoptar el color dinámico y, al mismo tiempo, mantener el control sobre algunos colores.

Estos colores suelen tener un significado o contexto específico en la aplicación que se perdería si se reemplazaran por un color aleatorio. Por otro lado, si no se modifican, estos colores pueden parecer visualmente accidentados o fuera de lugar.

El color en Material You se describe por matiz, croma y tono. El matiz de un color se relaciona con la percepción que uno tiene de él como miembro de un rango de color frente a otro. El tono describe qué tan claro u oscuro se ve y el croma es la intensidad del color. La percepción del matiz puede verse afectada por factores culturales y lingüísticos, como la falta de una palabra para el azul en las culturas antiguas que se ve en la misma familia que el verde.

57c46d9974c52e4a.pngUn tono específico puede considerarse cálido o frío según su ubicación en el espectro de tonos. Por lo general, se considera que un cambio hacia un tono rojo, naranja o amarillo aumenta el calor, mientras que hacia uno azul, verde o púrpura se dice que hace que el tono sea más frío. Incluso con los colores cálidos o fríos, tendrás tonos cálidos y fríos. Abajo, la barra de búsqueda el amarillo tiene un tono más anaranjado, mientras que el "más frío" el amarillo está más influenciado por el verde. 597c6428ff6b9669.png

El algoritmo de armonización de colores examina el matiz del color no desplazado y el color con el que debe armonizarse para localizar un tono que sea armonioso, pero que no altere las cualidades subyacentes del color. En el primer gráfico, hay tonalidades menos armoniosas de verde, amarillo y naranja trazados en un espectro. En el siguiente gráfico, el verde y el naranja están armonizados con el tono amarillo. El nuevo verde es más cálido y el nuevo naranja es más frío.

El matiz cambió entre naranja y verde, pero aún pueden percibirse como naranja y verde.

766516c321348a7c.png

Si deseas obtener más información sobre algunas de las decisiones de diseño, las exploraciones y las consideraciones, mis colegas Ayan Daniels y Andrew Lu escribieron una entrada de blog un poco más detallada que esta sección.

6. Armonización manual de un color

Para armonizar un solo tono, hay dos funciones en MaterialColors, harmonize y harmonizeWithPrimary.

harmonizeWithPrimary usa Context como medio para acceder al tema actual y, luego, a su color primario.

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

Para recuperar el conjunto de cuatro tonos, necesitamos hacer un poco más.

Como ya tenemos el color de origen, debemos hacer lo siguiente:

  1. determinar si deben armonizarse
  2. determinar si estamos en modo oscuro
  3. mostrar un objeto ColorRoles armonizado o no armonizado

Determinar si hay que armonizar

En el tema exportado de Material Theme Builder, incluimos atributos booleanos con la nomenclatura harmonize<Color>. A continuación, se muestra una función conveniente para acceder a ese valor.

Si lo encuentra, muestra su valor; de lo contrario, determina que no debería armonizar el 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
}

Crea un objeto ColorRoles armonizado

retrieveHarmonizedColorRoles es otra función conveniente que une todos los pasos mencionados anteriormente: recuperar el valor de color de un recurso con nombre, intentar resolver un atributo booleano para determinar la armonización y mostrar un objeto ColorRoles derivado del color original o combinado (dado el esquema claro u oscuro).

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. Completa tarjetas de transporte público

Como se mencionó antes, usaremos una RecyclerView y un adaptador para propagar y colorear la colección de tarjetas de transporte público.

e4555089b065b5a7.png

Almacenamiento de datos de transporte público

Para almacenar los datos de texto y la información de color de las tarjetas de transporte público, usamos una clase de datos que almacena el nombre, el destino y el ID de recurso de color.

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

Usaremos este color para generar los tonos que necesitamos en tiempo real.

Podrías armonizar en el tiempo de ejecución con la siguiente función 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. Armonización automática de colores

Como alternativa al control manual de la armonización, puedes hacer que se controle por ti. HarmonizedColorOptions es una clase de compilador que te permite especificar gran parte de lo que hemos hecho hasta ahora a mano.

Después de recuperar el contexto actual para tener acceso al esquema dinámico actual, debes especificar los colores base que deseas armonizar y crear un nuevo contexto basado en ese objeto HarmonizedColorOptions y el contexto habilitado para DynamicColors.

Si no deseas armonizar un color, simplemente no lo incluyas en armonizedOptions.

val newContext = DynamicColors.wrapContextIfAvailable(requireContext())


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

harmonizedContext =
 HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)

Con el color base armonizado ya administrado, puedes actualizar tu onBindViewHolder para llamar a MaterialColors.getColorRoles y especificar si los roles que se muestran deben ser oscuros o claros.

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. Unifica automáticamente los atributos del tema

Los métodos que se muestran hasta ahora dependen de la recuperación de los roles de color desde un color individual. Eso es ideal para mostrar que se genera un tono real, pero que no es realista para la mayoría de las aplicaciones existentes. Es probable que no derives un color directamente, sino que uses un atributo de tema existente.

Anteriormente en este codelab, hablamos sobre cómo exportar atributos de temas.

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

De manera similar al primer método automático, podemos proporcionar valores a HarmonizedColorOptions y usar HarmonizedColors para recuperar un objeto Context con los colores armonizados. Hay una diferencia clave entre los dos métodos. Además, necesitamos proporcionar una superposición de tema que contenga los campos que se van a armonizar.

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)

El adaptador usaría el contexto armonizado. Los valores de la superposición de temas deben hacer referencia a la variante clara u oscura no armonizada.

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

Dentro del archivo de diseño XML, podemos usar esos atributos armonizados de manera normal.

<?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. Código fuente

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. IU de ejemplo

Temas predeterminados y colores personalizados sin unificación

a5a02a72aef30529.png

Colores personalizados armonizados

4ac88011173d6753.png d5084780d2c6b886.png

dd0c8b90eccd8bef.png c51f8a677b22cd54.png

12. Resumen

En este codelab, aprendiste lo siguiente:

  • Conceptos básicos de nuestro algoritmo de armonización de colores
  • Cómo generar roles de color a partir de un color visto determinado
  • Cómo armonizar de manera selectiva un color en una interfaz de usuario
  • Cómo armonizar un conjunto de atributos en un tema

Si tienes alguna pregunta, no dudes en consultarnos en cualquier momento mediante @MaterialDesign en Twitter.

Mira más instructivos y contenido de diseño en youtube.com/MaterialDesign.