Android 12, announced with Google IO 2021, is touted as the biggest design change in history. Its new Material You design language, smooth animation, and new pieces are all impressive. This article will focus on the widget segment and discuss its various new features and adaptation methods after the redesign.

Widgets are called AppWidgets on the Android platform and are sometimes translated into widgets, widgets, and widgets. It’s all about the same thing: a special design that shows up on a Launcher and provides more information than just the Logo. It allows users to view information and simply interact with each other without having to open the App. It has a similar design on PC and on earlier Symbian.

preface

A brief review of the ongoing exploration of widget design for mobile platforms:

  • Earlier versions of Android lacked aesthetics, and widgets remained unchanged for years. It seems to be rarely used except for the weather, clocks and other commonly used components, and gradually forgotten
  • Windows PhoneThe design of flexible display information on the free size Logo with dynamic tiles is very advanced, but the ecological construction is difficult, so it has long been retired
  • Apple has always been conservative untiliOS 10The widget was introduced, but it was limited by a screen. untiliOS 14With the full support of the government, it was a big success, with a tendency to catch up
  • VIVO followed with a blockbusterOriginOSThe perfect combination of Logo and small pieces, trying to unify the concept of tiles and small pieces, is very commendable

Perhaps spurred on by business friends, Google is finally taking a fresh look at widgets, an old feature that has been redesigned for Android 12.

The following will guide you to experience the new features and corresponding adaptation methods of Android 12 widgets based on actual code.

1. Uniform variations in selection and presentation

In fact, even without any adaptation, the widget running directly on 12 differs significantly from the widget running on 11, mainly in terms of selectors and display effects.

Take, for example, widgets for Chrome and Youtube Music:

You can see some changes in 12:

  • The selector
  • Hover the search box at the top to find the target widget more quickly
  • Widgets automatically collapse according to the App to avoid irrelevant widgets taking up screen space
  • The App title also hints at the number of widgets to include
  • By default, widgets have rounded corners when you drag them onto the desktop

The widget selector on 11 is unsearchable and uncollapsible, and dragging onto the desktop has an initial right Angle effect.

2. Beautiful rounded corner design

Health information is increasingly important, with a small piece showing the number of steps taken today and a detailed step chart with the AndroidPlot framework.

override fun onUpdate(...). {
    for (appWidgetId in appWidgetIds) {
        showBarChartToWidget(context, appWidgetManager, appWidgetId)
    }
}

private fun showBarChartToWidget(...). {
    // Create plot view.
    val plot = XYPlot(context, "Pedometers chart")...// Set graph shape
    plot.setBorderStyle(Plot.BorderStyle.ROUNDED, 12f.12f)
    plot.isDrawingCacheEnabled = true

    // Reflect chart's bitmap to widget.
    val bmp = plot.drawingCache
    val remoteViews = RemoteViews(context.packageName, R.layout.widget_pedometer)
    remoteViews.setBitmap(R.id.bar_chart, "setImageBitmap", bmp)
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
}
Copy the code

No special adaptation, directly run to 12, you can have rounded corners.

However, the layout should follow the following two suggestions:

  • Do not place content around the corners to prevent it from being cut off
  • Do not use a transparent, empty view or layout for the background, in case the system cannot detect the boundary to crop

In fact, the following dimensions are preset to set the default rounded corner appearance.

  • system_app_widget_background_radius: The rounded corner size of the panel background, 16dp by default, 28dp up
  • system_app_widget_inner_radius: The rounded corner size of the inner view of the widget. 8dp by default and 20dp up
  • system_app_widget_internal_padding: The padding value of the internal view, 16dp by default

See the official schematic for the dimensions of the inner and outer rounded corners.

Note:

  1. These dimensions can be modified by the ROM vendor or 3rd Launcher, and do not necessarily guarantee a consistent size
  2. There is no official explanation on how the internal view of the widget can be applied to the internal rounded corner size, and the DEMO has not been adapted either. I don’t know whether the problem is ROM or App, which needs further research in the future

Of course, it was easy to support rounded corners in previous systems: customize the RADIUS attribute, apply it to shape drawable, and manually apply the drawable to background. Please refer to the official Sample:

Github.com/android/use…

3. Dynamic color effects

Add dark theme support to widgets to automatically adapt dynamic colors.

<! -- values/themes.xml -->
<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="Theme.AppWidget" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/white</item>.</style>
</resources>

<! -- values-night/themes.xml -->
<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="Theme.AppWidget" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <item name="colorPrimary">@color/purple_200</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/black</item>.</style>
</resources>
Copy the code

4. Improved widget preview

12. The preview interface for panel selection has been improved to show more accurate preview effect.

4.1 Dynamic Preview

Previously, you could only use the previewImage property to display a previewImage. Forgetting to update it during the iteration of the feature could result in a difference between the preview and the actual effect.

The previewLayout property is introduced to configure the actual layout of widgets, so that users can see a more realistic view in the widgets’ selectors rather than a layer of static images.

This eliminates the need to maintain additional previews while keeping the results consistent.

<appwidget-provider
    <!--The existing image attribute is specifiedUIDesign drawings provided-->
    android:previewImage="@drawable/app_widget_pedometer_preview_2"

    <! -- The new preview API specifies the actual layout -->
    android:previewLayout="@layout/widget_pedometer"
</appwidget-provider>
Copy the code

On the left is the initial design of the step group, and on the right is the final actual effect.

If you forget to convince the UI to redo the drawing, the preview on 11 will look a lot different than the real thing. With the new API, you can get a preview of what you see is what you get.

In general, the previewLayout property is best for specifying the actual layout of the widget. However, if the previewed test data conflicts with the actual default values, you can specify a dedicated preview layout, just make sure the layout is consistent.

4.2 Adding preview Description

The description attribute displays additional instructions below the widget preview, so that users can better understand its function positioning.

<appwidget-provider
    android:description="@string/app_widget_pedometer_description">
</appwidget-provider>
Copy the code

Note that the description attribute is not new at 12, but selectors before 12 do not support showing this description.

5. Support new interactive controls

The previous widget did not support CheckBox and other controls, from 12 to fully support CheckBox, Switch and RadioButton three state controls.

Here are the simple effects of using these three controls.

Here’s a simple to-do list to better illustrate the use of status widgets.

// Specify the CheckBox control in the widget layout<LinearLayout .
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:theme="@style/Theme.AppWidget.AppWidgetContainer">

    <include layout="@layout/widget_todo_list_title_region" />

    <CheckBox
        android:id="@+id/checkbox_first"
        style="@style/Widget.AppWidget.Checkbox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/todo_list_sample_1"
        Tools:text="@string/todo_list_tool" />.</LinearLayout>
Copy the code

If you run the same code on 11, you will see that the load failed.

The log

AppWidgetHostView: Error inflating AppWidget AppWidgetProviderInfo(UserHandle{0}/ComponentInfo{com.example.splash/com.example.splash.widget.TodoListAppWidget}): android.view.InflateException: Binary XML file line #13 in com.example.splash:layout/widget_todo_list: Binary XML file line #13 in com.example.splash:layout/widget_todo_list: Error inflating class android.widget.CheckBox

If the content of the text is uncertain, the code can dynamically control the text, while listening for user selection events.

For example, we’re going to show you the three big mountains that Android developers need to learn today, and we’re going to pop up Toast.

private fun updateAppWidget(...). {
    val viewId1 = R.id.checkbox_first
    val pendingIntent = PendingIntent.getBroadcast(...)

    val rv = RemoteViews(context.packageName, R.layout.widget_todo_list)
    rv.apply {
        // Set the text
        setTextViewText(viewId1, context.resources.getString(R.string.todo_list_android))
        ...

        // Set the default CheckBox status
        setCompoundButtonChecked(viewId1, true)

        // Listen for the corresponding CheckBox selection event
        setOnCheckedChangeResponse(
            viewId1,
            RemoteViews.RemoteResponse.fromPendingIntent(pendingIntent)
        )
    }
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
}

override fun onReceive(context: Context? , intent:Intent?).{...valchecked = intent.extras? .getBoolean(RemoteViews.EXTRA_CHECKED,false) ?: false
    valviewId = intent.extras? .getInt(EXTRA_VIEW_ID) ? : -1

    Toast.makeText(
        context,
        "ViewId : $viewId's checked status is now : $checked",
        Toast.LENGTH_SHORT
    ).show()
}
Copy the code

6. Convenient configuration of dimensions

12 For the size of the panel configuration link has also been improved, more convenient.

6.1 Precise dimensions

In addition to the existing attributes such as minWidth and minResizeWidth, several new attributes have been added to make it easier to configure the size of widgets.

  • TargetCellWidth and targetCellHeight: The width and height of the Cell on the Launcher, instead of minWidth and minHeight. In fact, a Launcher displays widgets in units of cells, so it is more reasonable to specify the number of cells directly
  • MaxResizeWidth and maxResizeHeight: Set the maximum size allowed on the Launcher to make up for the deficiency of minResizeWidth and minResizeHeight
<appwidget-provider
    .
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="110dp">
</appwidget-provider>
Copy the code

6.2 Flexible size adjustment

When you add widgets on iOS, they are fixed in size and do not support adjustment. On Android 12, widgets can be adjusted with a long press.

To support this feature, simply specify a re64signals value to the widgetFeatures property.

<appwidget-provider
    android:widgetFeatures="reconfigurable">
</appwidget-provider>
Copy the code

The reconfigurable flag was introduced in Android 9 (API level 28), but it was not widely supported in launchers until Android 12.

This property was actually introduced in Android 9, but it’s officially only been fully supported since S. I found that the size of the Pixel Launcher of version 11 can be directly adjusted. I don’t know whether the official meaning is that other Launcher is not supported.

6.3 Using the Default Settings

The configure property can launch a configuration screen before the widget is displayed, allowing the user to select the desired content, theme, style, and so on.

If you want the user to see the effect quickly and don’t want to show the screen, simply specify a new configuration_optional value in widgetFeatures.

<appwidget-provider
  .
  android:configure="com.example.appwidget.activity.WidgetConfigureActivity"
  android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>
Copy the code

If you change your mind later and want to replace the configuration, you can long press the widget to find the configuration entry.

One is the edit button at the bottom right of the widget, and the second is the Setup menu at the top, which was not available on previous versions.

7. Control layout efficiently

When a widget has a large number of contents, it is often given a limited Size in order to display its integrity. This means that the component can be successfully placed only when the space of the Launcher is large enough. When the space Launcher is in a rush, it’s awkward, and the user has to choose between removing other units or abandoning yours.

The best way to avoid this is to use different layouts for different sizes and to make choices about what to display. That is, more rich content is provided when the Size is sufficient, whereas only the most basic and commonly used information is presented.

7.1 Responsive Layout

How did you do this before? In addition to the default group a general train of thought, of various sizes by onAppWidgetOptionsChanged callback can also control the change of the layout, but usually very complicated.

The new RemoteViews(Map

Map)API simplifies the implementation process. When the widget is placed, the mapping between Size and layout is notified to the system, and when Size changes, AppWidgetManager automatically updates the corresponding layout.
,>

For example, when the Size of a to-do item is 3×2, the add button is displayed, and when the Size is 2×2, only the layout of the to-do list is displayed.

The code implementation is also simple and clear:

private fun updateAppWidgetWithResponsiveLayouts(...).{...// Button is displayed only when the size is wide enough
    val wideView = RemoteViews(rv)
    wideView.setViewVisibility(button, View.VISIBLE)

    val viewMapping: Map<SizeF, RemoteViews> = mapOf(
        SizeF(100f.100f) to rv,
        SizeF(200f.100f) to wideView
    )
    
    // Inform AppWidgetManager of the mapping between Size and RemoteViews layout
    val remoteViews = RemoteViews(viewMapping)
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
}
Copy the code

Benefits:

  • Eliminates the tedious task of providing a bunch of size pieces for the same function, reducing the burden on selectors
  • Simple implementation, automatic response

7.2 Precise Layout

Today’s mobile devices come in all sizes and shapes, especially the maturity of foldable screens. If a responsive layout still doesn’t meet more refined requirements, you can get further precise control of the layout by retrieving the target Size in the Size change callback.

You can get the target Size from AppWidgetManager using the OPTION_APPWIDGET_SIZES KEY added by AppWidgetManager.

// Listen for target size
override fun onAppWidgetOptionsChanged(...).{...// Get the new sizes.
    valsizes = newOptions? .getParcelableArrayList<SizeF>( AppWidgetManager.OPTION_APPWIDGET_SIZES )// Do nothing if sizes is not provided by the launcher.
    if (sizes.isNullOrEmpty()) {
        return
    }
    Log.d("Widget"."PedometerAppWidget#onAppWidgetOptionsChanged() size:${sizes}")

    // Get exact layout
    if (BuildCompat.isAtLeastS()) {
        valremoteViews = RemoteViews(sizes.associateWith(::createRemoteViews)) appWidgetManager? .updateAppWidget(appWidgetId, remoteViews) } }Copy the code

The following logs show that the target Size is sent back when the Size changes.

Widget  : PedometerAppWidget#onAppWidgetOptionsChanged() size:[377.42856x132. 0.214.57143x21657143.]
Copy the code

Then match the corresponding view from the preset fine layout.

private fun createRemoteViews(size: SizeF): RemoteViews {
    val smallView: RemoteViews = ...
    val tallView: RemoteViews = ...
    valwideView: RemoteViews = ... .return when (size) {
        SizeF(100f.100f) -> smallView
        SizeF(100f.200f) -> tallView
        SizeF(200f.100f) -> wideView ... }}Copy the code

Note: The Size list is actually provided by the Launcher. If the 3rd Launcher does not fit this feature, the Size returned may be empty

8. Feel free to update views

As RemoteViews is an important management class for widget views, the OSV also added a number of apis to give more control over the presentation of views.

  • color-changingsetColorStateList()
  • To change the marginssetViewLayoutMargin()
  • Change width and heightsetViewLayoutWidth()Etc.

These new apis allow us to implement many convenient features, such as updating the color of the text after CheckBox is selected. The idea is simple:

  1. Listen for widgets’ click events and pass the target view
  2. Gets a preset text color based on the state of the CheckBox
  3. Use setColorStateList() to update
override fun onReceive(context: Context? , intent:Intent?).{...// Get target widget.
    val appWidgetManager = AppWidgetManager.getInstance(context)
    valthisAppWidget = ComponentName(context!! .packageName, TodoListAppWidget::class.java.name)
    val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget)

    // Update widget color parameters dynamically.
    for (appWidgetId in appWidgetIds) {
        val remoteViews = RemoteViews(context.packageName, R.layout.widget_todo_list)
        remoteViews.setColorStateList(
            viewId,
            "setTextColor",
            getColorStateList(context, checked)
        )
        appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
    }
}

private fun getColorStateList(context: Context, checkStatus: Boolean): ColorStateList =
    if (checkStatus) 
        ColorStateList.valueOf(context.getColor(R.color.widget_checked_text_color))
    else 
        ColorStateList.valueOf(context.getColor(R.color.widget_unchecked_text_color))
Copy the code

Let’s say the Chart is too small to see clearly. You can make it zoom in after you click on it, and then you can click on it again.

// Get a preset width and height based on the recorded zoom state
// Update width and height by setViewLayoutWidth and setViewLayoutHeight
override fun onReceive(context: Context? , intent:Intent?).{...val widthScaleSize = if (scaleOutStatus) 200f else 260f
    val heightScaleSize = if (scaleOutStatus) 130f else 160f

    // Update widget layout parameters dynamically.
    for (appWidgetId in appWidgetIds) {
        val remoteViews = RemoteViews(context.packageName, R.layout.widget_pedometer)
        remoteViews.setViewLayoutWidth(viewId, widthScaleSize, TypedValue.COMPLEX_UNIT_DIP)
        remoteViews.setViewLayoutHeight(viewId, heightScaleSize, TypedValue.COMPLEX_UNIT_DIP)
        appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
    }
}
Copy the code

9. Smooth startup effect

In version 12, clicking on the Widget to launch the App gives a smoother transition and easy adaptation. The official instructions simply specify the Android Backgoround ID for the widget’s root layout.

<LinearLayout
    .
    android:id="@android:id/background">
</LinearLayout>
Copy the code

The actual action shows that there is no change in the App launch after adding this ID. The reasons for this need to be further studied.

12 Starts to restrict activities from Broadcast receivers or servers, but not from widgets. However, in order to avoid visual awkwardness, this background startup does not show the migration animation.

10. Simplified data binding

It is also common for widgets to display listViews. To provide data, you need to declare a RemoteViewsService that returns a RemoteViewsFactory.

The new setRemoteAdapter(int, RemoteCollectionItems) API in 12 simplifies the binding process considerably.

For example, creating a widget with a list of upcoming events allows efficient injection of data through the API.

private fun updateCountDownList(...).{...// Create a Builder for building Remote collection data
    val builder = RemoteViews.RemoteCollectionItems.Builder()
    val menuResources = context.resources.obtainTypedArray(R.array.count_down_list_titles)

    // Add RemoteViews for each Item to the Builder
    for (index in 0 until menuResources.length()) {
        ...
        builder.addItem(index.toLong(), constructRemoteViews(context, resId))
    }

    // Build the Remote collection data
    // Insert the setRemoteAdapter directly into the ListView
    val collectionItems = builder.setHasStableIds(true).build()
    remoteViews.setRemoteAdapter(R.id.count_down_list, collectionItems)
    ...
}

// Create RemoteViews for each Item in the ListView
private fun constructRemoteViews(...).: RemoteViews {
    val remoteViews = RemoteViews(context.packageName, R.layout.item_count_down)
    val itemData = context.resources.getStringArray(stringArrayId)

    // Iterates over the Item row to set the corresponding text
    itemData.forEachIndexed { index, value ->
        val viewId = when (index) {
            0 -> R.id.item_title
            1 -> R.id.item_time
            ...
        }
        remoteViews.setTextViewText(viewId, value)
    }
    return remoteViews
}
Copy the code

If the layout of an Item is not fixed in more than one way, you can use setViewTypeCount to specify the number of layout types, telling the ListView which ViewHolder type to supply. If not specified, the system will automatically identify the type of layout, the system needs additional processing.

But be aware: an exception is thrown if the specified number is different from the actual number.

IllegalArgumentException: View type count is set to 2, but the collection contains 3 different layout ids

In addition, the View that supports the API must be a subclass of AdapterView, such as the common ListView, GridView, and so on. RecyclerView is not supported, after all, small pieces of data is not much, can not use it doesn’t matter.

11. Added API summary

Here is a brief list of 12 new apis for widgets for your convenience.

RemoteViews class

methods role
RemoteViews(Map<SizeF, RemoteViews>) Create target RemoteViews from the responsive layout mapping table
addStableView() Add subviews to RemoteViews dynamically, similar to ViewGroup#addView()
setCompoundButtonChecked() Updates the checked status for the CheckBox or Switch control
setRadioGroupChecked() Updates the selected state for the RadioButton control
setRemoteAdapter(int , RemoteCollectionItems) Directly populate the widget’s ListView with data
setColorStateList() Dynamically updates the color of the widget view
setViewLayoutMargin() Dynamically updates the margins of the widget view
SetViewLayoutWidth (), setViewLayoutHeight () Dynamically updates the width and height of the widget view
setOnCheckedChangeResponse() Listen for changes in the status of the three status widgets such as CheckBox

XML attributes

attribute role
description Additional description of the configuration widget in the selector
previewLayout Configure the preview layout of widgets
reconfigurable Specify that the size of the widget supports direct adjustment
configuration_optional The content of the specified widget can be set to the default design without the need to launch the configuration screen
TargetCellWidth, targetCellHeight Limit the Launcher cell to which the widget belongs
MaxResizeWidth, maxResizeHeight Configure the maximum width and height dimensions that the widget can support

conclusion

From the above reading, you can see that Google has put a lot of effort into the widget redesign, injecting new gameplay and twists into an old feature.

A quick review of the new widget features in Android 12:

  • More convenient widget selector
  • More beautiful rounded border design
  • More flexible widget preview
  • More complete control support
  • More convenient size adjustment
  • More precise layout control
  • More free view updates
  • Easier list data binding

With so many new features, we can make widgets more efficient and provide a better user experience.

Follow In the footsteps of Android 12 and try to make existing widgets shine again.

Pending matters

  1. How to fit the rounded corner size of the panel’s internal view?
  2. How and what is the smooth transition effect of the widget launching the App?

In this paper, the DEMO

NewAppWidget

Reference documentation

Official propaganda

Official Introduction Document

The official Sample

Recommended reading

The new app splash screen on Android 12.

Fully retrieve Backup features that Android developers tend to overlook.

Why is CameraX recommended?