获取我的主屏幕小部件的大小。

17

我只想知道当前小部件的大小。 我找到了许多设置最小尺寸的问题,但我不想进行设置。 相反,我想显示那些适合小部件的信息。

如果小部件太小,我需要隐藏一些内容,但我需要知道尺寸才能做到这一点。 我查看了一些类的源代码,例如 AppWidgetProvider,甚至文档。 通常只有关于最小或最大尺寸的参考资料,但从未提及当前大小。

请指向正确的资源。

6个回答

12

很不幸,目前还没有API可以从AppWidget-Host获取此信息。

只有在调整大小事件(Android 4.1+)中才能获得当前尺寸信息,并且只有支持此功能的启动器才能获得信息(例如Sony XPeria Launcher以及一些第三方启动器都不支持)。

我遵循了这个堆栈溢出线程和Android开发文档:

确定主屏幕的应用程序小部件空间网格大小

https://developer.android.com/reference/android/appwidget/AppWidgetProvider.html#onAppWidgetOptionsChanged(android.content.Context,android.appwidget.AppWidgetManager,int,android.os.Bundle)

以下是我从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;
        }

在Android 4.1及以上版本,您现在可以获得当前设备方向下小部件的尺寸(以DIP为单位)。要知道您的小部件宽度和高度有多少个主屏幕网格单元格,您需要将此数值除以单元格大小。默认值为74dip,但使用不同的设备或启动器可能会有所不同。允许更改网格的自定义启动程序对小部件开发人员来说非常麻烦。

在Android<4.1中,无法获取当前窗口小部件的尺寸。因此,最好将API级别设置为JellyBean,以避免麻烦。当我开始制作我的小部件应用程序时,两年前还没有这个选择。

通知您重新绘制小部件的方向更改是另一个话题。

重要提示:如果允许用户拥有多个您的小部件,则必须根据它们的Id为每个小部件执行此计算!


2
我看了一下像MIN_WIDTH和MAX_WIDTH这样的字段,我认为这些字段只包含边界而不是实际值。我想知道这些值在调整大小时是否会改变。你能解释一下吗?如果不能,我稍后自己试试。 - rekire
1
providerInfo.minWidth/maxWidth不会改变,因为它们是在xml资源中定义的。实际上,你只能从中获取预定义的minWidth,maxWidth仅由AppWidgetHost(即Launcher)用于限制小部件大小。但是,在首次创建小部件时以及每个调整大小事件时,AppWidgetOptions包含当前的最小和最大宽度,即纵向和横向宽度。 - Jürgen 'Kashban' Wahlmann
1
这听起来有点奇怪。你说在调整大小和创建时可以读取宽度/高度。你上面的示例代码使用变量mWidgetLandHeight,该变量指向小部件的最小高度,该高度在xml中定义。那么小部件的当前大小在哪里?我看不到。如果您愿意,可以加入此聊天室以避免太多评论。 - rekire
1
Widget的当前大小在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); - Jürgen 'Kashban' Wahlmann

11

这是一个用于获取窗口小部件像素大小的类助手。

请注意,您应该使用应用程序上下文,否则在小部件旋转时方向将不正确。

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()

}

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)
        }
    }
}

根据1个单元格=40dp的规定,可以确定大小。请参考https://developer.android.com/guide/practices/ui_guidelines/widget_design.html#anatomy_determining_size。原始答案翻译为“最初的回答”。

1
你可以在小部件提供程序中添加一个方法,并且在调整大小时获取您的小部件尺寸,适用于所有从API 16开始的Android版本。
    @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);

    }

至于新增的小部件,您可以通过从其布局中获取其大小并将其乘以其列数来获得其尺寸:

    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 ();

获取小部件列数的方法来自于官方 Android 指南


0

根据Deni的答案,我目前得出了这样一个解决方案,

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() }

我有一个像这样的 .dp 文件,在上面的代码中使用了它,

 val Number.dp: Float get() = this.toFloat() * Resources.getSystem().displayMetrics.density

我之前不知道有Resources.getSystem()这个方法,很酷。顺便说一下,在扩展属性中可以省略this关键字。 - rekire

0

我们无法从OPTION_APPWIDGET_MAX_WIDTH和OPTION_APPWIDGET_MAX_HEIGHT获取小部件大小,因为它会返回错误的值。

这个解决方案基于@Zanael的答案。我添加了一个增量值,因为在某些设备上,cellSize可能不在minSize和maxSize范围内。

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
    )
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接