如何在应用小部件中为 Android AnalogClock 设置时间?
Posted
技术标签:
【中文标题】如何在应用小部件中为 Android AnalogClock 设置时间?【英文标题】:How to set time to Android AnalogClock in app widget? 【发布时间】:2012-07-27 10:31:11 【问题描述】:如何将自定义时间设置为android AnalogClock
放置在应用小部件中?
作为替代方案,我正在考虑覆盖默认的 AnalogClock 以通过代码设置时间。或者换句话说,我将创建从默认视图或模拟时钟扩展的自定义时钟。然后将我的自定义时钟放在小部件布局 UI 上。
这可能吗?恐怕我们受限于 RemoteViews 来拥有我们自己的自定义组件。
更新: 这是我最初遵循 vArDo 给出的解决方案的错误日志。
【问题讨论】:
【参考方案1】:简介
无法在应用小部件中包含自定义视图。 According to documentation 在布局中只能使用一小部分预定义的视图子集。因此,正如您所提到的,您无法创建自己的 AnalogClock
视图(正如我在其他答案中所展示的那样)并将其包含在应用小部件中。
但是,还有其他方法可用于在应用小部件中创建自定义 AnalogClock
。在这样的实现中直接在AnalogClock
中设置时间应该没有问题。
简答
实现此目的的方法之一是将自定义AnalogClock
绘制到ImageView
(这是应用小部件中允许的视图之一)。重复 PendingIntent
是使用 Service
每 60 秒绘制一次 ImageView
(通过 RemoteViews
)。
电池使用注意事项
请记住,每 60 秒通过RemoteViews
调用Service
操作以更新应用程序小部件 对电池的消耗并不那么轻。 Jelly Bean 示例实现在夜间使用了 2% 的电池(屏幕关闭,平均网络强度)。但是,我每 15 秒更新一次屏幕,这对于模拟时钟来说是不必要的。因此粗略估计,每分钟更新一次的应用小部件在夜间会消耗大约 0.5% 的电池电量 - 这对您的用户来说可能意义重大。
解决方案详情
更改绘图服务
首先,您必须创建Service
,它将绘制到您视图中的ImageView
。绘图代码几乎取自AnalogClock
实现并根据需要重新排列。首先,没有检查时间是否已更改 - 这是因为 Service
只有在我们也做出决定时才会被调用(例如每 60 秒)。秒变是我们从方法中的资源创建Drawable
s。
另一个变化是代码使用Canvas
将模拟时钟引入本地Bitmap
:
// Creating Bitmap and Canvas to which analog clock will be drawn.
Bitmap appWidgetBitmap = Bitmap.createBitmap(availableWidth, availableHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(appWidgetBitmap);
通过RemoteViews
将更新引入ImageView
。更改会被汇总,然后推送到主屏幕上的应用小部件中 - 有关详细信息,请参阅 docs)。在我们的例子中,只有一个变化,它是通过特殊方法完成的:
remoteViews.setImageViewBitmap(R.id.imageView1, appWidgetBitmap);
请记住,要使代码正常工作,您必须声明可用于绘制模拟时钟的可用空间。根据widget_provider.xml
中声明的内容,您可以确定ImageView
的尺寸。请注意,在绘图之前,您必须将dp
单位转换为px
单位:
// You'll have to determine tour own dimensions of space to which analog clock is drawn.
final int APP_WIDGET_WIDTH_DP = 210; // Taken from widget_provider.xml: android:minWidth="210dp"
final int APP_WIDGET_HEIGHT_DP = 210; // Taken from widget_provider.xml: android:minHeight="210dp"
final int availableWidth = (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, APP_WIDGET_WIDTH_DP, r.getDisplayMetrics()) + 0.5f);
final int availableHeight = (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, APP_WIDGET_HEIGHT_DP, r.getDisplayMetrics()) + 0.5f);
我已经粘贴了Service
子类here的完整代码。
您还可以在顶部找到负责设置显示时间的代码 - 根据需要进行修改:
mCalendar = new Time();
// Line below sets time that will be displayed - modify if needed.
mCalendar.setToNow();
int hour = mCalendar.hour;
int minute = mCalendar.minute;
int second = mCalendar.second;
mMinutes = minute + second / 60.0f;
mHour = hour + mMinutes / 60.0f;
另外不要忘记在您的AndroidManifest.xml
文件中声明此Service
和应用小部件,例如:
<receiver
android:name="TimeSettableAnalogClockAppWidget"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.appwidget.action.APPWIDGET_DISABLED"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_provider"/>
</receiver>
<service android:name="TimeSettableAnalogClockService"/>
this blog post 中简要介绍了有关使用服务更新应用小部件的最重要信息。
调用绘图
计划更新是在您的AppWidgetProvider
子类的onUpdate()
方法中完成的——在您的AndroidManifest.xml
文件中声明的方法。当应用小部件从主屏幕移除时,我们还需要移除 PendingIntent
。这是在重写的onDisabled()
方法中完成的。请记住声明将调用每个方法之一的两个操作:APPWIDGET_UPDATE
和 APPWIDGET_DISABLED
。请参阅上述AndroidManifest.xml
的摘录。
package com.example.anlogclocksettimeexample;
import java.util.Calendar;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
public class TimeSettableAnalogClockAppWidget extends AppWidgetProvider
private PendingIntent service = null;
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
// super.onUpdate(context, appWidgetManager, appWidgetIds);
final AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
final Calendar time = Calendar.getInstance();
time.set(Calendar.SECOND, 0);
time.set(Calendar.MINUTE, 0);
time.set(Calendar.HOUR, 0);
final Intent intent = new Intent(context, TimeSettableAnalogClockService.class);
if (service == null)
service = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
alarmManager.setRepeating(AlarmManager.RTC, time.getTime().getTime(), 1000 * 60 /* ms */, service);
@Override
public void onDisabled(Context context)
final AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(service);
onUpdate()
方法的最后一行安排每 60 秒发生一次重复事件,并立即发生第一次更新。
只画一次AnalogClock
使用上面的代码只调用一次服务的最简单方法是及时安排单个事件,而不是重复一个。使用以下行简单替换 alarmManager.setRepeating()
调用:
alarmManager.set(AlarmManager.RTC, time.getTime().getTime(), service);
结果
以下是自定义AnalogClock
应用小部件的屏幕截图,该小部件仅设置了一次时间(注意模拟时钟时间与状态栏中显示的时间之间的差异):
【讨论】:
是的,我喜欢这个答案。而且我不需要每分钟刷新时钟。因为我想在这个自定义时钟上显示固定时间。我还想知道您是如何设法每 15 秒更新一次屏幕以节省电池寿命的。 @Halim 我添加了有关解决方案的详细信息。我还描述了如何修改代码,以便Service
仅调用一次(根据您的评论)。但是,我不知道您所说的省电是什么意思 :) 我想指出的是,每 15 秒更新一次会消耗更多电池电量,而每 60 秒更新一次,这很自然 :) 我写了大约 15 的唯一原因秒间隔是因为这是我在手机上尝试过的。只是想说明这对手机的电池寿命有何影响。【参考方案2】:
除了AnalogClock
中的当前时间之外,无法将时间设置为其他时间。这个小部件非常封装。您可以轻松更改其外观(在 XML 中使用来自 R.styleable 的属性,但没有简单的方法可以更改其行为。
我可能会制作 AnalogClock.java 的副本(将其放在单独的包中可能是个好主意,例如 com.example.widget
)和可绘制对象的本地副本 clock_dial、clock_hand_hour、clock_hand_minute(注意:为了获得最佳结果,您必须为所有密度创建本地副本:ldpi、mdpi、hdpi 和 xhdpi)。有了这些并且对代码进行了小的更改,您将能够获得您想要的行为。
使用可从 XML 布局设置属性的自定义可绘制对象
为了能够从 XML 布局文件中设置 dial
、hand_hour
和 hand_minute
可绘制对象,您需要将这些属性声明为可用于自定义小部件的样式。
注意:在以下示例中,我创建了TimeSettableAnalogClock
并将其放入com.example.widget
包中。
创建res/values/attrs.xml
文件,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TimeSettableAnalogClock">
<attr name="dial" format="reference"/>
<attr name="hand_hour" format="reference"/>
<attr name="hand_minute" format="reference"/>
</declare-styleable>
</resources>
format=reference
表示值只能是对其他现有元素的引用,例如可绘制对象 (@drawable/some_custom_dial
)。
现在可以在 XML 布局文件中使用自定义属性:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/lib/com.example.widget"
android:layout_
android:layout_ >
<com.example.widget.TimeSettableAnalogClock
android:layout_
android:layout_
custom:dial="@drawable/some_custom_clock_dial"
custom:hand_hour="@drawable/some_custom_clock_hand_hour"
custom:hand_minute="@drawable/some_custom_clock_hand_minute"/>
</RelativeLayout>
最后,您需要在创建TimeSettableAnalogClock
对象时处理这些自定义属性:
public TimeSettableAnalogClock(Context context, AttributeSet attrs,
int defStyle)
super(context, attrs, defStyle);
Resources r = getContext().getResources();
TypedArray a =
context.obtainStyledAttributes(
attrs, R.styleable.TimeSettableAnalogClock, defStyle, 0);
mDial = a.getDrawable(R.styleable.TimeSettableAnalogClock_dial);
if (mDial == null)
mDial = r.getDrawable(R.drawable.clock_dial);
mHourHand = a.getDrawable(R.styleable.TimeSettableAnalogClock_hand_hour);
if (mHourHand == null)
mHourHand = r.getDrawable(R.drawable.clock_hand_hour);
mMinuteHand = a.getDrawable(R.styleable.TimeSettableAnalogClock_hand_minute);
if (mMinuteHand == null)
mMinuteHand = r.getDrawable(R.drawable.clock_hand_minute);
mCalendar = new Time();
mDialWidth = mDial.getIntrinsicWidth();
mDialHeight = mDial.getIntrinsicHeight();
注意,我几乎只是从对任何 drawable 或 styleable 的引用中删除了 com.android.internal.
。其余与AnalogClock constructor相同。
使用自定义可绘制对象,但不能从 XML 布局设置属性
如果您不需要通过 XML 设置属性(例如,所有模拟时钟在每个地方看起来都一样),您可以简单地解决问题。您只需要可绘制对象的本地副本(如开头所述),不需要 attrs.xml
文件。您的构造函数应如下所示:
public TimeSettableAnalogClock(Context context, AttributeSet attrs,
int defStyle)
super(context, attrs, defStyle);
Resources r = getContext().getResources();
mDial = r.getDrawable(R.drawable.clock_dial);
mHourHand = r.getDrawable(R.drawable.clock_hand_hour);
mMinuteHand = r.getDrawable(R.drawable.clock_hand_minute);
mCalendar = new Time();
mDialWidth = mDial.getIntrinsicWidth();
mDialHeight = mDial.getIntrinsicHeight();
如您所见,要短得多。 XML布局中的用法是一样的,但是你不需要xmlns:custom
部分,当然你不能再设置自定义属性了:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_ >
<com.example.widget.TimeSettableAnalogClock
android:layout_
android:layout_/>
</RelativeLayout>
如果您在这方面需要更多帮助,我可以使用更多建议解决方案的代码进一步更新我的答案。
【讨论】:
我试过这个解决方案。我从 Android 复制了 AnalogClock 源代码并将其放在我自己的包中。但我坚持使用 com.android.internal.R.styleable 我不知道在 Android 源代码上哪里可以找到它。你能帮忙吗? @Halimcom.android.internal.R.styleable
用于处理在 XML 中为 AnalogClock
设置的属性(例如 dial
或 hand_hour
)。如果您想在自定义模拟时钟小部件中处理此类属性,则必须将它们声明为可样式化。我已经更新了我的答案以展示如何做到这一点。如果您不需要此功能(即仅在构造函数中设置的小部件自定义可绘制对象),我还添加了有关如何简化小部件代码的部分。
非常感谢您提供上述代码。但是,我还有一些问题。新类 TimeSettableAnalogClock 它是从 View 还是 AnalogClock 扩展而来的?如果它从 View 扩展,我们是否需要覆盖 onAttachedToWindow()、onDetachedFromWindow()、onMeasure(int widthMeasureSpec、int heightMeasureSpec)、onSizeChanged(int w、int h、int oldw、int oldh)、onDraw(Canvas canvas) 等方法, onTimeChanged() 等?
@Halim 您的TimeSettableAnalogClock.java
应该是AnalogClock.java
的精确 副本,但我的回答中提到的更改除外。这意味着 1. 将所有 AnalogClock 字符串替换为 TimeSettableAnalogClock 字符串。 2. 您需要所有三个构造函数(不仅是具有三个参数的构造函数) - 这应该可以解决您的膨胀错误 3. 新类扩展 View
4。 您需要覆盖您提到的所有方法(只需将它们保留在AnalogClock.java
中)。
@Halim As,你提到的那个网站——你总是可以使用Google cache :)以上是关于如何在应用小部件中为 Android AnalogClock 设置时间?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Android Studio 中为小部件设置不高度?
如何在app小部件中使用ListView并在android中填充数据?