如何设置放置在应用小部件中的Android AnalogClock
的自定义时间?
作为替代方案,我想覆盖默认的AnalogClock来设置代码的时间。换句话说,我会创建自定义时钟,它从默认的View或AnalogClock扩展。然后将我的自定义时钟放在窗口小部件布局UI上。
这可能吗?我担心RemoteViews会限制我们自己的自定义组件。
更新 这是错误日志我首先遵循vArDo给出的解决方案。
答案 0 :(得分:6)
在app小部件中包含自定义视图是不可能的。 According to documentation只能在布局中使用小的预定义视图子集。所以,正如您所提到的,我们无法创建您自己的AnalogClock
视图(正如我在其他答案中所示)并将其包含在应用小部件中。
但是还有其他方法可用于在app小部件中创建自定义AnalogClock
。在这种实现中,直接在AnalogClock
中设置时间应该不是问题。
实现此目的的一种方法是将自定义AnalogClock
绘制到ImageView
(这是应用小部件中允许的视图之一)。重复PendingIntent
正在使用Service
每60秒吸引ImageView
次(通过RemoteViews
)。
请注意,每隔60秒通过Service
调用RemoteViews
动作来更新应用小部件的电池就不那么轻巧了。在 Jelly Bean 示例实现中,夜间使用2%的电池(屏幕关闭,平均网络强度)。但是,我每隔15秒就更新一次屏幕,这在模拟时钟方面是不必要的。因此粗略估计,每晚更新一次的应用程序小部件将在晚上消耗大约0.5%的电量 - 这对您的用户来说可能很重要。
首先,您必须创建Service
,它将吸引您视图中的ImageView
。绘图代码几乎取自AnalogClock
实现,并根据需要重新排列。首先,没有检查时间是否已经改变 - 这是因为Service
只有在我们决定时才会被调用(例如每60秒)。第二个变化是我们从方法中的资源创建Drawable
。
另一个变化是代码使用Bitmap
:
Canvas
// 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);
通过ImageView
将更新提取到RemoteViews
。汇总更改,然后将其推送到主屏幕上的应用小部件 - 有关详细信息,请参阅docs。在我们的例子中,只有一个变化,它通过特殊方法完成:
remoteViews.setImageViewBitmap(R.id.imageView1, appWidgetBitmap);
请记住,要使代码工作,您必须声明绘制模拟时钟的可用空间。根据{{1}}中声明的内容,您可以确定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);
子类here的完整代码。
同样在顶部,您将找到负责设置将显示的时间的代码 - 根据需要进行修改:
Service
另外,请不要忘记在 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;
文件中声明此Service
和app小部件,例如:
AndroidManifest.xml
有关使用服务更新应用小工具的最重要信息,请参阅this blog post。
计划更新是在 <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"/>
子类的onUpdate()
方法中完成的 - 在AppWidgetProvider
文件中声明的是on。当从主屏幕移除app小部件时,我们还需要删除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);
}
}
方法的最后一行安排重复事件每60秒发生一次,第一次更新立即发生。
onUpdate()
使用上面的代码,只调用一次服务的最简单方法是及时安排单个事件而不是重复事件。使用以下行简单替换AnalogClock
来电:
alarmManager.setRepeating()
以下是自定义alarmManager.set(AlarmManager.RTC, time.getTime().getTime(), service);
应用小部件的屏幕截图,其时间设置仅为一次(注意状态栏中显示的模拟时钟时间和时间之间的差异):
答案 1 :(得分:2)
AnalogClock
无法将时间设置为当前时间以外的其他时间。这个小部件是非常封装的。您可以轻松地更改它的外观(使用R.styleable中的属性在XML中,但是没有简单的方法来改变它的行为。
我可能会复制AnalogClock.java(可能将其放在单独的包中,例如com.example.widget
)和本地副本clock_dial,{{ 3}},clock_hand_hour(注意:要获得最佳效果,您必须为所有密度创建本地副本:clock_hand_minute,ldpi,mdpi和{{3} })。有了这些以及对代码的微小更改,您将能够获得所需的行为。
为了能够从XML布局文件设置dial
,hand_hour
和hand_minute
drawable,您需要将这些属性声明为自定义小部件的样式。
注意:在以下示例中,我创建了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
表示值只能是对其他现有元素的引用,例如drawables(@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_width="match_parent"
android:layout_height="match_parent" >
<com.example.widget.TimeSettableAnalogClock
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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();
}
注意,我几乎只删除了com.android.internal.
对任何 drawable 或 styleable 的引用。其余部分与hdpi中相同。
如果您不需要通过XML设置属性(例如,您的所有模拟时钟在每个地方看起来都相同),您可以简单地解决方案。您只需要本地的drawables副本(如开头所述),不需要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_width="match_parent"
android:layout_height="match_parent" >
<com.example.widget.TimeSettableAnalogClock
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
如果您需要更多关于此解决方案的帮助,我可以使用更多建议解决方案代码进一步更新我的答案。