Phối màu cơ bản trong Khung hiển thị Android

1. Trước khi bắt đầu

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách hài hoà màu tuỳ chỉnh với những màu do giao diện động tạo ra.

Điều kiện tiên quyết

Nhà phát triển phải

  • Làm quen với các khái niệm cơ bản về tuỳ chỉnh giao diện trong Android
  • Thoải mái làm việc với tiện ích Android Views và các thuộc tính tương ứng

Kiến thức bạn sẽ học được

  • Cách sử dụng tính năng hài hoà màu sắc trong ứng dụng bằng nhiều phương pháp
  • Cách hoạt động của sự hài hoà và cách phối màu thay đổi

Bạn cần có

  • Một máy tính đã cài đặt Android nếu bạn muốn theo dõi.

2. Tổng quan về ứng dụng

VoyaÅii là một ứng dụng chuyển tuyến đã sử dụng giao diện động. Đối với nhiều hệ thống phương tiện công cộng, màu sắc là một chỉ báo quan trọng cho biết tàu hoả, xe buýt hoặc xe điện và bạn không thể thay thế chúng bằng bất cứ màu động nào có sẵn ở cấp chính, phụ hoặc thứ ba. Chúng ta sẽ tập trung vào RecyclerView của thẻ đi phương tiện công cộng có màu.

62ff4b2fb6c9e14a.pngS

3. Tạo một giao diện

Bạn nên sử dụng công cụ Material Theme Builder (Trình tạo giao diện Material) làm điểm dừng đầu tiên để tạo giao diện Material3. Trên thẻ tuỳ chỉnh, bạn hiện có thể thêm nhiều màu khác vào giao diện của mình. Ở bên phải, bạn sẽ thấy vai trò của màu sắc và bảng sắc độ của các màu đó.

Trong phần màu mở rộng, bạn có thể xoá hoặc đổi tên màu.

20cc2cf72efef213.png.

Trình đơn xuất sẽ hiển thị một số cách xuất có thể có. Tại thời điểm viết bài này, cách xử lý đặc biệt đối với chế độ cài đặt hài hoà của Material Theme Builder (Trình tạo giao diện Material) chỉ có trong Chế độ xem Android

6c962ad528c09b4.png.

Tìm hiểu các giá trị mới để xuất dữ liệu

Để cho phép bạn sử dụng những màu này và vai trò của màu liên quan trong giao diện bất kể bạn có chọn phối hợp màu sắc hay không, tệp tải xuống đã xuất hiện sẽ bao gồm một tệp attrs.xml chứa tên vai trò của màu cho từng màu tuỳ chỉnh.

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

Trong themes.xml, chúng tôi đã tạo 4 vai trò của màu sắc cho mỗi màu tuỳ chỉnh (color<name>, colorOn<name>, color<name>Container, and colorOn<nameContainer>). Các thuộc tính harmonize<name> cho biết liệu nhà phát triển đã chọn tuỳ chọn trong Material Theme Builder (Trình tạo giao diện Material) hay chưa. Thao tác này sẽ không chuyển màu trong giao diện chính.

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

Trong tệp colors.xml, màu gốc dùng để tạo vai trò của màu liệt kê ở trên được chỉ định cùng với các giá trị boolean để cho biết bảng màu có bị thay đổi hay không.

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

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

4. Kiểm tra màu tùy chỉnh

Phóng to bảng điều khiển bên của Material Theme Builder (Trình tạo giao diện Material), chúng ta có thể thấy rằng thao tác thêm màu tuỳ chỉnh sẽ hiển thị một bảng điều khiển có 4 vai trò của màu chính trong bảng màu sáng và tối.

c6ee942b2b93cd92.png

Trong Khung hiển thị Android, chúng ta xuất các màu này cho bạn. Tuy nhiên, trong hậu trường, màu sắc có thể được biểu thị bằng một thực thể của đối tượng ColorRoles.

Lớp ColorRole có 4 thuộc tính là accent, onAccent, accentContaineronAccentContainer. Các thuộc tính này là đại diện số nguyên của bốn màu thập lục phân.

public final class ColorRoles {

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

  // truncated code

}

Bạn có thể truy xuất 4 vai trò của màu chính từ một màu tuỳ ý trong thời gian chạy bằng cách sử dụng getColorRoles trong lớp MaterialColors có tên là getColorRoles. Lớp này cho phép bạn tạo tập hợp 4 vai trò của màu sắc đó trong thời gian chạy dựa trên một màu gốc cụ thể.

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

Tương tự, các giá trị đầu ra là các giá trị màu thực tế, KHÔNG phải là con trỏ vào các giá trị đó.**

5. Phối màu là gì?

Hệ thống màu mới của Material sử dụng thuật toán theo thiết kế, tạo ra màu chính, màu phụ, màu thứ ba và màu trung tính từ một màu gốc nhất định. Một vấn đề mà chúng tôi nhận được rất nhiều khi trao đổi với các đối tác nội bộ và bên ngoài là cách nắm bắt màu sắc động trong khi vẫn kiểm soát được một số màu sắc.

Những màu này thường mang một ý nghĩa hoặc ngữ cảnh cụ thể trong ứng dụng và sẽ bị mất nếu được thay thế bằng một màu ngẫu nhiên. Ngoài ra, nếu bạn để nguyên vị trí, các màu này có thể trông chói mắt hoặc không phù hợp.

Màu sắc trong Material You được mô tả qua màu sắc, sắc độ và tông màu. Màu sắc của màu sắc liên quan đến cảm nhận của một người về màu đó như là một phần của dải màu này so với dải màu khác. Tông màu mô tả độ sáng hoặc tối mà màu đó xuất hiện, còn sắc độ là độ đậm của màu. Quan điểm về màu sắc có thể chịu ảnh hưởng của các yếu tố văn hoá và ngôn ngữ, chẳng hạn như việc không có từ màu xanh dương trong các nền văn hoá cổ đại mà thay vào đó là màu xanh lục trong cùng một bộ.

57c46d9974c52e4a.png.Một tông màu cụ thể có thể được coi là ấm hay lạnh tuỳ thuộc vào vị trí của màu đó trên phổ màu. Việc chuyển sang tông màu đỏ, cam hoặc vàng thường được coi là làm cho không khí ấm hơn và chuyển sang màu xanh lam, xanh lục hoặc tím được cho là làm cho không khí mát hơn. Ngay cả trong màu ấm hoặc màu lạnh, bạn vẫn sẽ có tông màu ấm và lạnh. Phía dưới, trạng thái "ấm hơn" màu vàng thì phủ màu cam hơn còn màu "mát hơn" màu vàng chịu ảnh hưởng nhiều hơn của màu xanh lục. 597c6428ff6b9669.pngS

Thuật toán hài hoà màu kiểm tra tông màu của màu không bị dịch chuyển và màu của nó phải được hài hoà để xác định màu sắc hài hoà nhưng không làm thay đổi chất lượng màu cơ bản. Trong hình ảnh đầu tiên, các sắc độ xanh lục, vàng và cam ít hài hoà hơn được thể hiện trên một dải phổ. Ở hình ảnh đồ hoạ tiếp theo, màu xanh lục và cam đã được hài hoà với tông màu vàng. Màu xanh lục mới ấm hơn và màu cam mới lạnh lẽo hơn.

Màu đã dịch chuyển sang màu cam và xanh lục nhưng vẫn có thể nhận biết được là màu cam và xanh lục.

766516c321348a7c.png.

Nếu bạn muốn tìm hiểu thêm về một số quyết định thiết kế, khám phá và những điều cần cân nhắc, các đồng nghiệp của tôi, Ayan Daniels và Andrew Lu đã viết một bài đăng trên blog chuyên sâu hơn so với phần này.

6. Phối màu theo cách thủ công

Để hài hoà một tông màu duy nhất, có 2 hàm trong MaterialColors, harmonizeharmonizeWithPrimary.

harmonizeWithPrimary dùng Context làm phương tiện để truy cập vào giao diện hiện tại, sau đó truy cập vào màu chính trên giao diện đó.

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

Để truy xuất tập hợp 4 sắc độ, chúng ta cần thao tác nhiều hơn một chút.

Vì đã có màu nguồn, chúng ta cần:

  1. xác định xem có cần hài hoà hay không,
  2. xác định xem chúng ta có đang ở chế độ tối hay không và,
  3. trả về đối tượng ColorRoles hài hoà hoặc không hài hoà.

Xác định xem có hài hoà hay không

Trong giao diện đã xuất của Material Theme Builder (Trình tạo giao diện Material), chúng ta đã đưa các thuộc tính boolean vào bằng cách sử dụng danh pháp harmonize<Color>. Dưới đây là một hàm tiện lợi để truy cập vào giá trị đó.

Nếu tìm thấy, hàm này sẽ trả về giá trị; nếu không thì nó sẽ xác định là không nên hài hoà màu sắc.

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

Tạo đối tượng ColorRoles hài hoà

retrieveHarmonizedColorRoles là một hàm tiện lợi khác kết hợp tất cả các bước nêu trên: truy xuất giá trị màu cho tài nguyên được đặt tên, cố gắng phân giải thuộc tính boolean để xác định sự hài hoà và trả về một đối tượng ColorRoles bắt nguồn từ màu gốc hoặc màu hỗn hợp (dựa trên bảng phối sáng hoặc tối).

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. Đang điền thẻ đi phương tiện công cộng

Như đã đề cập trước đó, chúng ta sẽ sử dụng RecyclerView và bộ chuyển đổi để điền và tô màu bộ sưu tập thẻ đi phương tiện công cộng.

e4555089b065b5a7.png

Lưu trữ dữ liệu Phương tiện

Để lưu trữ dữ liệu văn bản và thông tin màu cho thẻ đi phương tiện công cộng, chúng ta đang sử dụng một lớp dữ liệu lưu trữ tên, đích đến và mã nhận dạng tài nguyên màu.

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

Chúng ta sẽ sử dụng màu này để tạo các tông màu cần thiết theo thời gian thực.

Bạn có thể hài hoà trong thời gian chạy bằng hàm onBindViewHolder sau đây.

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. Tự động hài hoà màu

Thay vì xử lý quy trình hài hoà theo cách thủ công, bạn có thể yêu cầu xử lý quy trình này cho mình. HarmonizedColorOptions là một lớp trình tạo cho phép bạn chỉ định phần lớn những việc chúng ta đã thực hiện theo cách thủ công.

Sau khi truy xuất ngữ cảnh hiện tại để có quyền truy cập vào lược đồ động hiện tại, bạn cần chỉ định màu cơ sở muốn hài hoà và tạo ngữ cảnh mới dựa trên đối tượng HarmonizedColorOptions đó và ngữ cảnh đã bật DynamicColors.

Nếu bạn không muốn làm hài hoà một màu, chỉ cần đừng đưa màu đó vào HaronizedOptions.

val newContext = DynamicColors.wrapContextIfAvailable(requireContext())


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

harmonizedContext =
 HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)

Khi đã xử lý màu cơ sở hài hoà, bạn có thể cập nhật onBindViewHolder để chỉ gọi MaterialColors.getColorRoles và chỉ định xem các vai trò được trả về sẽ là sáng hay tối.

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. Tự động đồng bộ hoá các thuộc tính giao diện

Các phương thức hiển thị cho đến giờ dựa vào việc truy xuất vai trò của màu sắc từ một màu riêng lẻ. Điều này rất tốt khi thể hiện rằng giọng điệu thực đang được tạo ra nhưng không thực tế đối với hầu hết các ứng dụng hiện tại. Bạn có thể không trực tiếp lấy màu mà thay vào đó hãy sử dụng một thuộc tính giao diện hiện có.

Trong phần trước của lớp học lập trình này, chúng ta đã nói về cách xuất các thuộc tính giao diện.

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

Tương tự như phương thức tự động đầu tiên, chúng ta có thể cung cấp các giá trị cho HarmonizedColorOptions và sử dụng HarmonizedColors để truy xuất Context có màu sắc được hài hoà. Có một điểm khác biệt chính giữa hai phương pháp. Chúng ta cũng cần cung cấp lớp phủ giao diện chứa các trường cần hài hoà.

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)

Bộ chuyển đổi sẽ sử dụng ngữ cảnh hài hoà. Các giá trị trong lớp phủ giao diện phải đề cập đến biến thể sáng hoặc tối không hài hoà.

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

Bên trong tệp bố cục XML, chúng ta có thể sử dụng các thuộc tính hài hoà đó như bình thường.

<?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. Mã nguồn

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. Ví dụ về giao diện người dùng

Màu sắc tuỳ chỉnh và tuỳ chỉnh mặc định mà không hài hoà

a5a02a72aef30529.png

Màu tuỳ chỉnh hài hoà

4ac88011173d6753.pngS d5084780d2c6b886.png

dd0c8b90eccd8bef.png c51f8a677b22cd54.png

12. Tóm tắt

Trong lớp học lập trình này, bạn đã tìm hiểu:

  • Thông tin cơ bản về thuật toán hài hoà màu
  • Cách tạo vai trò của màu sắc từ một màu đã thấy.
  • Cách hài hoà một cách có chọn lọc một màu trong giao diện người dùng.
  • Cách hài hoà một nhóm thuộc tính trong một giao diện.

Nếu bạn có thắc mắc, cứ thoải mái hỏi chúng tôi bất cứ lúc nào qua @MaterialDesign trên X.

Hãy theo dõi để biết xem nội dung và hướng dẫn về thiết kế trên youtube.com/MaterialDesign