Get the size of my homescreen widget
Asked Answered
A

6

18

I just want to know how big my current widget is. I found tons of questions to set the minimum size, but I don't want to set it. Instead I want to show that informations which simply fit on the widget.

If the widget is too small I need to hide some things, but I need to know the size for that. I read the source code of some classes like the AppWidgetProvider, even the documentation. There are allways just references about the minimum or maximum size, but never the current size.

Please point me to the right resource.

Antonelli answered 6/8, 2014 at 6:29 Comment(0)
L
12

Unfortunately there still is no api to acquire this info from The AppWidget-Host.

You only get a current size info on resize events (Android 4.1+) and only from launchers which support this (Sony XPeria Launcher for example does not, as well as some third party launchers).

I followed this stackoverflow thread and the Android Developer Documentation:

Determine the homescreen's appwidgets space grid size

https://developer.android.com/reference/android/appwidget/AppWidgetProvider.html#onAppWidgetOptionsChanged%28android.content.Context,%20android.appwidget.AppWidgetManager,%20int,%20android.os.Bundle%29

Here is my code to determine the widget size, taken from Picture Calendar 2014 (Play Store):

        /* Get Device and Widget orientation. 
           This is done by adding a boolean value to 
           a port resource directory like values-port/bools.xml */

        mIsPortraitOrientation = getResources().getBoolean(R.bool.isPort);

        // Get min dimensions from provider info
        AppWidgetProviderInfo providerInfo = AppWidgetManager.getInstance(
            getApplicationContext()).getAppWidgetInfo(appWidgetId);

        // Since min and max is usually the same, just take min
        mWidgetLandWidth = providerInfo.minWidth;
        mWidgetPortHeight = providerInfo.minHeight;
        mWidgetPortWidth = providerInfo.minWidth;
        mWidgetLandHeight = providerInfo.minHeight;

        // Get current dimensions (in DIP, scaled by DisplayMetrics) of this
        // Widget, if API Level allows to
        mAppWidgetOptions = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
            mAppWidgetOptions = getAppWidgetoptions(mAppWidgetManager,
                    appWidgetId);

        if (mAppWidgetOptions != null
                && mAppWidgetOptions
                        .getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) > 0) {
            if (D.DEBUG_SERVICE)
                Log.d(TAG,
                        "appWidgetOptions not null, getting widget sizes...");
            // Reduce width by a margin of 8dp (automatically added by
            // Android, can vary with third party launchers)

            /* Actually Min and Max is a bit irritating, 
               because it depends on the homescreen orientation
               whether Min or Max should be used: */

            mWidgetPortWidth = mAppWidgetOptions
                    .getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
            mWidgetLandWidth = mAppWidgetOptions
                    .getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
            mWidgetLandHeight = mAppWidgetOptions
                    .getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
            mWidgetPortHeight = mAppWidgetOptions
                    .getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);

            // Get the value of OPTION_APPWIDGET_HOST_CATEGORY
            int category = mAppWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1);

            // If the value is WIDGET_CATEGORY_KEYGUARD, it's a lockscreen
            // widget (dumped with Android-L preview :-( ).
            mIsKeyguard = category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;

        } else {
            if (D.DEBUG_SERVICE)
                Log.d(TAG,
                        "No AppWidgetOptions for this widget, using minimal dimensions from provider info!");
            // For some reason I had to set this again here, may be obsolete
            mWidgetLandWidth = providerInfo.minWidth;
            mWidgetPortHeight = providerInfo.minHeight;
            mWidgetPortWidth = providerInfo.minWidth;
            mWidgetLandHeight = providerInfo.minHeight;
        }

        if (D.DEBUG_SERVICE)
            Log.d(TAG, "Dimensions of the Widget in DIP: portWidth =  "
                    + mWidgetPortWidth + ", landWidth = " + mWidgetLandWidth
                    + "; landHeight = " + mWidgetLandHeight
                    + ", portHeight = " + mWidgetPortHeight);

        // If device is in port oriantation, use port sizes
        mWidgetWidthPerOrientation = mWidgetPortWidth;
        mWidgetHeightPerOrientation = mWidgetPortHeight;

        if (!mIsPortraitOrientation)
        {
            // Not Portrait, so use landscape sizes
            mWidgetWidthPerOrientation = mWidgetLandWidth;
            mWidgetHeightPerOrientation = mWidgetLandHeight;
        }

On Android 4.1+ you now have the current size of the Widget in DIP for the current device orientation. To know how many homescreen grid cells your widget is wide and high, you have to divide this by cell size. Default is 74dip, but this may vary by using different devices or launchers. Custom Launchers which allow to change the grid are a pain in the ass for Widget developers.

On Android < 4.1 there is no way to get the current widget size. So better set your API level to JellyBean to avoid trouble. Should be ok now... two years ago when I started with my Widget App that was not an option.

Getting notified on orientation changes to redraw the widget(s) is another topic.

Important: If you allow the user to have multiple Widgets of yours, you have to do this calculation for everyone of them, based on their Id!

Leroy answered 6/8, 2014 at 8:7 Comment(4)
+1 for your work. I took a look at that fields like (MIN_WIDTH and MAX_WIDTH) I thought that this fields just contain the bounds and not the actual values. I would like to know if that values change on resizing. Could you explain that? If not I'll try it later myself.Antonelli
providerInfo.minWidth/maxWidth won't change since they are defined in xml resources. Actually you only get the predefined minWidth out of there, maxWidth is only used by the AppWidgetHost aka Launcher to limit the Widget Size. However, when first creating the Widget and on every resize event AppWidgetOptions contain the current min and max width, aka portrait and landscape width.Wearisome
That sounds a little wired. You say the width/height is readable on resize and creation. Your example code above uses the variable mWidgetLandHeight which points e.g. to the minHeight of the widget which is defined in the xml. So where is the current size of the widget? I cannot see that. If you like you can join this chat here to avoid too many comments.Antonelli
Current size of the Widget is in AppWidgetOptions: mWidgetPortWidth = mAppWidgetOptions .getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH); mWidgetLandWidth = mAppWidgetOptions .getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH); mWidgetLandHeight = mAppWidgetOptions .getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT); mWidgetPortHeight = mAppWidgetOptions .getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);Wearisome
A
11

Here is class helper to get widget size in pixels

Note that you should use Application context, otherwise orientation will be incorrect on widget rotation

class WidgetSizeProvider(
    private val context: Context // Do not pass Application context
) {

    private val appWidgetManager = AppWidgetManager.getInstance(context)

    fun getWidgetsSize(widgetId: Int): Pair<Int, Int> {
        val isPortrait = context.resources.configuration.orientation == ORIENTATION_PORTRAIT
        val width = getWidgetWidth(isPortrait, widgetId)
        val height = getWidgetHeight(isPortrait, widgetId)
        val widthInPx = context.dip(width)
        val heightInPx = context.dip(height)
        return widthInPx to heightInPx
    }

    private fun getWidgetWidth(isPortrait: Boolean, widgetId: Int): Int =
        if (isPortrait) {
            getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
        } else {
            getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
        }

    private fun getWidgetHeight(isPortrait: Boolean, widgetId: Int): Int =
        if (isPortrait) {
            getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
        } else {
            getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
        }

    private fun getWidgetSizeInDp(widgetId: Int, key: String): Int =
        appWidgetManager.getAppWidgetOptions(widgetId).getInt(key, 0)

    private fun Context.dip(value: Int): Int = (value * resources.displayMetrics.density).toInt()

}
Alteration answered 22/10, 2019 at 10:2 Comment(0)
R
2

Get cols/rows count (Kotlin):

val options = appWidgetManager.getAppWidgetOptions(appWidgetId)
val minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
val maxHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)

val oneCellHeight = ViewExtensions.convertDpToPx(40f * 1, context).toInt() - (30 * 1)
val twoCellHeight = ViewExtensions.convertDpToPx(40f * 2, context).toInt() - (30 * 2)
val threeCellHeight = ViewExtensions.convertDpToPx(40f * 3, context).toInt() - (30 * 3)
val fourCellHeight = ViewExtensions.convertDpToPx(40f * 4, context).toInt() - (30 * 4)

when {
    oneCellHeight in (minHeight..maxHeight) -> {
        // 1 cell
    }
    twoCellHeight in (minHeight..maxHeight) -> {
        // 2 cells
    }
    threeCellHeight in (minHeight..maxHeight) -> {
        // 3 cells
    }
    fourCellHeight in (minHeight..maxHeight) -> {
        // 4 cells
    }
    else -> {
        // More
    }
}

class ViewExtensions {
    companion object {
        fun convertDpToPx(sp: Float, context: Context): Float {
            return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sp, context.resources.displayMetrics)
        }
    }
}

Based on, that 1 cell = 40dp

https://developer.android.com/guide/practices/ui_guidelines/widget_design.html#anatomy_determining_size

Ringlet answered 22/2, 2019 at 14:56 Comment(0)
R
1

You can add an method to your widget provider and get your widget sizes when you resize it for all versions of Android started from API 16:

    @Override
    public void onAppWidgetOptionsChanged (Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle widgetInfo) {

        int width = widgetInfo.getInt (AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
        int height = widgetInfo.getInt (AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);

    }

And as for new added widget you can get its wizes by getting its sizes from its layout and multiply it to its cols num:

    AppWidgetProviderInfo widgetInfo = AppWidgetManager.getInstance (context).getAppWidgetInfo (widgetId);

    private int getColsNum (int size) {
        return (int) Math.floor ((size - 30) / 70);
    }

    public int getWidthColsNum () {
        return getColsNum (widgetInfo.minWidth);
    }

    public int getHeightColsNum () {
        return getColsNum (widgetInfo.minHeight);
    }

    int width = widgetInfo.minWidth * getWidthColsNum ();
    int height = widgetInfo.minHeight * getHeightColsNum ();

Getting widget cols num was taken from official Android guidelines.

Rupertruperta answered 17/7, 2018 at 20:47 Comment(0)
P
0

We can't get widget size from OPTION_APPWIDGET_MAX_WIDTH and OPTION_APPWIDGET_MAX_HEIGHT it will return wrong values.

This solution based on @Zanael answer. And I add delta value because on some device cellSize can be not in minSize and maxSize range.

private fun getWidgetSize(minSize: Int, maxSize: Int, context: Context): Int {
    var delta = Int.MAX_VALUE
    var lastDeltaRecord = -1
    for (i in 1..30) {
        val cellSize = convertDpToPx(40f * i, context).toInt() - (30 * i)
        if (cellSize in minSize..maxSize) {
            return i
        }
        val deltaMin = abs(minSize - cellSize)
        val deltaMax = abs(cellSize - maxSize)
        if (delta > deltaMin) {
            delta = deltaMin
            lastDeltaRecord = i
        }
        if (delta > deltaMax) {
            delta = deltaMax
            lastDeltaRecord = i
        }
    }
    return lastDeltaRecord
}

private fun convertDpToPx(sp: Float, context: Context): Float {
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        sp,
        context.resources.displayMetrics
    )
}
Planoconvex answered 21/5, 2020 at 11:16 Comment(0)
A
0

Based on Deni's answer, I currently ended up with a solution like this,

val manager = AppWidgetManager.getInstance(context)
...
val isPortrait = resources.configuration.orientation == ORIENTATION_PORTRAIT
val (width, height) = listOf(
    if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH
    else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
    if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT
    else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
).map { manager.getAppWidgetOptions(widgetId).getInt(it, 0).dp.toInt() }

I have a .dp like this which is in use in above code,

 val Number.dp: Float get() = this.toFloat() * Resources.getSystem().displayMetrics.density
Agnella answered 6/9, 2021 at 22:13 Comment(1)
I was not aware of Resources.getSystem() this is cool, by the way you can omit in the extension property the this.Antonelli

© 2022 - 2024 — McMap. All rights reserved.