Статьи

Кодирование виджета для приложения Android: обновление виджета

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

В этой серии из трех частей мы создаем виджет приложения, который обладает всеми функциями, которые есть практически во всех виджетах приложений Android.

В первом посте мы создали виджет, который извлекает и отображает данные и выполняет уникальное действие в ответ на события onClick . Тем не менее, в нашем виджете все еще отсутствует один важный элемент функциональности: он никогда не обновляется новой информацией. Виджет, который никогда не может предложить ничего нового, не так уж и полезен, поэтому мы дадим нашему виджету два разных способа обновления.

  • Android SDK
    Кодирование виджета для Android: ввод и отображение

К концу этой серии мы расширим наш виджет, чтобы получать и отображать новые данные автоматически по расписанию и в ответ на взаимодействие с пользователем.

Мы подбираем все с того места, где остановились, поэтому, если у вас нет копии виджета, который мы создали в первом посте, вы можете скачать его с GitHub .

Первым шагом является обновление нашего макета для отображения некоторых данных, которые со временем меняются. Чтобы помочь нам точно определить, когда наш виджет получает каждое обновление, я собираюсь поручить виджету получать и отображать текущее время, когда оно выполняет обновление.

Я собираюсь добавить следующее в макет виджета:

  • TextView который отображает метку последнего обновления .
  • TextView который будет отображать время последнего обновления.

Пока мы работаем над файлом new_app_widget.xml , я также собираюсь добавить TextView который в конечном итоге позволит пользователю запускать обновление вручную:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<LinearLayout xmlns:android=»http://schemas.android.com/apk/res/android»
   android:layout_width=»match_parent»
   android:layout_height=»match_parent»
   android:padding=»@dimen/widget_margin»
   android:background=»@drawable/widget_background»
   android:orientation=»vertical» >
    
   <LinearLayout
       android:background=»@drawable/tvbackground»
       style=»@style/widget_views»
       android:layout_width=»match_parent»
       android:layout_height=»wrap_content»
       android:orientation=»horizontal»>
        
       <TextView
           android:id=»@+id/id_label»
           android:layout_width=»wrap_content»
           android:layout_height=»wrap_content»
           android:text=»@string/widget_id»
           style=»@style/widget_text» />
            
       <TextView
           android:id=»@+id/id_value»
           android:layout_width=»wrap_content»
           android:layout_height=»wrap_content»
           android:text=».»
           style=»@style/widget_text» />
            
   </LinearLayout>
    
   <TextView
       android:id=»@+id/launch_url»
       style=»@style/widget_views»
       android:layout_width=»wrap_content»
       android:layout_height=»wrap_content»
       android:text=»@string/URL»
       android:background=»@drawable/tvbackground»/>
 
//Add a ‘Tap to update’ TextView//
 
   <TextView
       android:id=»@+id/update»
       style=»@style/widget_views»
       android:layout_width=»wrap_content»
       android:layout_height=»wrap_content»
       android:text=»@string/update»
       android:background=»@drawable/tvbackground»/>
 
   <LinearLayout
       android:background=»@drawable/tvbackground»
       style=»@style/widget_views»
       android:layout_width=»match_parent»
       android:layout_height=»wrap_content»
       android:orientation=»vertical»>
 
//Add a TextView that’ll display our ‘Last Update’ label//
 
   <TextView
       android:id=»@+id/update_label»
       style=»@style/widget_text»
       android:layout_width=»match_parent»
       android:layout_height=»wrap_content»
       android:text=»@string/last_update» />
 
//Add the TextView that’ll display the time of the last update//
 
   <TextView
       android:id=»@+id/update_value»
       style=»@style/widget_text»
       android:layout_width=»match_parent»
       android:layout_height=»wrap_content»
       android:text=»@string/time» />
   </LinearLayout>
 
</LinearLayout>

Затем определите новые строковые ресурсы, на которые мы ссылаемся в нашем макете:

1
2
3
<string name=»update»>Tap to update</string>
<string name=»last_update»>Last Update</string>
<string name=»time»>%1$s</string>

@string/time выглядит не так, как ваша обычная строка, так как это просто заполнитель, который будет заменен во время выполнения.

Это дает нам готовый макет:

Предварительный просмотр полного макета виджета приложения Android

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

При создании этого вида расписания автоматического обновления 1800000 миллисекунд (30 минут) — это наименьший интервал, который вы можете использовать. Даже если вы установите для updatePeriodMillis вашего проекта менее 30 минут, ваш виджет будет обновляться только раз в полчаса.

Решить, как часто должен обновляться ваш виджет, никогда не бывает просто. Частые обновления значительно расходуют заряд батареи устройства, но устанавливайте эти обновления слишком далеко друг от друга, и ваш виджет может отображать значительно устаревшую информацию для пользователя.

Чтобы найти баланс, который подходит для вашего конкретного проекта, вам нужно протестировать свой виджет в диапазоне частот обновления и измерить влияние каждой частоты на срок службы батареи, а также следить за тем, заметно ли устаревает содержимое виджета.

Так как частые-слишком частые являются одним из тех разочаровывающих субъективных вопросов, где нет «правильного» ответа, это может помочь получить второе мнение, организовав некоторое пользовательское тестирование. Вы даже можете настроить некоторое A / B-тестирование, чтобы проверить, принимаются ли определенные частоты обновления более положительно, чем другие.

Откройте файл AppWidgetProviderInfo вашего проекта ( res / xml / new_app_widget_info.xml ), и вы увидите, что он уже определяет интервал обновления по умолчанию, равный 86400000 миллисекунд (24 часа).

1
android:updatePeriodMillis=»86400000″

Существует множество виджетов, которые обновляются только один раз каждые 24 часа, например, виджет, отображающий прогноз погоды, может потребоваться только для получения новой информации один раз в день. Однако, даже если ваш виджет должен обновляться только один раз в день, это не идеально для тестирования вашего приложения. Никто не хочет ждать 24 часа, чтобы посмотреть, правильно ли onUpdate() их onUpdate() , поэтому вы обычно используете самый короткий интервал при выполнении начального тестирования, а затем изменяете это значение позже, если требуется.

Чтобы было проще протестировать наше приложение, я собираюсь использовать наименьший возможный интервал:

1
android:updatePeriodMillis=»1800000″

Метод onUpdate() отвечает за обновление представлений вашего виджета новой информацией. Если вы откроете провайдер виджетов вашего проекта ( java / values ​​/ NewAppWidget.java ), то увидите, что он уже содержит схему метода onUpdate() :

1
2
3
4
5
6
7
8
9
@Override
  public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
 
      // There may be multiple widgets active, so update all of them//
 
      for (int appWidgetId : appWidgetIds) {
          updateAppWidget(context, appWidgetManager, appWidgetId);
      }
  }

Независимо от того, обновляется ли ваш виджет автоматически или в ответ на взаимодействие с пользователем, процесс обновления точно такой же: менеджер виджетов отправляет широковещательное намерение с действием ACTION_APPWIDGET_UPDATE , а класс провайдера виджетов отвечает, вызывая метод onUpdate() , который в свою очередь вызывает вспомогательный метод updateAppWidget() .

После того как мы обновили наш класс NewAppWidget.java для получения и отображения текущего времени, наш проект будет использовать этот же фрагмент кода независимо от того, как onUpdate() метод onUpdate() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import java.text.DateFormat;
import java.util.Date;
 
public class NewAppWidget extends AppWidgetProvider {
 
   static void updateAppWidget(Context context,
                               AppWidgetManager appWidgetManager,
                               int appWidgetId) {
 
//Retrieve the time//
 
       String timeString =
               DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date());
 
 //Construct the RemoteViews object//
 
       RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
       views.setTextViewText(R.id.id_value, String.valueOf(appWidgetId));
 
//Retrieve and display the time//
 
       views.setTextViewText(R.id.update_value,
               context.getResources().getString(
                       R.string.time, timeString));
 
       Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(«https://code.tutsplus.com/»));
       PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
       views.setOnClickPendingIntent(R.id.launch_url, pendingIntent);
       appWidgetManager.updateAppWidget(appWidgetId, views);
   }
 
   @Override
   public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
 
 //If multiple widgets are active, then update them all//
 
       for (int appWidgetId : appWidgetIds) {
           updateAppWidget(context, appWidgetManager, appWidgetId);
       }
   }
}

В приведенном выше коде есть одна проблема: если пользователь нажимает на TextView несколько раз в течение минуты, визуальный индикатор обновления виджета отсутствует, просто потому что нет нового времени для отображения. Это, вероятно, не вызовет проблем у ваших конечных пользователей, которые вряд ли будут сидеть там, нажимая на TextView несколько раз в минуту и ​​задаваясь вопросом, почему время не изменилось. Тем не менее, это может быть проблемой при тестировании вашего приложения, поскольку это означает, что вам придется подождать не менее 60 секунд между onUpdate() метода onUpdate() .

Чтобы всегда было какое-то визуальное подтверждение того, что метод onUpdate() был успешно выполнен, я собираюсь отображать тост всякий раз, когда onUpdate() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
import android.widget.Toast;
 
@Override
 
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
   for (int appWidgetId : appWidgetIds) {
       updateAppWidget(context, appWidgetManager, appWidgetId);
       Toast.makeText(context, «Widget has been updated! «, Toast.LENGTH_SHORT).show();
 
  }
   }
}
Проверьте правильность обновления виджета, включив тост

Наш виджет теперь будет обновляться автоматически каждые полчаса, но как насчет обновления в ответ на взаимодействие с пользователем?

Мы уже заложили много основы, добавив в наш макет элемент Tap to Update TextView и расширив класс NewAppWidget.java для извлечения и отображения текущего времени при каждом onUpdate() .

AppWidgetManager.ACTION_APPWIDGET_UPDATE только создать Intent с AppWidgetManager.ACTION_APPWIDGET_UPDATE , который представляет собой действие, информирующее виджет о том, что пришло время обновлять, и затем вызывает этот Intent в ответ на взаимодействие пользователя с Tap для обновления TextView .

Вот завершенный класс NewAppWidget.java :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import java.text.DateFormat;
import java.util.Date;
import android.widget.Toast;
 
public class NewAppWidget extends AppWidgetProvider {
   static void updateAppWidget(Context context,
                               AppWidgetManager appWidgetManager,
                               int appWidgetId) {
 
//Retrieve the current time//
 
       String timeString =
               DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date());
 
       RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
       views.setTextViewText(R.id.id_value, String.valueOf(appWidgetId));
       views.setTextViewText(R.id.update_value,
               context.getResources().getString(
                       R.string.time, timeString));
 
//Create an Intent with the AppWidgetManager.ACTION_APPWIDGET_UPDATE action//
 
       Intent intentUpdate = new Intent(context, NewAppWidget.class);
       intentUpdate.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
 
//Update the current widget instance only, by creating an array that contains the widget’s unique ID//
 
       int[] idArray = new int[]{appWidgetId};
       intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, idArray);
 
//Wrap the intent as a PendingIntent, using PendingIntent.getBroadcast()//
 
       PendingIntent pendingUpdate = PendingIntent.getBroadcast(
               context, appWidgetId, intentUpdate,
               PendingIntent.FLAG_UPDATE_CURRENT);
 
//Send the pending intent in response to the user tapping the ‘Update’ TextView//
 
       views.setOnClickPendingIntent(R.id.update, pendingUpdate);
       Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(«https://code.tutsplus.com/»));
       PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
       views.setOnClickPendingIntent(R.id.launch_url, pendingIntent);
 
//Request that the AppWidgetManager updates the application widget//
 
       appWidgetManager.updateAppWidget(appWidgetId, views);
   }
 
   @Override
   public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
       for (int appWidgetId : appWidgetIds) {
           updateAppWidget(context, appWidgetManager, appWidgetId);
           Toast.makeText(context, «Widget has been updated! «, Toast.LENGTH_SHORT).show();
       }
   }
}

Возможность обновления виджета по требованию может иметь неоценимое значение при тестировании вашего проекта, поскольку это означает, что вам никогда не придется ждать истечения интервала обновления. Даже если вы не включите эту функцию в готовое приложение, вам может потребоваться добавить временную кнопку «Нажать на обновление» , чтобы облегчить себе жизнь при тестировании проекта.

Наш виджет теперь выглядит совершенно иначе, чем виджет, который Android Studio сгенерировал для нас автоматически, но на данный момент он все еще использует автоматически сгенерированное изображение предварительного просмотра.

Создайте уникальный виджет для Android-приложения.

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

Самый простой способ — использовать приложение Widget Preview , включенное в эмулятор Android.

Создание предварительного просмотра виджета

Сначала установите ваш проект на виртуальное устройство Android (AVD). Затем откройте панель приложений AVD и запустите приложение Widget Preview. AVD отобразит список каждого приложения, установленного на устройстве, — выберите ваше приложение из списка.

Ваш виджет будет отображаться на пустом фоне. Потратьте некоторое время на изменение размера и настройку виджета, чтобы он выглядел наилучшим образом, и как только вы будете довольны результатами, выберите « Сделать снимок» . Снимок экрана будет сохранен в формате PNG в папке Download AVD. Чтобы получить этот файл, вернитесь в Android Studio и откройте проводник файлов устройств , выбрав « Вид»> «Инструменты Windows»> «Проводник файлов устройств» на панели инструментов. В представлении Device File Explorer перейдите в папку sdcard / Download , где вы найдете изображение предварительного просмотра, сохраненное как: [имя_приложения] _ori_ [ориентация] .png

Создайте изображение для предварительного просмотра, используя встроенное приложение Widget Preview для Android Virtual Devices

Перетащите это изображение из Android Studio и поместите его в легко доступное место, например на рабочий стол. Дайте изображению более информативное имя, затем перетащите изображение в папку для рисования вашего проекта. Теперь вы можете открыть файл AppWidgetProviderInfo (в данном случае это new_app_widget_info.xml ) и изменить следующую строку для ссылки на новое изображение предварительного просмотра:

1
android:previewImage=»@drawable/example_appwidget_preview»

Наконец, вы можете удалить ненужный пример example_appwidget_preview из вашего проекта.

Пришло время проверить готовый виджет!

Сначала установите обновленный проект на свое физическое устройство Android или AVD. Удалите все существующие экземпляры этого виджета с рабочего стола, чтобы вы знали , что работаете с последней версией.

Чтобы добавить свой виджет, нажмите любое пустое место на рабочем столе, нажмите « Виджет», а затем выберите свой виджет. Вы получите возможность изменить размер и переместить виджет, если это необходимо.

Значение Last Update будет отображать время, когда вы создали этот виджет. Нажмите Tap, чтобы обновить, и виджет должен отобразить тост и новое время. Если вы разместите второй экземпляр виджета на рабочем столе, он должен отображать время, отличное от первого.

Создать несколько экземпляров одного и того же виджета приложения

Вы можете обновить любой из этих экземпляров виджета, нажав на Tap, чтобы обновить TextView . Если вы инициируете ручное обновление для первого виджета, то второй виджет не будет обновляться, и наоборот.

Помимо обновлений вручную, диспетчер виджетов приложений будет обновлять все экземпляры вашего виджета каждые 30 минут в зависимости от времени создания первого экземпляра. Хотя обновление вручную может привести к тому, что экземпляры будут отображаться разное время, каждые полчаса все экземпляры будут обновляться одновременно, и в этот момент они будут отображаться одинаково.

Возможно, вы захотите сохранить несколько экземпляров этого виджета на своем домашнем экране, чтобы вы могли следить за тем, как их содержимое со временем меняется в ответ на комбинацию автоматического и ручного обновления.

В этой серии мы создали виджет для приложений Android, который демонстрирует, как реализовать все наиболее распространенные функции, присутствующие в виджетах приложений. Если вы следили с самого начала, то к этому моменту вы создадите виджет, который обновляется автоматически и в ответ на пользовательский ввод и способен реагировать на события onClick (вы также можете загрузить весь проект с GitHub ).

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

  • Android SDK
    Что такое Android Instant Apps?
  • Android SDK
    Создание доступных приложений для Android
  • Android SDK
    Создание интерфейса с вкладками «Дизайн материала» в приложении для Android
    Чике Мгбемена
  • Android SDK
    Совет: создайте пользовательскую плитку быстрых настроек для Android
    Ашраф Хатхибелагал