Phong cách và giao diện

Thử cách Compose
Jetpack Compose là bộ công cụ giao diện người dùng được đề xuất cho Android. Tìm hiểu cách xử lý giao diện trong Compose.

Kiểu và giao diện trên Android cho phép bạn tách riêng chi tiết về thiết kế ứng dụng khỏi cấu trúc và hành vi giao diện người dùng, tương tự như biểu định kiểu trong thiết kế web.

Kiểu (style) là một tập hợp các thuộc tính chỉ định giao diện của một View. Kiểu có thể chỉ định các thuộc tính như màu phông chữ, kích thước phông chữ, màu nền và nhiều thuộc tính khác.

Giao diện là một tập hợp các thuộc tính được áp dụng cho toàn bộ ứng dụng, hoạt động hoặc hệ phân cấp khung hiển thị, chứ không chỉ một khung hiển thị riêng lẻ. Khi bạn áp dụng một giao diện, mọi khung hiển thị trong ứng dụng hoặc hoạt động sẽ áp dụng từng thuộc tính của giao diện đó mà nó hỗ trợ. Giao diện cũng có thể áp dụng kiểu cho các thành phần không phải chế độ xem, chẳng hạn như thanh trạng thái và nền cửa sổ.

Kiểu và giao diện được khai báo trong tệp tài nguyên kiểu trong res/values/, thường có tên là styles.xml.

Hình 1. 2 giao diện được áp dụng cho cùng một hoạt động: Theme.AppCompat (bên trái) và Theme.AppCompat.Light (bên phải).

Giao diện và kiểu

Giao diện và kiểu có nhiều điểm tương đồng, nhưng chúng được sử dụng cho các mục đích khác nhau. Giao diện và kiểu có cùng cấu trúc cơ bản: một cặp khoá-giá trị ánh xạ thuộc tính với tài nguyên.

Kiểu chỉ định thuộc tính cho một loại thành phần hiển thị cụ thể. Ví dụ: một kiểu có thể chỉ định các thuộc tính của một nút. Mỗi thuộc tính bạn chỉ định trong một kiểu là thuộc tính bạn có thể đặt trong tệp bố cục. Việc trích xuất tất cả các thuộc tính cho một kiểu giúp bạn dễ dàng sử dụng và duy trì các thuộc tính đó trên nhiều tiện ích.

Giao diện xác định một tập hợp tài nguyên được đặt tên có thể được tham chiếu theo kiểu, bố cục, tiện ích, v.v. Giao diện chỉ định tên ngữ nghĩa, chẳng hạn như colorPrimary, cho tài nguyên Android.

Các kiểu và giao diện sẽ phối hợp với nhau. Ví dụ: bạn có thể có một kiểu chỉ định rằng một phần của nút là colorPrimary và một phần khác là colorSecondary. Định nghĩa thực tế về các màu đó được cung cấp trong giao diện. Khi thiết bị chuyển sang chế độ ban đêm, ứng dụng của bạn có thể chuyển từ giao diện "sáng" sang giao diện "tối", thay đổi giá trị của tất cả các tên tài nguyên đó. Bạn không cần thay đổi kiểu vì kiểu đang sử dụng tên ngữ nghĩa chứ không phải phần định nghĩa màu cụ thể.

Để biết thêm thông tin về cách giao diện và kiểu phối hợp hoạt động, hãy xem bài đăng trên blog Định kiểu cho Android: giao diện và kiểu.

Tạo và áp dụng kiểu

Để tạo một kiểu mới, hãy mở tệp res/values/styles.xml của dự án. Đối với mỗi kiểu bạn muốn tạo, hãy làm theo các bước sau:

  1. Thêm một thành phần <style> có tên giúp nhận dạng chính xác kiểu.
  2. Thêm một thành phần <item> cho mỗi thuộc tính kiểu mà bạn muốn xác định. name trong mỗi mục chỉ định một thuộc tính mà bạn sẽ dùng làm thuộc tính XML trong bố cục của mình. Giá trị trong phần tử <item> là giá trị cho thuộc tính đó.

Ví dụ: giả sử bạn xác định kiểu sau:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="GreenText" parent="TextAppearance.AppCompat">
        <item name="android:textColor">#00FF00</item>
    </style>
</resources>

Bạn có thể áp dụng kiểu cho thành phần hiển thị như sau:

<TextView
    style="@style/GreenText"
    ... />

Mỗi thuộc tính được chỉ định trong kiểu sẽ được áp dụng cho thành phần hiển thị đó nếu thành phần hiển thị đó chấp nhận. Khung hiển thị sẽ bỏ qua mọi thuộc tính mà khung hiển thị không chấp nhận.

Tuy nhiên, thay vì áp dụng một kiểu cho từng khung hiển thị, bạn thường áp dụng kiểu làm giao diện cho toàn bộ ứng dụng, hoạt động hoặc tập hợp khung hiển thị của mình, như mô tả trong một phần khác của hướng dẫn này.

Mở rộng và tuỳ chỉnh kiểu

Khi tạo kiểu của riêng bạn, hãy luôn mở rộng kiểu hiện có từ khung hoặc Thư viện hỗ trợ để duy trì khả năng tương thích với kiểu giao diện người dùng của nền tảng. Để mở rộng một kiểu, hãy chỉ định kiểu bạn muốn mở rộng bằng thuộc tính parent. Sau đó, bạn có thể ghi đè các thuộc tính kiểu kế thừa và thêm thuộc tính mới.

Ví dụ: bạn có thể kế thừa giao diện văn bản mặc định của nền tảng Android và sửa đổi giao diện đó như sau:

<style name="GreenText" parent="@android:style/TextAppearance">
    <item name="android:textColor">#00FF00</item>
</style>

Tuy nhiên, hãy luôn kế thừa các kiểu ứng dụng cốt lõi từ Thư viện hỗ trợ Android. Các kiểu trong Thư viện hỗ trợ cung cấp khả năng tương thích bằng cách tối ưu hoá từng kiểu cho các thuộc tính giao diện người dùng có trong từng phiên bản. Kiểu Thư viện hỗ trợ thường có tên giống với kiểu trên nền tảng, nhưng đi kèm với AppCompat.

Để kế thừa kiểu từ thư viện hoặc dự án của riêng bạn, hãy khai báo tên kiểu gốc không có phần @android:style/ như trong ví dụ trước. Ví dụ: ví dụ sau kế thừa các kiểu giao diện văn bản từ Thư viện hỗ trợ:

<style name="GreenText" parent="TextAppearance.AppCompat">
    <item name="android:textColor">#00FF00</item>
</style>

Bạn cũng có thể kế thừa các kiểu (ngoại trừ các kiểu trên nền tảng) bằng cách mở rộng tên kiểu bằng ký hiệu dấu chấm, thay vì sử dụng thuộc tính parent. Tức là hãy thêm tiền tố tên kiểu bạn muốn kế thừa vào tên kiểu của bạn, phân tách bằng dấu chấm. Bạn thường chỉ làm việc này khi mở rộng kiểu của riêng mình, chứ không phải kiểu từ các thư viện khác. Ví dụ: kiểu sau kế thừa tất cả kiểu của GreenText trong ví dụ trước, sau đó tăng kích thước văn bản:

<style name="GreenText.Large">
    <item name="android:textSize">22dp</item>
</style>

Bạn có thể tiếp tục kế thừa các kiểu như thế này bao nhiêu lần tuỳ thích bằng cách liên kết thêm nhiều tên.

Để tìm những thuộc tính bạn có thể khai báo bằng thẻ <item>, hãy tham khảo bảng "Thuộc tính XML" trong nhiều mã tham chiếu lớp. Tất cả thành phần hiển thị đều hỗ trợ thuộc tính XML từ lớp View cơ sở và nhiều thành phần hiển thị thêm các thuộc tính đặc biệt riêng. Ví dụ: thuộc tính XML TextView bao gồm thuộc tính android:inputType mà bạn có thể áp dụng cho thành phần hiển thị văn bản nhận dữ liệu đầu vào, chẳng hạn như tiện ích EditText.

Áp dụng kiểu dưới dạng giao diện

Bạn có thể tạo một giao diện giống như cách bạn tạo kiểu. Điểm khác biệt là cách bạn áp dụng: thay vì áp dụng một kiểu bằng thuộc tính style trên khung hiển thị, bạn áp dụng một giao diện có thuộc tính android:theme trên thẻ <application> hoặc thẻ <activity> trong tệp AndroidManifest.xml.

Ví dụ: dưới đây là cách áp dụng giao diện "tối" trong Material Design của Thư viện hỗ trợ Android cho toàn bộ ứng dụng:

<manifest ... >
    <application android:theme="@style/Theme.AppCompat" ... >
    </application>
</manifest>

Và sau đây là cách áp dụng giao diện "sáng" chỉ cho một hoạt động:

<manifest ... >
    <application ... >
        <activity android:theme="@style/Theme.AppCompat.Light" ... >
        </activity>
    </application>
</manifest>

Mỗi khung hiển thị trong ứng dụng hoặc hoạt động đều áp dụng các kiểu mà khung hiển thị hỗ trợ từ các kiểu được xác định trong giao diện nhất định. Nếu chỉ hỗ trợ một số thuộc tính được khai báo trong kiểu, thì khung hiển thị sẽ chỉ áp dụng các thuộc tính đó và bỏ qua các thuộc tính không được hỗ trợ.

Kể từ Android 5.0 (API cấp 21) và Thư viện hỗ trợ Android phiên bản 22.1, bạn cũng có thể chỉ định thuộc tính android:theme cho một thành phần hiển thị trong tệp bố cục. Thao tác này sẽ sửa đổi giao diện của khung hiển thị đó và mọi khung hiển thị con. Điều này rất hữu ích khi thay đổi bảng màu của giao diện trong một phần cụ thể trên giao diện của bạn.

Các ví dụ trước cho thấy cách áp dụng một giao diện, chẳng hạn như Theme.AppCompat do Thư viện hỗ trợ Android cung cấp. Tuy nhiên, thông thường, bạn nên tuỳ chỉnh giao diện cho phù hợp với thương hiệu của ứng dụng. Cách tốt nhất để làm như vậy là mở rộng các kiểu này trong Thư viện hỗ trợ và ghi đè một số thuộc tính như mô tả trong phần sau.

Hệ thống phân cấp kiểu

Android cung cấp nhiều cách để đặt thuộc tính trên toàn bộ ứng dụng Android. Ví dụ: bạn có thể đặt thuộc tính ngay trong một bố cục, áp dụng một kiểu cho một thành phần hiển thị, áp dụng một giao diện cho một bố cục và thậm chí là đặt thuộc tính theo phương thức lập trình.

Khi chọn cách định kiểu cho ứng dụng, hãy lưu ý đến hệ thống phân cấp kiểu của Android. Nhìn chung, hãy sử dụng giao diện và kiểu càng nhiều càng tốt để đảm bảo tính nhất quán. Nếu bạn chỉ định các thuộc tính giống nhau ở nhiều vị trí, thì danh sách sau đây sẽ xác định thuộc tính nào cuối cùng cũng được áp dụng. Danh sách được sắp xếp theo thứ tự từ mức độ ưu tiên cao nhất đến thấp nhất.

  1. Áp dụng kiểu ở cấp ký tự hoặc đoạn văn bản bằng cách sử dụng span văn bản cho các lớp bắt nguồn từ TextView.
  2. Áp dụng các thuộc tính theo phương thức lập trình.
  3. Áp dụng trực tiếp các thuộc tính riêng lẻ cho một khung hiển thị.
  4. Áp dụng kiểu cho thành phần hiển thị.
  5. Kiểu mặc định.
  6. Áp dụng giao diện cho một tập hợp thành phần hiển thị, một hoạt động hoặc toàn bộ ứng dụng.
  7. Áp dụng một số kiểu dành riêng cho thành phần hiển thị, chẳng hạn như đặt TextAppearance trên TextView.

Hình 2. Kiểu trên span ghi đè kiểu trên textAppearance.

TextAppearance

Một hạn chế về kiểu là bạn chỉ có thể áp dụng một kiểu cho mỗi View. Tuy nhiên, trong TextView, bạn cũng có thể chỉ định thuộc tính TextAppearance hoạt động tương tự như một kiểu, như minh hoạ trong ví dụ sau:

<TextView
    ...
    android:textAppearance="@android:style/TextAppearance.Material.Headline"
    android:text="This text is styled via textAppearance!" />

TextAppearance cho phép bạn xác định kiểu cụ thể cho văn bản trong khi vẫn giữ nguyên kiểu của View dành cho các mục đích sử dụng khác. Tuy nhiên, lưu ý rằng nếu bạn xác định bất kỳ thuộc tính văn bản nào trực tiếp trên View hoặc theo một kiểu, thì các giá trị đó sẽ ghi đè giá trị TextAppearance.

TextAppearance hỗ trợ một số thuộc tính định kiểu mà TextView cung cấp. Để xem danh sách thuộc tính đầy đủ, hãy xem TextAppearance.

Có một số thuộc tính TextView phổ biến không được bao gồm, đó là lineHeight[Multiplier|Extra], lines, breakStrategyhyphenationFrequency. TextAppearance hoạt động ở cấp ký tự chứ không phải cấp đoạn văn bản, vì vậy, các thuộc tính ảnh hưởng đến toàn bộ bố cục sẽ không được hỗ trợ.

Tuỳ chỉnh giao diện mặc định

Khi bạn tạo một dự án bằng Android Studio, Android Studio sẽ áp dụng giao diện Material Design cho ứng dụng của bạn theo mặc định, như xác định trong tệp styles.xml của dự án. Kiểu AppTheme này mở rộng giao diện từ Thư viện hỗ trợ và bao gồm cơ chế ghi đè cho các thuộc tính màu sắc mà các thành phần chính trên giao diện người dùng sử dụng, chẳng hạn như thanh ứng dụngnút hành động nổi, nếu được sử dụng. Vì vậy, bạn có thể nhanh chóng tuỳ chỉnh thiết kế màu của ứng dụng bằng cách cập nhật các màu được cung cấp.

Ví dụ: tệp styles.xml của bạn có dạng như sau:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

Thực ra thì các giá trị kiểu tham chiếu đến các tài nguyên màu khác, được xác định trong tệp res/values/colors.xml của dự án. Đó là tệp mà bạn chỉnh sửa để thay đổi màu. Xem Tổng quan về màu sắc trong Material Design để cải thiện trải nghiệm người dùng bằng màu động và các màu tuỳ chỉnh khác.

Sau khi bạn chọn xong màu, hãy cập nhật giá trị trong res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--   Color for the app bar and other primary UI elements. -->
    <color name="colorPrimary">#3F51B5</color>

    <!--   A darker variant of the primary color, used for
           the status bar (on Android 5.0+) and contextual app bars. -->
    <color name="colorPrimaryDark">#303F9F</color>

    <!--   a secondary color for controls like checkboxes and text fields. -->
    <color name="colorAccent">#FF4081</color>
</resources>

Sau đó, bạn có thể ghi đè bất kỳ kiểu nào khác bạn muốn. Ví dụ: bạn có thể thay đổi màu nền của hoạt động như sau:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    ...
    <item name="android:windowBackground">@color/activityBackground</item>
</style>

Để biết danh sách các thuộc tính mà bạn có thể sử dụng trong giao diện, hãy xem bảng thuộc tính tại R.styleable.Theme. Khi thêm kiểu cho các thành phần hiển thị trong bố cục, bạn cũng có thể tìm thấy các thuộc tính bằng cách xem bảng "Thuộc tính XML" trong thông tin tham chiếu đến lớp thành phần hiển thị. Ví dụ: tất cả thành phần hiển thị đều hỗ trợ các thuộc tính XML từ lớp View cơ sở.

Hầu hết thuộc tính được áp dụng cho một số loại thành phần hiển thị cụ thể, còn một số khác áp dụng cho tất cả thành phần hiển thị. Tuy nhiên, một số thuộc tính giao diện liệt kê tại R.styleable.Theme áp dụng cho cửa sổ hoạt động, không áp dụng cho thành phần hiển thị trong bố cục. Ví dụ: windowBackground thay đổi nền cửa sổ và windowEnterTransition xác định một ảnh động chuyển đổi để sử dụng khi hoạt động bắt đầu. Để biết thêm thông tin, hãy xem phần Bắt đầu một hoạt động bằng ảnh động.

Thư viện hỗ trợ Android cũng cung cấp các thuộc tính khác mà bạn có thể dùng để tuỳ chỉnh giao diện mở rộng từ Theme.AppCompat, chẳng hạn như thuộc tính colorPrimary hiển thị trong ví dụ trước. Bạn nên xem các tài nguyên này tốt nhất trong tệp attrs.xml của thư viện.

Ngoài ra, còn có nhiều giao diện có sẵn trong Thư viện hỗ trợ mà bạn có thể muốn mở rộng thay cho các giao diện hiển thị trong ví dụ trước. Vị trí tốt nhất để xem các giao diện có sẵn là tệp themes.xml của thư viện.

Thêm kiểu cho phiên bản cụ thể

Nếu phiên bản Android mới thêm các thuộc tính giao diện mà bạn muốn sử dụng, thì bạn có thể thêm các thuộc tính đó vào giao diện trong khi vẫn tương thích với các phiên bản cũ. Bạn chỉ cần một tệp styles.xml khác được lưu trong thư mục values có chứa bộ hạn định phiên bản tài nguyên:

res/values/styles.xml        # themes for all versions
res/values-v21/styles.xml    # themes for API level 21+ only

Vì các kiểu trong tệp values/styles.xml có sẵn cho mọi phiên bản nên giao diện của bạn trong values-v21/styles.xml có thể kế thừa các kiểu đó. Tức là bạn có thể tránh tình trạng trùng lặp kiểu bằng cách bắt đầu bằng giao diện "cơ sở", sau đó mở rộng theo kiểu dành riêng cho phiên bản.

Ví dụ: để khai báo hiệu ứng chuyển đổi cửa sổ cho Android 5.0 (API cấp 21) trở lên, bạn cần sử dụng các thuộc tính mới. Vì vậy, giao diện cơ sở của bạn trong res/values/styles.xml có thể có dạng như sau:

<resources>
    <!-- Base set of styles that apply to all versions. -->
    <style name="BaseAppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/primaryColor</item>
        <item name="colorPrimaryDark">@color/primaryTextColor</item>
        <item name="colorAccent">@color/secondaryColor</item>
    </style>

    <!-- Declare the theme name that's actually applied in the manifest file. -->
    <style name="AppTheme" parent="BaseAppTheme" />
</resources>

Sau đó, hãy thêm các kiểu dành riêng cho phiên bản trong res/values-v21/styles.xml như sau:

<resources>
    <!-- extend the base theme to add styles available only with API level 21+ -->
    <style name="AppTheme" parent="BaseAppTheme">
        <item name="android:windowActivityTransitions">true</item>
        <item name="android:windowEnterTransition">@android:transition/slide_right</item>
        <item name="android:windowExitTransition">@android:transition/slide_left</item>
    </style>
</resources>

Bây giờ, bạn có thể áp dụng AppTheme trong tệp kê khai và hệ thống sẽ chọn các kiểu có sẵn cho mỗi phiên bản hệ thống.

Để biết thêm thông tin về cách sử dụng tài nguyên thay thế cho các thiết bị khác nhau, hãy xem phần Cung cấp tài nguyên thay thế.

Tuỳ chỉnh kiểu tiện ích

Mỗi tiện ích trong khung và Thư viện hỗ trợ đều có một kiểu mặc định. Ví dụ: khi bạn định kiểu cho ứng dụng bằng một giao diện trong Thư viện hỗ trợ, một thực thể của Button sẽ được định kiểu bằng kiểu Widget.AppCompat.Button. Nếu muốn áp dụng kiểu tiện ích khác cho một nút, bạn có thể thực hiện bằng thuộc tính style trong tệp bố cục. Ví dụ: sau đây áp dụng kiểu nút không đường viền của thư viện:

<Button
    style="@style/Widget.AppCompat.Button.Borderless"
    ... />

Nếu muốn áp dụng kiểu này cho mọi nút, bạn có thể khai báo kiểu này trong buttonStyle của giao diện như sau:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="buttonStyle">@style/Widget.AppCompat.Button.Borderless</item>
    ...
</style>

Bạn cũng có thể mở rộng kiểu tiện ích, giống như khi mở rộng bất kỳ kiểu nào khác, sau đó áp dụng kiểu tiện ích tuỳ chỉnh trong bố cục hoặc giao diện.

Tài nguyên khác

Để tìm hiểu thêm về giao diện và kiểu, hãy xem thêm các tài nguyên sau:

Bài đăng trên blog