获取我的主屏幕小部件的大小
Posted
技术标签:
【中文标题】获取我的主屏幕小部件的大小【英文标题】:Get the size of my homescreen widget 【发布时间】:2014-09-28 23:43:08 【问题描述】:我只想知道我当前的小部件有多大。我发现了很多问题来设置最小尺寸,但我不想设置它。相反,我想显示适合小部件的信息。
如果小部件太小,我需要隐藏一些东西,但我需要知道它的大小。我阅读了一些类的源代码,例如AppWidgetProvider
,甚至是文档。总是有关于最小或最大尺寸的参考,但从来没有当前尺寸。
请指点我正确的资源。
【问题讨论】:
【参考方案1】:不幸的是,仍然没有 api 可以从 AppWidget-Host 获取此信息。
您只能获得有关调整大小事件 (android 4.1+) 的当前大小信息,并且只能从支持此功能的启动器(例如 Sony XPeria Launcher 以及一些第三方启动器)获得。
我关注了这个 *** 线程和 Android 开发者文档:
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
这是我确定小部件大小的代码,取自 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,但这可能因使用不同的设备或启动器而异。允许更改网格的自定义启动器对于 Widget 开发人员来说是个难题。
在 Android
获取有关方向更改以重绘小部件的通知是另一个主题。
重要提示:如果您允许用户拥有多个您的小部件,您必须根据他们的 ID 为他们每个人进行此计算!
【讨论】:
+1 为您的工作。我查看了(MIN_WIDTH 和 MAX_WIDTH)之类的字段,我认为这些字段只包含边界而不是实际值。我想知道调整大小时这些值是否会改变。你能解释一下吗?如果没有,我稍后会自己尝试。 providerInfo.minWidth/maxWidth 不会改变,因为它们是在 xml 资源中定义的。实际上,您只能从那里获得预定义的 minWidth,maxWidth 仅由 AppWidgetHost aka Launcher 用于限制小部件大小。但是,当第一次创建 Widget 并在每个调整大小事件时 AppWidgetOptions 包含当前的最小和最大宽度,也就是纵向和横向宽度。 这听起来有点牵强。你说宽度/高度在调整大小和创建时是可读的。您上面的示例代码使用变量mWidgetLandHeight
,它指向例如到 xml 中定义的小部件的 minHeight。那么小部件的当前大小在哪里?我看不到。如果你喜欢你可以加入this chat here以避免太多的cmets。
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);【参考方案2】:
这是获取小部件大小(以像素为单位)的类助手
请注意,您应该使用应用程序上下文,否则小部件旋转时方向将不正确
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()
【讨论】:
【参考方案3】:获取列/行数 (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
【讨论】:
【参考方案4】:您可以向小部件提供程序添加一个方法,并在您为从 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);
对于新添加的小部件,您可以通过从其布局中获取其大小并将其乘以它的 cols num 来获得它的 wizes:
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 ();
获取小部件 cols num 取自官方 Android guidelines。
【讨论】:
【参考方案5】:我们无法从 OPTION_APPWIDGET_MAX_WIDTH 和 OPTION_APPWIDGET_MAX_HEIGHT 获取小部件大小,它将返回错误值。
此解决方案基于@Zanael 答案。我添加了 delta 值,因为在某些设备上 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
)
【讨论】:
【参考方案6】:基于Deni's answer,我目前最终得到了这样的解决方案,
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
。以上是关于获取我的主屏幕小部件的大小的主要内容,如果未能解决你的问题,请参考以下文章