Widget Sizing & Previews (Android)

Widget Sizing

Grid Cells vs Density-Independent Pixels (dp)

Android widgets are sized two different ways depending on the OS version. Android 12+ places widgets using targetCellWidth/targetCellHeight, in grid cells. Android 11 and older don't understand those attributes at all and place widgets using minWidth/minHeight, in dp, so Voltra always emits both.

When minWidth/minHeight aren't set explicitly, they're derived from the widget's cell size using Google's published figures for a 5x4 handset grid:

  • minWidth (dp) = (cellCount × 73) - 16
  • minHeight (dp) = (cellCount × 66) - 15

For example, 2 cells comes out to 130 dp wide and 117 dp tall, and 4 cells to 276 dp wide and 249 dp tall.

Cell size varies by device, launcher and orientation, so this conversion is only an approximation, and it only matters on Android 11 and older — Android 12+ sizes the widget from targetCellWidth/ targetCellHeight directly. If you need precise placement on Android 11 and older, set minWidth and minHeight explicitly in dp instead of relying on the conversion.

Standard Dimensions

FamilyCellsDefault DPTypical Use
Small2×1130 × 51Quick glance info
Medium2×2130 × 117Main widget size
Large4×2276 × 117Rich content
Extra Large4×4276 × 249Complex layouts

Resize Bounds

Voltra can also emit minResizeWidth, minResizeHeight, maxResizeWidth, and maxResizeHeight in the generated appwidget-provider XML, all optional and all in dp:

  • minResizeWidth/minResizeHeight set the smallest size the user can resize the widget to, and are supported on all Android versions.
  • maxResizeWidth/maxResizeHeight set the largest size the user can resize the widget to. They were introduced in Android 12 and are ignored on older versions.

These bounds only take effect on axes the widget can resize along, per resizeMode — a maxResizeHeight on a widget with resizeMode: "horizontal" has no effect, for instance. Set only the ones you need; Voltra emits exactly what you provide and nothing otherwise, so existing widgets are unaffected.

Android also ignores a bound that contradicts the widget's minimum size: minResizeWidth has no effect if it's greater than minWidth, and maxResizeWidth has no effect if it's smaller than minWidth — likewise for the height pair against minHeight. Since minWidth/minHeight are often derived from targetCellWidth/targetCellHeight rather than written explicitly (see above), Voltra warns at build time when it detects one of these contradictions, naming the attribute, rather than failing the build.

For example:

{
  "widgets": [
    {
      "id": "weather",
      "displayName": "Weather Widget",
      "targetCellWidth": 2,
      "targetCellHeight": 2,
      "resizeMode": "horizontal|vertical",
      "minResizeWidth": 110,
      "minResizeHeight": 100,
      "maxResizeWidth": 276,
      "maxResizeHeight": 249
    }
  ]
}

This lets the widget resize down to 110×100 dp, and up to 276×249 dp on Android 12+.

Widget Picker Previews

When users add a widget to their home screen, Android displays a preview in the widget picker. Voltra supports three preview methods, with automatic fallback:

  1. previewLayout (Android 12+) - Custom XML layout for scalable preview
  2. previewImage (All versions) - Static image or auto-generated layout
  3. Default - System placeholder layout

Using previewImage

Static preview image for all Android versions:

{
  "widgets": [
    {
      "id": "weather",
      "displayName": "Weather Widget",
      "targetCellWidth": 2,
      "targetCellHeight": 2,
      "previewImage": "./assets/widgets/weather-preview.png"
    }
  ]
}

When only previewImage is specified, Voltra automatically generates a layout that displays the image with proper scaling.

Using previewLayout

Custom XML layout for scalable previews (Android 12+):

{
  "widgets": [
    {
      "id": "todos",
      "displayName": "Todo Widget",
      "targetCellWidth": 2,
      "targetCellHeight": 2,
      "previewLayout": "./assets/widgets/todos-preview.xml"
    }
  ]
}

Example todos-preview.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    android:background="#FFFFFF">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Todo List"
        android:textSize="18sp"
        android:textStyle="bold" />

    <!-- Add more layout elements here -->

</LinearLayout>

The preview layout is rendered at the widget's target size and displayed in the widget picker.

Combined Preview Setup

For best results across Android versions, provide both:

{
  "widgets": [
    {
      "id": "weather",
      "displayName": "Weather Widget",
      "targetCellWidth": 2,
      "targetCellHeight": 2,
      "previewImage": "./assets/widgets/weather-preview.png",
      "previewLayout": "./assets/widgets/weather-preview.xml",
      "initialStatePath": "./widgets/weather-initial.tsx"
    }
  ]
}

This uses previewLayout on Android 12+, falls back to previewImage on Android 11 and earlier, and shows actual widget content on the home screen via initialStatePath once available.

Need React or React Native expertise you can count on?