【问题标题】:How to set time to Android AnalogClock in app widget?如何在应用小部件中为 Android AnalogClock 设置时间?
【发布时间】:2012-07-27 10:31:11
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: android layout widget components android-appwidget


    【解决方案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 秒)。秒变是我们从方法中的资源创建Drawables。

    另一个变化是代码使用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_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 应用小部件的屏幕截图,该小部件仅设置了一次时间(注意模拟时钟时间与状态栏中显示的时间之间的差异):

    【讨论】:

    • 是的,我喜欢这个答案。而且我不需要每分钟刷新时钟。因为我想在这个自定义时钟上显示固定时间。我还想知道您是如何设法每 15 秒更新一次屏幕以节省电池寿命的。
    • @Halim 我添加了有关解决方案的详细信息。我还描述了如何修改代码,以便 Service 仅调用一次(根据您的评论)。但是,我不知道您所说的省电是什么意思 :) 我想指出的是,每 15 秒更新一次会消耗更多电池电量,而每 60 秒更新一次,这很自然 :) 我写了大约 15 的唯一原因秒间隔是因为这是我在手机上尝试过的。只是想说明这对手机的电池寿命有何影响。
    【解决方案2】:

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

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

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

    为了能够从 XML 布局文件中设置 dialhand_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();
    }
    

    注意,我几乎只是从对任何 drawablestyleable 的引用中删除了 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_width="match_parent"
        android:layout_height="match_parent" >
    
        <com.example.widget.TimeSettableAnalogClock
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    
    </RelativeLayout>
    

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

    【讨论】:

    • 我试过这个解决方案。我从 Android 复制了 AnalogClock 源代码并将其放在我自己的包中。但我坚持使用 com.android.internal.R.styleable 我不知道在 Android 源代码上哪里可以找到它。你能帮忙吗?
    • @Halim com.android.internal.R.styleable 用于处理在 XML 中为 AnalogClock 设置的属性(例如 dialhand_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 :)
    猜你喜欢
    • 2021-05-23
    • 2015-01-12
    • 2013-05-23
    • 1970-01-01
    • 2020-02-22
    • 2016-10-12
    • 2018-07-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多