Как установить время на Android AnalogClock в виджете приложения?

Как установить пользовательское время для Android AnalogClock размещены в виджете приложения?

В качестве альтернативы я думал переопределить AnalogClock по умолчанию, чтобы установить время с помощью кодов. Или, другими словами, я бы создал свои собственные часы, которые расширяются от просмотра по умолчанию или AnalogClock. Затем поместите мои пользовательские часы в пользовательский интерфейс виджета.

Это возможно? Боюсь, что у нас в RemoteViews есть свой собственный компонент.

ОБНОВЛЕНИЕ: Это журнал ошибок, который я имею вначале после решения, данного vArDo.

2 ответа

Решение

вступление

Невозможно включить пользовательские представления в виджеты приложения. В соответствии с документацией в макете может использоваться только небольшое предопределенное подмножество видов. Итак, как вы упомянули, невозможно создать свой собственный AnalogClock просмотреть (как я показал в моем другом ответе) и включить его в виджет приложения.

Однако есть и другие средства, которые можно использовать для создания пользовательских AnalogClock в виджете приложения. В такой реализации время установки прямо в AnalogClock не должно быть проблемой.

Короткий ответ

Одним из способов сделать это является рисование на заказ AnalogClock в ImageView (который является одним из разрешенных видов в виджетах приложения). повторяющий PendingIntent использует Service втянуть в ImageView каждые 60 секунд (через RemoteViews).

Обратите внимание на использование батареи

Имейте в виду, что вызывая Service действие по обновлению виджета приложения через RemoteViews каждые 60 секунд не так легко на батарее. В примере реализации Jelly Bean использовалось 2% батареи ночью (выключенный экран, средняя мощность сети). Тем не менее, я обновляю экран каждые 15 секунд, что не является необходимым, когда дело доходит до аналоговых часов. Таким образом, по приблизительным оценкам, виджет приложения с одним обновлением в минуту будет потреблять около 0,5% заряда аккумулятора ночью - это может быть важно для ваших пользователей.

Детали решения

Изменения в оформлении сервиса

Прежде всего вам придется создать Service это привлечет в ImageView представить на ваш взгляд. Код для рисования в значительной степени взят из 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);

Обновление оформлено в ImageView с помощью 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 файл. Нам также нужно будет удалить 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 Виджет приложения, время которого было установлено только один раз (обратите внимание на разницу между временем аналоговых часов и временем, показанным в строке состояния):

Там нет никакого способа установить время на что-то, кроме текущего времени в AnalogClock, Этот виджет очень инкапсулирован. Вы можете легко изменить его внешний вид (в XML, используя атрибуты R.styleable, но нет простого способа изменить его поведение).

Я бы, вероятно, пошел с созданием копии AnalogClock.java (может быть хорошей идеей поместить ее в отдельный пакет, например com.example.widget) и локальные копии drawables clock_dial, clock_hand_hour, clock_hand_minute (ПРИМЕЧАНИЕ: для получения наилучших результатов вам придется создавать локальные копии для всех плотностей: ldpi, mdpi, hdpi и xhdpi). С их помощью и небольшими изменениями в коде вы сможете получить желаемое поведение.

Использование пользовательских атрибутов Drawables WITH, устанавливаемых из макета XML

Чтобы быть в состоянии установить dial, hand_hour а также hand_minute Рисунки из файлов макета XML, вам нужно объявить эти атрибуты как стилизуемые для вашего пользовательского виджета.

ПРИМЕЧАНИЕ: в следующем примере я создал 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. от ссылки на любой нарисованный или стиль. Остальное такое же, как в конструкторе AnalogClock.

Использование пользовательских элементов рисования БЕЗ атрибутов, устанавливаемых из макета XML

Если вам не нужно, чтобы ваши атрибуты настраивались через 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>

Я могу дополнить свой ответ еще большим количеством кода предлагаемого решения, если вам нужна дополнительная помощь по этому вопросу.

Другие вопросы по тегам