Android App Widget is an extension of app. Users can place app widgets provided by the app on the home screen. App can provide a piece of information or a simple function through app widgets without requiring users to launch the app. For example, the Clock app provides an app widget that displays the current time. The user can see the time on the app widget on the home screen instead of opening the clock app to see the time. This article will introduce how to develop a simple app widget.
The complete code for this chapter can be found in .
Table of Contents
Creating an App Widget
It is very easy to create an app widget in the project. As shown in the figure below, in the project, select New -> Widget -> App Widget.
Next, enter the class name for the app widget. In the Resizable
field, we can choose the resizeable direction. In the Minimum Widget
and Minimum Height
fields, enter the minimum size for the app widget.
Then, Android Studio will help us generate all the required code. Of course, you can also manually add these codes one by one. We now have a working app widget. You can install this app in emulator, and display PersonInfoAppWidget on the home screen.
Next, we need to modify PersonInfoAppWidget into an app widget that can display personal information.
Modify person_info_app_widget.xml as follows. As can be seen from the layout, the PersonInfoAppWidget will display the person’s name and job.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" style="@style/Widget.AppWidgetExample.AppWidget.Container" android:layout_width="match_parent" android:layout_height="match_parent" android:theme="@style/Theme.AppWidgetExample.AppWidgetContainer"> <LinearLayout style="@style/Widget.AppWidgetExample.AppWidget.InnerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/name_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:textStyle="bold|italic" /> <TextView android:id="@+id/job_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:textAlignment="textEnd" android:textStyle="bold|italic" /> </LinearLayout> </RelativeLayout>
Modify PersonInfoAppWidget.kt as follows. Execute the app again, we can see that the PersonInfoAppWidget will display the person’s name and job.
package com.waynestalk.appwidget import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider import android.content.Context import android.widget.RemoteViews val wayne = Person( name = "Wayne", job = "Software programmer", website = "https://waynestalk.com/", github = "https://github.com/xhhuango", ) /** * Implementation of App Widget functionality. */ class PersonInfoAppWidget : AppWidgetProvider() { override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, ) { // There may be multiple widgets active, so update all of them for (appWidgetId in appWidgetIds) { updateAppWidget(context, appWidgetManager, appWidgetId) } } override fun onEnabled(context: Context) { // Enter relevant functionality for when the first widget is created } override fun onDisabled(context: Context) { // Enter relevant functionality for when the last widget is disabled } } internal fun updateAppWidget( context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, ) { // Construct the RemoteViews object val view = RemoteViews(context.packageName, R.layout.person_info_app_widget) view.setTextViewText(R.id.name_text_view, wayne.name) view.setTextViewText(R.id.job_text_view, wayne.job) // Instruct the widget manager to update the widget appWidgetManager.updateAppWidget(appWidgetId, view) }
AppWidgetProvider
PersonInfoAppWidget inherits AppWidgetProvider, and AppWidgetProvider inherits BroadcastReceiver. So when there is any change in app widget, AppWidgetProvider will receive broadcasts.
The following are commonly used AppWidgetProvider methods:
- onUpdate: When the app widget is asked to provide RemoteViews. We must at least implement this method to provide the view of the app widget.
- onDeleted: When one or more app widgets are deleted, such as from the home screen.
- onEnabled: When an app widget is added.
- onDisabled: When the last app widget is removed.
- onAppWidgetOptionsChanged: When an app widget is resized, or its options are changed.
Each app widget can have multiple instances on the home screen. Hence, each instance has an appWidgetId
. Therefore, most of the methods of AppWidgetProvider have appWidgetId
parameters, so that AppWidgetProvider can know which instance is changed.
Because AppWidgetProvider is BroadcastReceiver, we must declare it in AndroidManifest.xml. If you use Android Studio to add app widget, then Android Studio will automatically declare it in AndriodManifest.xml for us.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.AppWidgetExample" tools:targetApi="31"> <receiver android:name=".PersonInfoAppWidget" 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/person_info_app_widget_info" /> </receiver> </application> </manifest>
RemoteViews
AppWidget can only provide RemoteViews. The content of RemoteViews can be inflated from a layout. However, RemoteViews only supports certain layouts. All the layouts it supports are listed here. We can see that RemoteViews only supports android layouts, not androidx layouts.
When we want to change the views in the layout, we cannot directly access a view, but must use the method of RemoteViews. For example, in PersonInfoAppWidget, when we want to set the name to TextView, we cannot directly access the TextView, but must use RemoteViews.setTextViewText().
val view = RemoteViews(context.packageName, R.layout.person_info_app_widget) view.setTextViewText(R.id.name_text_view, wayne.name) view.setTextViewText(R.id.job_text_view, wayne.job)
AppWidgetProviderInfo XML
When declaring AppWidgetProvider in AndroidManifest.xml, we need to declare AppWidgetProviderInfo in <receiver/> with <meta-data/> .
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <application > <receiver android:name=".PersonInfoAppWidget" 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/person_info_app_widget_info" /> </receiver> </application> </manifest>
If you use Android Studio to add an app widget, it will automatically generate an AppWidgetProviderInfo for us. In this project, Android Studio generates person_info_widget_info.xml.
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/app_widget_description" android:initialKeyguardLayout="@layout/person_info_app_widget" android:initialLayout="@layout/person_info_app_widget" android:minWidth="110dp" android:minHeight="40dp" android:previewImage="@drawable/example_appwidget_preview" android:previewLayout="@layout/person_info_app_widget" android:resizeMode="horizontal|vertical" android:targetCellWidth="2" android:targetCellHeight="1" android:updatePeriodMillis="86400000" android:widgetCategory="home_screen" />
Below we briefly explain the various attributes of <appwidget-provider/>, please refer to the official.
Attributes | Description |
---|---|
targetCellWidth targetCellHeight minWidth minHeight | Starting from Android 12, targetCellWidth and targetCellHeight specify the default size of the app widget. If the home screen does not support grid-based layout, minWidth and minHeight will be used instead. Android 11 and lower, minWidth and minHeight specify the default size of the app widget. When minWidth and minHeight cannot correspond to the size of cells, their values will be rounded up to the nearest cell size. |
minResizeWidth minResizeHeight | Specifies the absolute minimum size of the app widget. |
maxResizeWidth maxResizeHeight | Specifies the recommended maximum size for the app widget. |
resizeMode | Specifies the orientation in which the app widget can be resized.horizontal , vertical , none , horizontal|vertical . |
initialLayout | The layout resource of the app widget. |
description | Displays the description of this app widget in the widget picker. |
previewImage previewLayout | Starting from Android 12, previewLayout specifies a scalable preview. Android 11 and lower, previewImage specifies the preview image. The Widget picker displays this preview. |
updatePeriodMillis | Specifies how often the widget framework will call AppWidgetProvider.onUpdate() to update the app widget’s view. |
widgetCategory | Specifies that this app widget can be displayed on the home screen ( home_screen ) or lock screen ( keyguard ). |
Providing Responsive Layout
When the app widget is resized, we can provide different layouts according to different sizes.
When the PersonInfoAppWidget is resized bigger, we will provide the following layout. In addition to displaying personal names and jobs, the following layout will also display Website and GitHub.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" style="@style/Widget.AppWidgetExample.AppWidget.Container" android:layout_width="match_parent" android:layout_height="match_parent" android:theme="@style/Theme.AppWidgetExample.AppWidgetContainer"> <LinearLayout style="@style/Widget.AppWidgetExample.AppWidget.InnerView" android:layout_width="match_parent" android:layout_height="100dp" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/name_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold|italic" /> <TextView android:id="@+id/job_text_view" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textAlignment="textEnd" android:textStyle="bold|italic" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Website:" android:textStyle="bold|italic" /> <TextView android:id="@+id/website_text_view" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textAlignment="textEnd" android:textStyle="bold|italic" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="GitHub:" android:textStyle="bold|italic" /> <TextView android:id="@+id/github_text_view" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:textAlignment="textEnd" android:textStyle="bold|italic" /> </LinearLayout> </LinearLayout> </RelativeLayout>
We can put the view and its corresponding size into a Map, and then set the map to AppWidgetManager.updateAppWidget(). When PersonInfoAppWidget is displayed, it will choose the nearest layout to display according to its size.
internal fun updateAppWidget( context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, ) { // Construct the RemoteViews object val simpleView = RemoteViews(context.packageName, R.layout.person_info_app_widget) simpleView.setTextViewText(R.id.name_text_view, wayne.name) simpleView.setTextViewText(R.id.job_text_view, wayne.job) val detailView = RemoteViews(context.packageName, R.layout.person_info_detail_app_widget) detailView.setTextViewText(R.id.name_text_view, wayne.name) detailView.setTextViewText(R.id.job_text_view, wayne.job) detailView.setTextViewText(R.id.website_text_view, wayne.website) detailView.setTextViewText(R.id.github_text_view, wayne.github) val viewMapping = mapOf( SizeF(60f, 100f) to simpleView, SizeF(100f, 200f) to detailView, ) val views = RemoteViews(viewMapping) // Instruct the widget manager to update the widget appWidgetManager.updateAppWidget(appWidgetId, views) }
Conclusion
When creating an app widget in a project, many steps are required. However, if you use Android Studio to create an app widget, it is very easy. It will automatically generate all the necessary code for us. We can modify it based on these codes, which saves us a lot of time.
Reference
- Create a simple widget, Google developers.