Cung cấp bố cục tiện ích linh hoạt

Trang này mô tả các tinh chỉnh để cải thiện kích thước tiện ích và tăng tính linh hoạt ra mắt trong Android 12 (API cấp 31). Bài viết này cũng trình bày chi tiết cách xác định kích thước cho tiện ích của bạn.

Dùng API cải tiến đối với kích thước và bố cục tiện ích

Kể từ Android 12 (API cấp 31), bạn có thể cung cấp thêm kích thước đã tinh chỉnh và bố cục linh hoạt bằng cách làm theo các bước sau, như mô tả trong tiếp theo:

  1. Chỉ định các quy tắc ràng buộc khác đối với kích thước tiện ích.

  2. Cung cấp bố cục thích ứng hoặc chính xác bố cục.

Trong các phiên bản Android trước đây, bạn có thể lấy phạm vi kích thước của bằng cách sử dụng OPTION_APPWIDGET_MIN_WIDTH! OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH, và OPTION_APPWIDGET_MAX_HEIGHT dữ liệu bổ sung rồi ước tính kích thước tiện ích, nhưng logic đó không hoạt động trong tất cả các trường hợp ngoại lệ. Đối với các tiện ích nhắm mục tiêu đến Android 12 trở lên, bạn nên cung cấp đáp ứng hoặc chính xác bố cục.

Chỉ định các giới hạn khác về kích thước tiện ích

Android 12 bổ sung các API để bạn đảm bảo tiện ích của mình có kích thước đáng tin cậy hơn trên các thiết bị khác nhau có nhiều kích thước màn hình.

Ngoài minWidth hiện có, minHeight, minResizeWidth, và minResizeHeight hãy sử dụng các thuộc tính appwidget-provider mới sau đây:

  • targetCellWidthtargetCellHeight: xác định kích thước mục tiêu của tiện ích dựa trên các ô lưới của trình chạy. Nếu đã xác định, các thuộc tính này sẽ được sử dụng thay cho minWidth hoặc minHeight.

  • maxResizeWidthmaxResizeHeight: xác định kích thước tối đa mà trình chạy cho phép người dùng đổi kích thước tiện ích.

XML sau đây cho biết cách sử dụng các thuộc tính kích thước.

<appwidget-provider
  ...
  android:targetCellWidth="3"
  android:targetCellHeight="2"
  android:maxResizeWidth="250dp"
  android:maxResizeHeight="110dp">
</appwidget-provider>

Cung cấp bố cục thích ứng

Nếu bố cục cần thay đổi tuỳ thuộc vào kích thước của tiện ích, bạn nên tạo một tập hợp nhỏ các bố cục, mỗi bố cục hợp lệ cho nhiều kích thước. Nếu trường hợp này không thể, có một cách khác là cung cấp bố cục dựa trên tiện ích chính xác trong thời gian chạy, như được mô tả trong trang này.

Tính năng này giúp mở rộng quy mô mượt mà hơn và cải thiện hệ thống tổng thể sức khoẻ, vì hệ thống không phải đánh thức ứng dụng mỗi lần nó sẽ hiển thị tiện ích đó ở một kích thước khác.

Ví dụ về mã sau đây cho thấy cách cung cấp danh sách bố cục.

Kotlin

override fun onUpdate(...) {
    val smallView = ...
    val tallView = ...
    val wideView = ...

    val viewMapping: Map<SizeF, RemoteViews> = mapOf(
            SizeF(150f, 100f) to smallView,
            SizeF(150f, 200f) to tallView,
            SizeF(215f, 100f) to wideView
    )
    val remoteViews = RemoteViews(viewMapping)

    appWidgetManager.updateAppWidget(id, remoteViews)
}

Java

@Override
public void onUpdate(...) {
    RemoteViews smallView = ...;
    RemoteViews tallView = ...;
    RemoteViews wideView = ...;

    Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
    viewMapping.put(new SizeF(150f, 100f), smallView);
    viewMapping.put(new SizeF(150f, 200f), tallView);
    viewMapping.put(new SizeF(215f, 100f), wideView);
    RemoteViews remoteViews = new RemoteViews(viewMapping);

    appWidgetManager.updateAppWidget(id, remoteViews);
}

Giả sử tiện ích có các thuộc tính sau:

<appwidget-provider
    android:minResizeWidth="160dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="200dp">
</appwidget-provider>

Đoạn mã trên có nghĩa như sau:

  • smallView hỗ trợ từ 160 dp (minResizeWidth) × 110 dp (minResizeHeight) thành 160 dp × 199 dp (điểm cắt tiếp theo – 1 dp).
  • tallView hỗ trợ từ 160 dp × 200 dp đến 214 dp (điểm cắt tiếp theo – 1) × 200dp.
  • wideView hỗ trợ từ 215 dp × 110 dp (minResizeHeight) đến 250 dp (maxResizeWidth) × 200dp (maxResizeHeight).

Tiện ích của bạn phải hỗ trợ kích thước trong khoảng từ minResizeWidth × minResizeHeight đến maxResizeWidth × maxResizeHeight. Trong phạm vi đó, bạn có thể quyết định điểm cắt để chuyển đổi bố cục.

Ví dụ về bố cục thích ứng
Hình 1. Ví dụ về bố cục thích ứng.

Cung cấp bố cục chính xác

Nếu không thể sử dụng một tập hợp nhỏ các bố cục thích ứng, bạn có thể cung cấp các bố cục khác nhau được điều chỉnh cho phù hợp với kích thước hiển thị tiện ích. Đây là thường có 2 kích thước cho điện thoại (chế độ dọc và ngang) và 4 kích thước cho thiết bị có thể gập lại.

Để triển khai giải pháp này, ứng dụng của bạn cần thực hiện các bước sau:

  1. Quá tải AppWidgetProvider.onAppWidgetOptionsChanged(), được gọi khi tập hợp kích thước thay đổi.

  2. Gọi AppWidgetManager.getAppWidgetOptions(), Phương thức này sẽ trả về Bundle chứa các kích thước.

  3. Truy cập vào khoá AppWidgetManager.OPTION_APPWIDGET_SIZES trên Bundle.

Ví dụ về mã sau đây cho thấy cách cung cấp bố cục chính xác.

Kotlin

override fun onAppWidgetOptionsChanged(
        context: Context,
        appWidgetManager: AppWidgetManager,
        id: Int,
        newOptions: Bundle?
) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, id, newOptions)
    // Get the new sizes.
    val sizes = newOptions?.getParcelableArrayList<SizeF>(
            AppWidgetManager.OPTION_APPWIDGET_SIZES
    )
    // Check that the list of sizes is provided by the launcher.
    if (sizes.isNullOrEmpty()) {
        return
    }
    // Map the sizes to the RemoteViews that you want.
    val remoteViews = RemoteViews(sizes.associateWith(::createRemoteViews))
    appWidgetManager.updateAppWidget(id, remoteViews)
}

// Create the RemoteViews for the given size.
private fun createRemoteViews(size: SizeF): RemoteViews { }

Java

@Override
public void onAppWidgetOptionsChanged(
    Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    // Get the new sizes.
    ArrayList<SizeF> sizes =
        newOptions.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES);
    // Check that the list of sizes is provided by the launcher.
    if (sizes == null || sizes.isEmpty()) {
      return;
    }
    // Map the sizes to the RemoteViews that you want.
    Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
    for (SizeF size : sizes) {
        viewMapping.put(size, createRemoteViews(size));
    }
    RemoteViews remoteViews = new RemoteViews(viewMapping);
    appWidgetManager.updateAppWidget(id, remoteViews);
}

// Create the RemoteViews for the given size.
private RemoteViews createRemoteViews(SizeF size) { }

Xác định kích thước cho tiện ích của bạn

Mỗi tiện ích phải xác định một targetCellWidthtargetCellHeight cho các thiết bị chạy Android 12 trở lên — hoặc minWidthminHeight cho tất cả thiết bị phiên bản Android—cho biết mức dung lượng tối thiểu mà ứng dụng đã sử dụng theo mặc định. Tuy nhiên, khi người dùng thêm một tiện ích vào màn hình chính, việc này thường chiếm nhiều hơn chiều rộng và chiều cao tối thiểu mà bạn chỉ định.

Màn hình chính của Android cung cấp cho người dùng một lưới không gian có sẵn để họ có thể xem biểu tượng và tiện ích vị trí. Lưới này có thể khác nhau tùy theo thiết bị; ví dụ: nhiều điện thoại di động có lưới 5x4 còn máy tính bảng có thể cung cấp lưới lớn hơn. Khi tiện ích của bạn sẽ được kéo dài để chiếm số lượng ô tối thiểu, theo chiều ngang và chiều dọc, được yêu cầu nhằm đáp ứng các hạn chế đối với targetCellWidthtargetCellHeight trên các thiết bị đang chạy Android 12 trở lên hoặc các quy tắc ràng buộc đối với minWidthminHeight đang bật thiết bị chạy Android 11 (API cấp 30) trở xuống.

Đã áp dụng cả chiều rộng và chiều cao của ô cũng như kích thước của lề tự động vào các tiện ích có thể khác nhau tuỳ theo thiết bị. Sử dụng bảng sau để ước tính ước tính kích thước tối thiểu của tiện ích của bạn trong điện thoại di động có lưới 5x4 thông thường, dựa trên số lượng ô lưới bị chiếm dụng mà bạn muốn:

Số ô (chiều rộng x chiều cao) Kích thước có sẵn ở chế độ dọc (dp) Kích thước có sẵn ở chế độ ngang (dp)
1x1 57x102dp 127x51dp
2x1 130x102dp 269x51dp
3x1 203x102dp 412x51dp
4x1 276x102dp 554x51dp
5x1 349x102dp 697x51dp
5x2 349x220dp 697x117dp
5x3 349x337dp 697x184dp
5x4 349x455dp 697x250dp
... ... ...
n x m (73n – 16) x (118m – 16) (142n – 15) x (66m – 15)

Dùng kích thước ô ở chế độ dọc để cho biết các giá trị mà bạn cung cấp các thuộc tính minWidth, minResizeWidthmaxResizeWidth. Tương tự, sử dụng kích thước ô ở chế độ ngang để cho biết các giá trị mà bạn cung cấp cho các thuộc tính minHeight, minResizeHeightmaxResizeHeight.

Nguyên nhân là do chiều rộng ô thường nhỏ hơn ở chế độ dọc hơn so với ở chế độ ngang—và tương tự, chiều cao ô thường bằng nhỏ hơn ở chế độ ngang so với ở chế độ dọc.

Ví dụ: Nếu bạn muốn chiều rộng tiện ích có thể đổi kích thước thành một ô trên Google Pixel 4, bạn cần đặt minResizeWidth thành tối đa 56 dp để đảm bảo giá trị của thuộc tính minResizeWidth nhỏ hơn lớn hơn 57 dp – vì ô có chiều dọc ít nhất là 57 dp. Tương tự, nếu bạn muốn chiều cao của tiện ích có thể đổi kích thước trong một ô trên cùng một thiết bị, bạn cần đặt minResizeHeight ở mức tối đa là 50dp để đảm bảo giá trị của thuộc tính minResizeHeight nhỏ hơn 51 dp – vì một ô có chiều cao tối thiểu là 51 dp ở chế độ ngang.

Mỗi tiện ích đều có thể đổi kích thước trong phạm vi kích thước giữa minResizeWidth/minResizeHeightmaxResizeWidth/maxResizeHeight tức là giá trị này cần phải thích ứng với mọi phạm vi kích thước giữa chúng.

Ví dụ: để đặt kích thước mặc định của tiện ích tại vị trí, bạn có thể thiết lập các thuộc tính sau:

<appwidget-provider
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:minWidth="180dp"
    android:minHeight="110dp">
</appwidget-provider>

Điều này có nghĩa là kích thước mặc định của tiện ích là 3x2 ô, như được chỉ định bởi Các thuộc tính targetCellWidthtargetCellHeight – hoặc 180×110dp, dưới dạng do minWidthminHeight chỉ định cho các thiết bị đang chạy Android 11 trở xuống. Trong trường hợp sau, kích thước trong các ô có thể khác nhau tuỳ theo thiết bị.

Ngoài ra, để đặt phạm vi kích thước được hỗ trợ cho tiện ích của mình, bạn có thể đặt các thuộc tính:

<appwidget-provider
    android:minResizeWidth="180dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp">
</appwidget-provider>

Như đã chỉ định bởi các thuộc tính trước, chiều rộng của tiện ích là có thể đổi kích thước từ 180dp thành 530dp và chiều cao có thể thay đổi kích thước từ 110dp thành 450dp. Sau đó, tiện ích này có thể đổi kích thước từ 3x2 thành 5x2 ô, miễn là có các điều kiện sau:

Kotlin

val smallView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_small)
val mediumView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_medium)
val largeView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_large)

val viewMapping: Map<SizeF, RemoteViews> = mapOf(
        SizeF(180f, 110f) to smallView,
        SizeF(270f, 110f) to mediumView,
        SizeF(270f, 280f) to largeView
)

appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))

Java

RemoteViews smallView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_small);
RemoteViews mediumView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_medium);
RemoteViews largeView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_large);

Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
viewMapping.put(new SizeF(180f, 110f), smallView);
viewMapping.put(new SizeF(270f, 110f), mediumView);
viewMapping.put(new SizeF(270f, 280f), largeView);
RemoteViews remoteViews = new RemoteViews(viewMapping);

appWidgetManager.updateAppWidget(id, remoteViews);

Giả sử tiện ích này sử dụng bố cục thích ứng được xác định trong phần trên các đoạn mã. Điều này có nghĩa là bố cục được chỉ định là R.layout.widget_weather_forecast_small được dùng từ 180 dp (minResizeWidth) x 110dp (minResizeHeight) đến 269x279dp (điểm cắt tiếp theo – 1). Tương tự, R.layout.widget_weather_forecast_medium được sử dụng từ 270x110dp đến 270x279dp, và R.layout.widget_weather_forecast_large được dùng từ 270x280dp đến 530dp (maxResizeWidth) x 450dp (maxResizeHeight).

Khi người dùng đổi kích thước tiện ích, giao diện của tiện ích cũng sẽ thay đổi để thích ứng với từng kích thước trong ô, như được minh hoạ trong các ví dụ sau.

Ví dụ về tiện ích thời tiết ở kích thước lưới 3x2 nhỏ nhất. Giao diện người dùng cho thấy
            tên vị trí (Tokyo), nhiệt độ (14°) và biểu tượng cho biết
            trời có mây rải rác.
Hình 2. R.layout.widget_weather_forecast_small 3x2.

Ví dụ về tiện ích thời tiết trong &quot;phương tiện&quot; 4x2 kích thước. Đổi kích thước tiện ích
            cách này dựa trên tất cả giao diện người dùng từ kích thước tiện ích trước đó,
            và thêm nhãn &#39;Nhiều mây&#39; và thông tin dự báo nhiệt độ từ
            4 giờ chiều đến 7 giờ tối.
Hình 3. 4x2 R.layout.widget_weather_forecast_medium.

Ví dụ về tiện ích thời tiết trong &quot;phương tiện&quot; 5x2 kích thước. Đổi kích thước tiện ích
            bằng cách này sẽ dẫn đến giao diện người dùng giống như kích thước trước đó, ngoại trừ việc nó
            được kéo dài thêm một ô để chiếm nhiều không gian theo chiều ngang hơn.
Hình 4. 5x2 R.layout.widget_weather_forecast_medium.

Ví dụ về tiện ích thời tiết ở kích thước &quot;lớn&quot; 5x3 kích thước. Đổi kích thước tiện ích
            cách này dựa trên tất cả giao diện người dùng
từ các kích thước tiện ích trước đây,
            và thêm chế độ xem bên trong tiện ích chứa thông tin dự báo thời tiết
            vào thứ Ba và thứ Tư. Biểu tượng cho biết trời nắng hoặc mưa
            nhiệt độ cao và thấp mỗi ngày.
Hình 5. 5x3 R.layout.widget_weather_forecast_large.

Ví dụ về tiện ích thời tiết ở kích thước &quot;lớn&quot; 5x4 kích thước. Đổi kích thước tiện ích
            cách này dựa trên tất cả giao diện người dùng
từ các kích thước tiện ích trước đây,
            đồng thời thêm thứ Năm và thứ Sáu (cùng với các biểu tượng tương ứng của chúng
            cho biết loại thời tiết cũng như nhiệt độ cao và thấp
            cho mỗi ngày).
Hình 6. R.layout.widget_weather_forecast_large 5x4.