如何在app小部件中设置Android AnalogClock的时间?

时间:2012-07-29 14:19:45

标签: android layout widget components android-appwidget

如何设置放置在应用小部件中的Android AnalogClock的自定义时间?

作为替代方案,我想覆盖默认的AnalogClock来设置代码的时间。换句话说,我会创建自定义时钟,它从默认的View或AnalogClock扩展。然后将我的自定义时钟放在窗口小部件布局UI上。

这可能吗?我担心RemoteViews会限制我们自己的自定义组件。

更新 这是错误日志我首先遵循vArDo给出的解决方案。

enter image description here

2 个答案:

答案 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); 应用小部件的屏幕截图,其时间设置仅为一次(注意状态栏中显示的模拟时钟时间和时间之间的差异):

enter image description here

答案 1 :(得分:2)

AnalogClock无法将时间设置为当前时间以外的其他时间。这个小部件是非常封装的。您可以轻松地更改它的外观(使用R.styleable中的属性在XML中,但是没有简单的方法来改变它的行为。

我可能会复制AnalogClock.java(可能将其放在单独的包中,例如com.example.widget)和本地副本clock_dial,{{ 3}},clock_hand_hour(注意:要获得最佳效果,您必须为所有密度创建本地副本:clock_hand_minuteldpimdpi和{{3} })。有了这些以及对代码的微小更改,您将能够获得所需的行为。

使用自定义drawables WITH属性可从XML布局

设置

为了能够从XML布局文件设置dialhand_hourhand_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布局设置的属性的自定义drawable

如果您不需要通过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>

如果您需要更多关于此解决方案的帮助,我可以使用更多建议解决方案代码进一步更新我的答案。