Tạo tiện ích ứng dụng bằng tính năng Xem nhanh

Các phần sau đây mô tả cách tạo một tiện ích ứng dụng cơ bản bằng Glance.

Khai báo AppWidget trong tệp kê khai

Sau khi hoàn tất các bước thiết lập, hãy khai báo AppWidget và siêu dữ liệu của lớp này trong ứng dụng.

  1. Mở rộng trình nhận AppWidget từ GlanceAppWidgetReceiver:

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
        override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget")
    }

  2. Đăng ký nhà cung cấp tiện ích ứng dụng trong tệp AndroidManifest.xml và tệp siêu dữ liệu liên quan:

        <receiver android:name=".glance.MyReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/my_app_widget_info" />
    </receiver>
    

Thêm siêu dữ liệu AppWidgetProviderInfo

Tiếp theo, hãy làm theo hướng dẫn Tạo tiện ích để tạo và xác định thông tin tiện ích ứng dụng trong tệp @xml/my_app_widget_info.

Điểm khác biệt duy nhất đối với Glance là không có XML initialLayout, nhưng bạn phải xác định một XML. Bạn có thể sử dụng bố cục tải được xác định trước trong thư viện:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>

Khai báo XML AppWidgetProviderInfo

Đối tượng AppWidgetProviderInfo xác định các thuộc tính thiết yếu của tiện ích. Xác định AppWidgetProviderInfo trong tệp tài nguyên siêu dữ liệu XML (res/xml/my_app_widget_info.xml) bên trong phần tử <appwidget-provider>:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:targetCellWidth="1"
    android:targetCellHeight="1"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="120dp"
    android:updatePeriodMillis="86400000"
    android:description="@string/example_appwidget_description"
    android:previewLayout="@layout/example_appwidget_preview"
    android:initialLayout="@layout/glance_default_loading_layout"
    android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"
    android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

Thuộc tính định cỡ tiện ích

Màn hình chính mặc định đặt các tiện ích trong cửa sổ của nó dựa trên một lưới các ô có chiều cao và chiều rộng xác định. Hầu hết màn hình chính chỉ cho phép tiện ích có kích thước là bội số nguyên của các ô lưới, ví dụ: hai ô theo chiều ngang và ba ô theo chiều dọc.

Các thuộc tính định cỡ tiện ích cho phép bạn chỉ định kích thước mặc định cho tiện ích và cung cấp giới hạn dưới và giới hạn trên về kích thước của tiện ích. Trong bối cảnh này, kích thước mặc định của tiện ích là kích thước mà tiện ích đó sử dụng khi được thêm vào màn hình chính lần đầu tiên.

Bảng sau đây mô tả các thuộc tính <appwidget-provider> liên quan đến kích thước tiện ích:

Thuộc tính và nội dung mô tả
targetCellWidth và targetCellHeight (Android 12), minWidth và minHeight
  • Kể từ Android 12, các thuộc tính targetCellWidth và targetCellHeight chỉ định kích thước mặc định của tiện ích theo ô lưới. Các thuộc tính này bị bỏ qua trong Android 11 trở xuống và có thể bị bỏ qua nếu màn hình chính không hỗ trợ bố cục dựa trên lưới.
  • Các thuộc tính minWidth và minHeight chỉ định kích thước mặc định của tiện ích theo dp. Nếu giá trị chiều rộng hoặc chiều cao tối thiểu của tiện ích không khớp với kích thước của ô, thì các giá trị này sẽ được làm tròn lên kích thước ô gần nhất.
Bạn nên chỉ định cả hai nhóm thuộc tính (targetCellWidth và targetCellHeight, cũng như minWidth và minHeight) để ứng dụng có thể quay lại sử dụng minWidth và minHeight nếu thiết bị của người dùng không hỗ trợ targetCellWidth và targetCellHeight. Nếu được hỗ trợ, các thuộc tính targetCellWidth và targetCellHeight sẽ được ưu tiên hơn các thuộc tính minWidth và minHeight.
minResizeWidth và minResizeHeight Chỉ định kích thước tối thiểu tuyệt đối của tiện ích. Các giá trị này chỉ định kích thước mà tiện ích không thể đọc được hoặc không thể sử dụng được. Khi sử dụng các thuộc tính này, người dùng có thể đổi kích thước tiện ích thành kích thước nhỏ hơn kích thước mặc định của tiện ích. Thuộc tính minResizeWidth sẽ bị bỏ qua nếu lớn hơn minWidth hoặc nếu bạn không bật tính năng đổi kích thước theo chiều ngang. Xem resizeMode. Tương tự, thuộc tính minResizeHeight sẽ bị bỏ qua nếu lớn hơn minHeight hoặc nếu bạn không bật tính năng đổi kích thước theo chiều dọc.
maxResizeWidth và maxResizeHeight Chỉ định kích thước tối đa được đề xuất của tiện ích. Nếu các giá trị không phải là bội số của kích thước ô lưới, thì các giá trị đó sẽ được làm tròn lên kích thước ô gần nhất. Thuộc tính maxResizeWidth sẽ bị bỏ qua nếu nhỏ hơn minWidth hoặc nếu bạn không bật tính năng đổi kích thước theo chiều ngang. Xem resizeMode. Tương tự, thuộc tính maxResizeHeight sẽ bị bỏ qua nếu nhỏ hơn minHeight hoặc nếu bạn không bật tính năng đổi kích thước theo chiều dọc. Ra mắt trong Android 12.
resizeMode Chỉ định các quy tắc mà theo đó một tiện ích có thể được đổi kích thước. Bạn có thể sử dụng thuộc tính này để cho phép đổi kích thước tiện ích trên màn hình chính theo chiều ngang, chiều dọc hoặc cả hai trục. Người dùng chạm và giữ một tiện ích để hiện ô điều khiển thay đổi kích thước, sau đó kéo ô điều khiển ngang hoặc dọc để thay đổi kích thước của tiện ích trên lưới bố cục. Giá trị cho thuộc tính resizeMode bao gồm horizontal, vertical và none. Để khai báo một tiện ích có thể đổi kích thước theo chiều ngang và chiều dọc, hãy sử dụng horizontal|vertical.

Ví dụ

Để minh hoạ cách các thuộc tính trong bảng trước ảnh hưởng đến kích thước tiện ích, hãy giả sử các thông số kỹ thuật sau:

  • Một ô lưới có chiều rộng 30 dp và chiều cao 50 dp.
  • Sau đây là quy cách của thuộc tính:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="80dp"
    android:minHeight="80dp"
    android:targetCellWidth="2"
    android:targetCellHeight="2"
    android:minResizeWidth="40dp"
    android:minResizeHeight="40dp"
    android:maxResizeWidth="120dp"
    android:maxResizeHeight="120dp"
    android:resizeMode="horizontal|vertical" />

Kể từ Android 12:

Sử dụng các thuộc tính targetCellWidth và targetCellHeight làm kích thước mặc định của tiện ích.

Kích thước mặc định của tiện ích là 2x2. Bạn có thể đổi kích thước tiện ích xuống 2x1 hoặc lên 4x3.

Android 11 trở xuống:

Sử dụng các thuộc tính minWidth và minHeight để tính toán kích thước mặc định của tiện ích.

Chiều rộng mặc định = Math.ceil(80 / 30) = 3

Chiều cao mặc định = Math.ceil(80 / 50) = 2

Kích thước mặc định của tiện ích là 3x2. Bạn có thể đổi kích thước tiện ích xuống 2x1 hoặc lên toàn màn hình.

Các thuộc tính tiện ích bổ sung

Bảng sau đây mô tả các thuộc tính <appwidget-provider> liên quan đến các đặc điểm khác ngoài kích thước tiện ích.

Thuộc tính và nội dung mô tả
updatePeriodMillis Xác định tần suất khung tiện ích yêu cầu cập nhật từ GlanceAppWidgetReceiver bằng cách gọi phương thức gọi lại onUpdate(). Bạn nên cập nhật càng ít càng tốt (không quá một lần mỗi giờ) để tiết kiệm pin. Để biết thông tin chi tiết, hãy xem phần Thời điểm cập nhật tiện ích trong phần Quản lý trạng thái của Glance.
initialLayout Trỏ đến tài nguyên bố cục xác định bố cục tải của tiện ích trước khi các thành phần giao diện người dùng Glance hiển thị. Bạn có thể sử dụng bố cục tải được xác định trước trong thư viện: @layout/glance_default_loading_layout.
configure Xác định hoạt động cấu hình sẽ chạy khi người dùng thêm tiện ích. Hãy xem hướng dẫn Cho phép người dùng định cấu hình tiện ích ứng dụng.
description Chỉ định nội dung mô tả cho bộ chọn tiện ích để hiển thị cho tiện ích của bạn. Ra mắt trong Android 12.
previewLayout (Android 12) và previewImage (Android 11 trở xuống)
  • Kể từ Android 12, thuộc tính previewLayout chỉ định một bản xem trước có thể mở rộng mà bạn cung cấp dưới dạng bố cục XML được đặt thành kích thước mặc định của tiện ích. Tốt nhất là tệp này trỏ đến một tệp ánh xạ XML tĩnh khớp với bố cục thiết kế của bạn.
  • Trong Android 11 trở xuống, thuộc tính previewImage chỉ định ảnh chụp màn hình có thể vẽ hình ảnh tĩnh về giao diện của tiện ích, xuất hiện trong bộ chọn tiện ích.
Bạn nên chỉ định cả hai để ứng dụng của bạn có thể quay lại các nền tảng cũ một cách linh hoạt. Đối với các nền tảng mới hơn (Android 15 trở lên), bạn có thể xác định bản xem trước được tạo trực tiếp trong Kotlin bằng `GlanceAppWidget.providePreview`. Hãy xem hướng dẫn về Bản xem trước được tạo.
autoAdvanceViewId Chỉ định mã thành phần hiển thị của thành phần hiển thị phụ của tiện ích được máy chủ của tiện ích tự động chuyển.
widgetCategory Khai báo xem tiện ích của bạn có thể hiển thị trên màn hình chính (home_screen), màn hình khoá (keyguard) hay cả hai hay không. Đối với Android 5.0 trở lên, chỉ home_screen là hợp lệ.
widgetFeatures Khai báo các tính năng mà tiện ích hỗ trợ. Ví dụ: nếu cấu hình của tiện ích là không bắt buộc, hãy chỉ định cả configuration_optional và reconfigurable.

Xác định GlanceAppWidget

  1. Tạo một lớp mới mở rộng từ GlanceAppWidget và ghi đè phương thức provideGlance. Đây là phương thức mà bạn có thể tải dữ liệu cần thiết để hiển thị tiện ích:

    class MyAppWidget : GlanceAppWidget() {
    
        override suspend fun provideGlance(context: Context, id: GlanceId) {
    
            // In this method, load data needed to render the AppWidget.
            // Use `withContext` to switch to another thread for long running
            // operations.
    
            provideContent {
                // create your AppWidget here
                Text("Hello World")
            }
        }
    }

  2. Tạo thực thể cho lớp này trong glanceAppWidget trên GlanceAppWidgetReceiver:

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
    
        // Let MyAppWidgetReceiver know which GlanceAppWidget to use
        override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
    }

Giờ đây, bạn đã định cấu hình AppWidget bằng Glance.

Quản lý bộ nhớ hình ảnh của tiện ích trên các nhóm kích thước

Khi tiện ích của bạn hiển thị bitmap hoặc URI hình ảnh content://, hãy giữ tổng bộ nhớ hình ảnh RemoteViews được chuyển đổi tuần tự trong giới hạn của hệ thống (6 * width * height byte, được thực thi bằng IllegalArgumentException trên Android 17 trở lên) bằng cách làm như sau:

  • Giải mã bitmap dùng chung trước provideContent: Nếu tiện ích của bạn sử dụng SizeMode.Responsive hoặc SizeMode.Exact, thì Glance sẽ chạy lambda provideContent một lần cho mỗi bộ chứa DpSize. Việc giải mã Bitmap bên trong provideContent sẽ phân bổ một thực thể Bitmap riêng biệt cho mỗi bộ chứa, nhân mức sử dụng bộ nhớ trong RemoteViews.mBitmapCache. Giải mã bitmap được lấy mẫu giảm (Bitmap.Config.RGB_565 cho hình ảnh mờ) một lần bên trong provideGlance trước provideContent để tất cả bộ chứa đều dùng chung một tệp đối chiếu Bitmap.
  • Sử dụng lệnh nhập ImageProvider phù hợp: Jetpack Glance cung cấp hai phương thức nạp chồng ImageProvider riêng biệt trên hai gói:

    • Sử dụng import androidx.glance.ImageProvider khi truyền ImageProvider(bitmap: Bitmap) trong bộ nhớ hoặc tài nguyên drawable ImageProvider(resId: Int).
    • Sử dụng import androidx.glance.appwidget.ImageProvider khi truyền URI ImageProvider(uri: Uri). Khi phân phát tệp đã lưu vào bộ nhớ đệm thông qua URI FileProvider, hãy cấp quyền Intent.FLAG_GRANT_READ_URI_PERMISSION cho mọi gói trình chạy Intent.CATEGORY_HOME đã cài đặt để máy chủ có thể kết xuất hình ảnh.

Sử dụng lớp GlanceAppWidgetReceiver để xử lý các thông báo của tiện ích

Tiện ích GlanceAppWidgetReceiver điều phối các thông báo và thông tin cập nhật trạng thái nền tảng bằng cách mở rộng AppWidgetProvider cơ bản. Lớp này nhận các sự kiện của nền tảng khi tiện ích của bạn được cập nhật, xoá, bật hoặc tắt, sau đó chuyển các sự kiện này thành yêu cầu về vòng đời của Compose.

Khai báo tiện ích trong tệp kê khai

Khai báo lớp con của lớp GlanceAppWidgetReceiver dưới dạng một bộ nhận tín hiệu truyền tin trong tệp AndroidManifest.xml:

<receiver android:name="MyReceiver"
          android:exported="false">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/my_app_widget_info" />
</receiver>

Phần tử <receiver> yêu cầu thuộc tính android:name, thuộc tính này chỉ định lớp nhận. Bộ thu phải chấp nhận hành động truyền tin ACTION_APPWIDGET_UPDATE bên trong <intent-filter>.

Phần tử <meta-data> phải xác định tên của phần tử là android.appwidget.provider và thuộc tính android:resource phải trỏ đến tài nguyên siêu dữ liệu XML AppWidgetProviderInfo (@xml/my_app_widget_info).

Triển khai lớp GlanceAppWidgetReceiver

Trong Glance, bạn mở rộng GlanceAppWidgetReceiver thay vì trực tiếp mở rộng AppWidgetProvider. Triển khai bằng cách liên kết thiết bị thu với thực thể GlanceAppWidget. Các lệnh gọi lại chính có trong GlanceAppWidgetReceiver hoạt động như sau:

  • onUpdate(): Tự động bị Glance ghi đè để thực thi các bản cập nhật thành phần. Nếu ghi đè onUpdate theo cách thủ công, bạn phải gọi super.onUpdate để cho phép Glance khởi chạy thành công các luồng thành phần.
  • onAppWidgetOptionsChanged(): Được gọi khi tiện ích được đặt lần đầu hoặc được đổi kích thước. Glance đọc các mục trong gói tuỳ chọn nâng cao để bố cục của bạn điều chỉnh liền mạch dựa trên kích thước thời gian chạy.
  • onDeleted(Context, IntArray): Được gọi bất cứ khi nào người dùng xoá một thực thể tiện ích cụ thể.
  • onEnabled(Context): Được kích hoạt khi thực thể đầu tiên của tiện ích được tạo thành công. Rất phù hợp để chạy các hoạt động di chuyển trên toàn cầu.
  • onDisabled(Context): Được gọi khi phiên bản hoạt động gần đây nhất của nhà cung cấp bị xoá.
  • onReceive(Context, Intent): Chặn mọi thông báo truyền tin của nền tảng trước các phương thức gọi lại cụ thể. Bạn phải đảm bảo rằng mọi logic của trình nhận tuỳ chỉnh mà bạn viết đều gọi super.onReceive(context, intent) và không bao giờ được tự gọi goAsync vì Glance tự động định tuyến công việc một cách không đồng bộ.

Nhận ý định truyền tin của tiện ích

Trong nền, GlanceAppWidgetReceiver lọc và xử lý các ý định truyền tin của tiện ích nền tảng cơ bản sau:

Tạo giao diện người dùng

Đoạn mã sau đây minh hoạ cách tạo giao diện người dùng:

/* Import Glance Composables
 In the event there is a name clash with the Compose classes of the same name,
 you may rename the imports per https://kotlinlang.org/docs/packages.html#imports
 using the `as` keyword.

import androidx.glance.Button
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.text.Text
*/
class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // Load data needed to render the AppWidget.
        // Use `withContext` to switch to another thread for long running
        // operations.

        provideContent {
            // create your AppWidget here
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        Column(
            modifier = GlanceModifier.fillMaxSize(),
            verticalAlignment = Alignment.Top,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button(
                    text = "Home",
                    onClick = actionStartActivity<MyActivity>()
                )
                Button(
                    text = "Work",
                    onClick = actionStartActivity<MyActivity>()
                )
            }
        }
    }
}

Mã mẫu trước đó thực hiện những việc sau:

  • Trong Column cấp cao nhất, các mục được đặt theo chiều dọc, mục này sau mục kia.
  • Column mở rộng kích thước để phù hợp với không gian có sẵn (thông qua GlanceModifier và căn chỉnh nội dung lên trên cùng (verticalAlignment) và căn giữa theo chiều ngang (horizontalAlignment).
  • Nội dung của Column được xác định bằng lambda. Thứ tự rất quan trọng.
    • Mục đầu tiên trong Column là thành phần Text có khoảng đệm 12.dp.
    • Mục thứ hai là Row, trong đó các mục được đặt theo chiều ngang, mục này sau mục kia, với hai Buttons được căn giữa theo chiều ngang (horizontalAlignment). Màn hình cuối cùng phụ thuộc vào không gian có sẵn. Hình ảnh sau đây là một ví dụ về giao diện của thông báo:
destination_widget
Hình 1. Ví dụ về giao diện người dùng.

Bạn có thể thay đổi giá trị căn chỉnh hoặc áp dụng các giá trị đối tượng sửa đổi khác (chẳng hạn như khoảng đệm) để thay đổi vị trí và kích thước của các thành phần. Hãy xem tài liệu tham khảo để biết danh sách đầy đủ các thành phần, tham số và đối tượng sửa đổi có sẵn cho mỗi lớp.

Triển khai góc bo tròn

Android 12 giới thiệu các tham số hệ thống để tuỳ chỉnh bán kính góc của tiện ích ứng dụng một cách linh động:

  • system_app_widget_background_radius: Chỉ định bán kính góc của vùng chứa nền tiện ích (không bao giờ lớn hơn 28 dp).
  • Bán kính bên trong: Để tránh bị cắt nội dung, hãy tính bán kính tỷ lệ cho nội dung bên trong dựa trên đường viền nền của hệ thống: systemRadiusValue - widgetPadding

Trong Glance, bạn có thể áp dụng các thuộc tính định cỡ bán kính góc một cách linh động trong thành phần bằng cách sử dụng GlanceModifier.cornerRadius(android.R.dimen.system_app_widget_background_radius).

Để có khả năng tương thích ngược trên các thiết bị chạy Android 11 (cấp độ API 30) trở xuống, hãy triển khai thuộc tính tùy chỉnh và phương án dự phòng tài nguyên giao diện tuỳ chỉnh:

  • /values/attrs.xml

    <resources>
    <attr name="backgroundRadius" format="dimension" />
    </resources>
    
  • /values/styles.xml

    <resources>
    <style name="MyWidgetTheme">
      <item name="backgroundRadius">@dimen/my_background_radius_dimen</item>
    </style>
    </resources>
    
  • /values-31/styles.xml

    <resources>
    <style name="MyWidgetTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
      <item name="backgroundRadius">@android:dimen/system_app_widget_background_radius</item>
    </style>
    </resources>
    
  • /drawable/my_widget_background.xml

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="?attr/backgroundRadius" />
    </shape>