0

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

作为替代方案,我正在考虑覆盖默认的 AnalogClock 以通过代码设置时间。或者换句话说,我将创建从默认视图或模拟时钟扩展的自定义时钟。然后将我的自定义时钟放在小部件布局 UI 上。

这可能吗?恐怕我们受限于 RemoteViews 来拥有我们自己的自定义组件。

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

在此处输入图像描述

4

2 回答 2

6

介绍

无法在应用小部件中包含自定义视图。根据文档,布局中只能使用少量预定义的视图子集。因此,正如您所提到的,您无法创建自己的AnalogClock视图(正如我在其他答案中所展示的那样)并将其包含在应用小部件中。

但是,还有其他方法可用于AnalogClock在应用小部件中创建自定义。在这样的实现中,直接设置时间AnalogClock应该不是问题。

简短的回答

实现此目的的方法之一是将自定义绘制AnalogClockImageView(这是应用程序小部件中允许的视图之一)。重复PendingIntent用于每 60 秒Service绘制一次(通过)。ImageViewRemoteViews

电池使用注意事项

请记住,每 60 秒调用Service一次更新应用程序小部件的操作RemoteViews对电池的消耗并不是那么轻巧。Jelly Bean示例实现在夜间使用了 2% 的电池(屏幕关闭,平均网络强度)。但是,我每 15 秒更新一次屏幕,这对于模拟时钟来说是不必要的。因此粗略估计,每分钟更新一次的应用小部件在夜间会消耗大约 0.5% 的电池电量——这对您的用户来说可能很重要。

解决方案详情

更改绘图服务

首先,您必须创建ServiceImageView在您的视图中呈现的内容。绘图代码几乎取自AnalogClock实现并根据需要重新排列。首先,没有检查时间是否已经改变——这是因为Service只有当我们决定时才会调用(例如每 60 秒)。秒变是我们Drawable从方法中的资源创建s。

另一个变化是代码将模拟时钟引入本地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);

更新被引入ImageViewvia RemoteViews。更改被聚合,然后推送到主屏幕上的应用程序小部件 - 有关详细信息,请参阅文档)。在我们的例子中,只有一个变化,它是通过特殊方法完成的:

    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粘贴了子类的完整代码。

同样在顶部,您会找到负责设置将显示的时间的代码 - 根据需要进行修改:

    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在你的文件中声明这个和应用小部件AndroidManifest.xml,例如:

    <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- 在您的AndroidManifest.xml文件中声明的 on 。PendingIntent当应用小部件从主屏幕中删除时,我们还需要删除。这是在覆盖的onDisabled()方法中完成的。请记住声明将调用每个方法之一的两个操作:APPWIDGET_UPDATEAPPWIDGET_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仅设置一次时间的自定义应用小部件的屏幕截图(请注意模拟时钟时间与状态栏中显示的时间之间的差异):

在此处输入图像描述

于 2012-08-13T21:09:43.087 回答
2

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

我可能会制作AnalogClock.java的副本(将它放在单独的包中可能是个好主意,例如com.example.widget)和可绘制时钟的本地副本clock_dialclock_hand_hourclock_hand_minute(注意:为了获得最佳结果,您必须创建所有密度的本地副本:ldpimdpihdpixhdpi)。有了这些和对代码的小改动,您将能够获得您想要的行为。

使用可从 XML 布局设置的属性的自定义可绘制对象

为了能够从 XML 布局文件中设置dial和drawable,您需要将这些属性声明为可用于自定义小部件的样式hand_hourhand_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_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.了对任何drawablestyleable的引用。其余部分与AnalogClock 构造函数中的相同。

使用没有可从 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:custompart,当然你不能再设置自定义属性了:

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

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

于 2012-08-01T13:56:46.143 回答