Xử lý nhiều hình dạng đồng hồ

Stay organized with collections Save and categorize content based on your preferences.

Lớp phủ trên Wear OS sử dụng kỹ thuật bố cục giống như các thiết bị Android khác, nhưng cần được thiết kế với các hạn chế riêng cho đồng hồ.

Lưu ý: Đừng chuyển chức năng và giao diện người dùng nguyên bản từ ứng dụng dành cho thiết bị di động sang Wear OS rồi mong đợi mang lại trải nghiệm tốt cho người dùng.

Nếu bạn thiết kế ứng dụng cho đồng hồ có màn hình vuông, thì nội dung gần các góc của màn hình có thể bị cắt trên đồng hồ tròn. Nếu bạn đang sử dụng một danh sách dọc có thể cuộn, thì vấn đề này không đáng kể vì người dùng có thể cuộn để căn giữa nội dung. Tuy nhiên, đối với màn hình đơn lẻ, tính năng này có thể đem lại trải nghiệm không tốt cho người dùng.

Ví dụ: hình 1 cho thấy bố cục sau đây trên màn hình vuông và màn hình tròn:

Hình 1. Ví dụ cho thấy bố cục được thiết kế cho màn hình vuông có thể không ổn lắm trên màn hình tròn.

Nếu bạn sử dụng các chế độ cài đặt sau cho bố cục của mình, văn bản sẽ hiển thị không đúng cách trên các thiết bị có màn hình tròn:

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="@string/very_long_hello_world"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Có hai phương pháp để giải quyết vấn đề này:

  • Sử dụng các bố cục trong Thư viện giao diện người dùng Wear OS cho cả thiết bị hình vuông và hình tròn.
    • BoxInsetLayout: Bố cục này áp dụng các phần lồng ghép cửa sổ khác nhau tuỳ thuộc vào hình dạng của màn hình thiết bị. Hãy sử dụng phương pháp này khi bạn muốn sử dụng bố cục tương tự trên cả hai hình dạng màn hình mà không cần phải cắt các khung hiển thị gần cạnh của màn hình tròn.
    • Bố cục cong: Sử dụng bố cục này khi bạn muốn hiển thị và thao tác trên danh sách các mục dọc được tối ưu hoá cho màn hình tròn.
  • Cung cấp tài nguyên bố cục thay thế cho các thiết bị hình vuông và hình tròn như được mô tả trong hướng dẫn Cung cấp tài nguyên thay thế. Trong thời gian chạy, Wear OS sẽ phát hiện hình dạng của màn hình thiết bị và tải bố cục chính xác.

Để biết thêm thông tin về cách thiết kế lớp phủ, hãy đọc phần Nguyên tắc thiết kế Wear OS.

Sử dụng BoxInsetLayout

Hình 2. Phần lồng ghép cửa sổ trên màn hình tròn.

Lớp BoxInsetLayout trong Thư viện giao diện người dùng Wear OS cho phép bạn xác định một bố cục phù hợp với cả màn hình vuông và màn hình tròn. Lớp này áp dụng các phần lồng ghép cửa sổ bắt buộc tuỳ thuộc vào hình dạng màn hình và cho phép bạn dễ dàng căn chỉnh khung hiển thị ở chính giữa hoặc gần các cạnh của màn hình.

Hình vuông màu xám trong hình 2 cho thấy khu vực mà BoxInsetLayout có thể tự động đặt các khung hiển thị con trên màn hình tròn sau khi áp dụng các phần lồng ghép cửa sổ bắt buộc. Để được hiển thị trong khu vực này, các khung hiển thị con chỉ định thuộc tính layout_boxedEdges bằng các giá trị sau:

  • Tổ hợp top, bottom, leftright. Ví dụ: Giá trị "left|top" định vị các cạnh bên trái và trên cùng của thành phần con trong hình vuông màu xám ở hình 2.
  • Giá trị "all" định vị tất cả nội dung của thành phần con trong hình vuông màu xám ở hình 2. Đây là phương pháp phổ biến nhất cùng với một ConstraintLayout bên trong.

Trên màn hình vuông, các phần lồng ghép cửa sổ bằng 0 và thuộc tính layout_boxedEdges bị bỏ qua.

Hình 3. Định nghĩa bố cục hoạt động trên cả màn hình vuông và màn hình tròn.

Bố cục như trong hình 3 sử dụng phần tử <BoxInsetLayout> và hoạt động trên màn hình vuông và màn hình tròn:

<androidx.wear.widget.BoxInsetLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:padding="15dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dp"
        app:layout_boxedEdges="all">

        <TextView
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:text="@string/sometext"
            android:textAlignment="center"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <ImageButton
            android:background="@android:color/transparent"
            android:layout_height="50dp"
            android:layout_width="50dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:src="@drawable/cancel" />

        <ImageButton
            android:background="@android:color/transparent"
            android:layout_height="50dp"
            android:layout_width="50dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:src="@drawable/ok" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.wear.widget.BoxInsetLayout>

Lưu ý các phần của bố cục được đánh dấu đậm:

  • android:padding="15dp"

    Dòng này gán khoảng đệm cho phần tử <BoxInsetLayout>.

  • android:padding="5dp"

    Dòng này gán khoảng đệm cho phần tử ConstraintLayout bên trong.

  • app:layout_boxedEdges="all"

    Dòng này đảm bảo phần tử ConstraintLayout và các phần tử con của phần tử đó được đóng gói bên trong khu vực xác định bằng các phần lồng ghép cửa sổ trên màn hình tròn. Dòng này không ảnh hưởng đến màn hình vuông.

Sử dụng bố cục cong

Lớp WearableRecyclerView trong Thư viện giao diện người dùng Wear OS cho phép bạn chọn sử dụng bố cục cong, được tối ưu hoá cho màn hình tròn. Để bật bố cục cong cho danh sách dạng cuộn trong ứng dụng của bạn, hãy xem phần Tạo danh sách trên Wear OS.

Sử dụng các bố cục khác nhau cho màn hình vuông và màn hình tròn

Thiết bị Wear OS có thể có màn hình vuông hoặc tròn. Ứng dụng của bạn phải có khả năng hỗ trợ cả hai cấu hình của thiết bị. Để làm điều này, bạn nên cung cấp các tài nguyên thay thế. Đặt bộ hạn định tài nguyên thành round hoặc notround trên bố cục, thứ nguyên hoặc các loại tài nguyên khác.

Chẳng hạn hãy xem xét việc sắp xếp các bố cục như sau:

  • Thư mục layout/ chứa các bố cục phù hợp với cả đồng hồ có màn hình tròn và màn hình vuông.
  • Các thư mục layout-round/layout-notround/ chứa bố cục dành riêng cho hình dạng màn hình.

Bạn cũng có thể sử dụng các thư mục tài nguyên res/values, res/values-roundres/values-notround. Bằng việc sắp xếp tài nguyên theo cách này, bạn có thể chia sẻ một bố cục đơn trong khi chỉ thay đổi các thuộc tính cụ thể dựa trên loại thiết bị.

Thay đổi các giá trị

Một cách dễ dàng để tạo đồng hồ có màn hình tròn và màn hình vuông là sử dụng values/dimens.xmlvalues-round/dimens.xml. Bằng cách chỉ định nhiều tuỳ chọn cài đặt khoảng đệm, bạn có thể tạo bố cục sau bằng một tệp layout.xml và 2 tệp dimens.xml:

<dimen name="header_start_padding">36dp</dimen>
<dimen name="header_end_padding">22dp</dimen>
<dimen name="list_start_padding">36dp</dimen>
<dimen name="list_end_padding">22dp</dimen>
Sử dụng values-round/dimens.xml

Hình 4. Sử dụng values-round/dimens.xml.

<dimen name="header_start_padding">16dp</dimen>
<dimen name="header_end_padding">16dp</dimen>
<dimen name="list_start_padding">10dp</dimen>
<dimen name="list_end_padding">10dp</dimen>
Sử dụng values/dimens.xml

Hình 5. Sử dụng values/dimens.xml.

Bạn nên thử nghiệm nhiều giá trị để xem giá trị nào hiệu quả nhất.

Sử dụng XML để bù cho phần cằm (phần bo tròn ở góc dưới màn hình)

Một số đồng hồ có phần lồng ghép (còn được gọi là “chin” ("cằm")) trong một màn hình tròn khác. Nếu bạn không chỉnh sửa phần cằm, thì một số thiết kế của bạn có thể bị phần này che khuất.

Ví dụ: bạn có thể có thiết kế như sau:

Kiểu thiết kế trái tim cơ bản

Hình 6. Kiểu thiết kế trái tim cơ bản.

Đoạn mã activity_main.xml này xác định bố cục của nó.

<FrameLayout
  ...
  <androidx.wear.widget.RoundedDrawable
    android:id="@+id/androidbtn"
    android:src="@drawable/ic_android"
    .../>
   <ImageButton
    android:id="@+id/lovebtn"
    android:src="@drawable/ic_favourite"
    android:paddingTop="5dp"
    android:paddingBottom="5dp"
    android:layout_gravity="bottom"
    .../>
</FrameLayout>

Nếu bạn không xử lý mã, một phần của thiết kế sẽ bị phần cằm che khuất.

Kiểu thiết kế trái tim cơ bản

Hình 7. Chưa áp dụng bản sửa lỗi nào.

Bạn có thể sử dụng thuộc tính fitsSystemWindows để đặt khoảng đệm nhằm tránh phần cằm. Đoạn mã activity_main.xml sau đây cho thấy cách sử dụng fitsSystemWindows:

<ImageButton
  android:id="@+id/lovebtn"
  android:src="@drawable/ic_favourite"
  android:paddingTop="5dp"
  android:paddingBottom="5dp"
  android:fitsSystemWindows="true"
  .../>
Sử dụng fitsSystemWindows

Hình 8. Sử dụng thuộc tính fitsSystemWindows.

Xin lưu ý rằng các giá trị khoảng đệm trên cùng và dưới cùng mà bạn đã xác định sẽ được ghi đè để làm cho mọi thứ vừa với cửa sổ hệ thống. Cách khắc phục là thay thế các giá trị khoảng đệm bằng InsetDrawable.

Tạo tệp inset_favourite.xml để xác định các giá trị khoảng đệm:

<inset
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:drawable="@drawable/ic_favourite"
  android:insetTop="5dp"
  android:insetBottom="5dp" />

Xoá khoảng đệm khỏi activity_main.xml:

<ImageButton
  android:id="@+id/lovebtn"
  android:src="@drawable/inset_favourite"
  android:paddingTop="5dp"
  android:paddingBottom="5dp"
  android:fitsSystemWindows="true"
  .../>
Sử dụng InsetDrawables

Hình 9. Sử dụng InsetDrawables.

Xử lý phần cằm theo phương thức lập trình

Nếu cần có nhiều quyền kiểm soát hơn so với khả năng khai báo bằng XML, bạn có thể điều chỉnh bố cục của mình theo phương thức lập trình. Để có được kích thước của cằm, hãy gắn View.OnApplyWindowInsetsListener vào chế độ xem ngoài cùng của bố cục.

Thêm mã sau vào tệp hoạt động chính:

Kotlin

private var chinSize: Int = 0

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // find the outermost element
    findViewById<View>(R.id.outer_container).apply {
        // attach a View.OnApplyWindowInsetsListener
        setOnApplyWindowInsetsListener { v, insets ->
            chinSize = insets.systemWindowInsetBottom
            // The following line is important for inner elements which react to insets
            v.onApplyWindowInsets(insets)
            insets
        }
    }
}

Java

private int chinSize;
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // find the outermost element
    final View container = findViewById(R.id.outer_container);
    // attach a View.OnApplyWindowInsetsListener
    container.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
        @Override
        public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
            chinSize = insets.getSystemWindowInsetBottom();
            // The following line is important for inner elements which react to insets
            v.onApplyWindowInsets(insets);
            return insets;
        }
    });
}