Как создавать виджеты в android

Эта статья проведет вас через весь процесс создания виджета. Мы будем делать это поэтапно:

  1. Введение в виджеты
  2. Создайте виджет размером 1 * 1: мы добавим в проект основные файлы, необходимые для создания виджета.
  3. Добавьте прослушиватель кликов: здесь мы увидим, как работает прослушиватель кликов и чем он отличается от обычного слушателя кликов.
  4. Добавление нескольких виджетов для вашего приложения: мы увидим, как разные виджеты к провайдеру виджетов.
  5. Обновите данные / пользовательский интерфейс для виджета: мы определим размер виджета, а затем соответствующим образом изменим данные / пользовательский интерфейс для виджета.
  6. Обработка адаптеров для виджета: мы увидим, как добавить Gridview / Listview / Stackview в виджет.

Образец проекта

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



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

Знакомство с виджетами

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

Например, это файл макета для нашего виджета, пока он просто показывает изображение.

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/widgetImageView"
        android:src="@mipmap/ic_launcher"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >
    
</ImageView>

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



Теперь для доступа к представлениям в файле java мы используем findViewById (). Но здесь у нас есть такие методы, как setTextViewText () для добавления текста в текстовое представление.

Теперь давайте начнем с добавления виджета на наш рабочий стол.

Создайте виджет размером 1 * 1

Мы собираемся разместить виджет на рабочем столе, пока он будет показывать там значок. Этот значок будет иметь размер 1 * 1.

Перейти к ветви step_1-basic_widget_creation.

Сначала нам нужно добавить файл в res / xml. я называю этот файл sample_widget_info.xml. Он состоит из «appwidget-provider», который дает основную информацию о нашем виджете. Это выглядит следующим образом:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/layout_widget_simple"
    android:minHeight="40dp"
    android:minWidth="40dp"
    android:previewImage="@mipmap/ic_launcher"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="1800000"
    android:widgetCategory="home_screen" />

Я получил это изображение с сайта https://developer.android.com/guide/practices/ui_guidelines/widget_design.html#anatomy.

  • Согласно приведенному выше изображению, для создания виджета размером 1 * 1 нам нужны minWidth и minHeight 40dp соответственно.
  • Атрибут initialLayout определяет, какое представление использовать изначально, когда мы добавляем виджет на рабочий стол.
  • Атрибут preview_image определяет, какой значок / изображение показывать в селекторе виджетов. Здесь, как наш выбор размера, значок отображается как виджет 1 * 1.
  • Атрибут resizeMode определяет, хотим ли мы, чтобы виджет мог расширяться по горизонтали или по вертикали.
  • Атрибут updatePeriodMillis определяет интервал времени для обновления виджета. минимальное время, которое мы можем дать, - 30 минут, т.е. 1800000.
  • Атрибут widgetCategory определяет, где разместить домашний экран или клавиатуру виджета.

Теперь мы должны сообщить android, что мы добавили виджет для нашего приложения, и мы делаем это, определяя получателя в файле манифеста. Этот приемник будет расширять AppWidgetProvider, здесь этот файл - SampleAppWidgetProvider.

Мы добавим этот приемник в файл манифеста.

<receiver android:name=".SampleAppWidgetProvider">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>

    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/sample_widget_info" />
</receiver>

Здесь мы добавили действие для обнаружения:

  • Добавлен новый экземпляр Your AppWidget, добавленный на главный экран из AppWidget Chooser (от поставщика AppWidget).
  • По истечении запрошенного интервала обновления, который вы указали в информационном файле AppWidget с помощью атрибута android:updatePeriodMillis.
  • При перезагрузке устройства

Метаданные указывают на информационный файл, который мы создали для нашего виджета.

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

Добавить прослушиватель кликов

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

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



Теперь переключитесь на ветку step_2-widget_click_implementation.

Для реализации щелчка мы теперь будем использовать файл SampleAppWidgetProvider. Здесь мы будем использовать только метод onUpdate (). Этот метод вызывается всякий раз, когда есть вероятность, что виджет обновляется (а не размеры или параметры виджета, подробнее об этом позже).

Он предоставляет следующие параметры:

  • Контекст: чтобы предоставить нам контекст. получает информацию об установленных поставщиках AppWidget и другом состоянии, связанном с AppWidget.
  • AppWidgetManager: предоставляет информацию об установленных поставщиках AppWidgets и других состояниях, связанных с AppWidget.
  • int []: это массив appWidgetIds, эти идентификаторы представляют все AppWidgets, доступные на главном экране для нашего приложения.

Теперь мы собираемся добавить цикл for в метод onUpdate () для доступа ко всем виджетам, которые доступны на главном экране для нашего приложения.

for (int appWidgetId : appWidgetIds) {
    updateAppWidget(context, appWidgetManager, appWidgetId);
}

Здесь метод updateAppWidget () будет обрабатывать работу удаленных представлений. На данный момент мы только хотим открыть действие, больше ничего. поэтому наш метод updateAppWidget () будет выглядеть так:

static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,int appWidgetId) {

    // Create an Intent to launch MainActivity when clicked
    Intent intent = new Intent(context, MainActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
    
    // Construct the RemoteViews object
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.layout_widget_simple);
    // Widgets allow click handlers to only launch pending intents
    views.setOnClickPendingIntent(R.id.widgetImageView, pendingIntent);
    
    // Instruct the widget manager to update the widget
    appWidgetManager.updateAppWidget(appWidgetId, views);
}

Здесь мы сделали 3 вещи:

  • Создано отложенное намерение открыть MainActivity.
  • Создал удаленное представление и добавил прослушиватель кликов с помощью метода setOnClickPendingIntent ().
  • Дайте указание виджету обновить виджет, мы будем вызывать этот метод каждый раз, когда обновляем виджет.

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

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

Переключитесь на ветку step_2-widget_multi_click_implementation

Добавление нескольких виджетов для вашего приложения

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

Теперь для этого вам понадобится 2 типа просмотров.

Это можно сделать двумя способами.

  • Мы можем использовать один xml-файл и один AppWidgetProvider и обнаруживать изменение размера при увеличении или уменьшении размера и обновлять представление, чтобы отобразить новое представление и обработать его соответствующим образом. Но в этом случае в поставщике виджетов вы увидите только один виджет с размером, который вы определили в информационном файле sample_widget_info.xml.
  • Мы можем использовать 2 xml файла и 2 AppWidgetProvider для этих 2 виджетов. Плюс здесь в том, что мы увидим 2 виджета в Провайдере виджетов, но здесь мы не сможем изменить размер 2 виджетов ниже минимальных размеров, которые мы предоставляем. Здесь мы попробуем этот шаг, а потом рассмотрим первый.

Перейдите в ветку step_3-multiple_widget.

Давайте создадим второй информационный файл white_sample_widget_info.xml, второй файл AppWidgetProvider SampleWhiteAppWidgetProvider.java и второй файл макета layout_widget_white_simple.xml. Если у вас возникли проблемы затем обратитесь к коду в вышеупомянутой ветке.

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

Перейти в ветку step_3-multiple_view_single_widget

Здесь у нас есть 2 представления, кроме одного AppWidgetProvider.

Теперь этот метод немного сложен, поскольку, как упоминалось выше на шаге 3, метод onUpdate () не вызывается, когда вы изменяете размер виджета, вместо этого onAppWidgetOptionsChanged () вызываются методы. Поэтому вместо вызова метода обновления для получения удаленного представления мы собираемся использовать службу, которая будет уведомлять об обновлении представления, и через это мы обновим виджет. Эта служба будет службой намерений WidgetUpdateService.java.

Эта служба запускается из двух мест: из метода onUpdate () и из onAppWidgetOptionsChanged (). Теперь мы собираемся создать объект удаленного просмотра из этой службы. Мы сделали это, потому что хотим обнаружить изменение размеров виджета, а это невозможно сделать из метода onUpdate (). Итак, два метода в нашем SampleAppWidgetProvider.java будут выглядеть так:

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    WidgetUpdateService.startActionUpdateAppWidgets(context);
}

@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,int appWidgetId, Bundle newOptions) {
    WidgetUpdateService.startActionUpdateAppWidgets(context);
    super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
}

В методе startActionUpdateAppWidgets () мы запускаем службу как

public static void startActionUpdateAppWidgets(Context context) {
    Intent intent = new Intent(context, WidgetUpdateService.class);
    intent.setAction(ACTION_UPDATE_APP_WIDGETS);
    context.startService(intent);
}

Мы будем обрабатывать действие ACTION_UPDATE_APP_WIDGETS в методе onHandleIntent (). Здесь мы собираемся получить экземпляр AppWidgetManager, и через него мы собираемся вызвать метод обновления updateAppAllWidget () из SampleAppWidgetProvider.java , чтобы получить удаленный просмотр. Мы сделаем это как:

private void handleActionUpdateAppWidgets() {

    //You can do any task regarding this process you want to do here, then update the widget.

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
    int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(this, SampleAppWidgetProvider.class));

    SampleAppWidgetProvider.updateAllAppWidget(this, appWidgetManager,appWidgetIds);
}

И updateAppAllWidget () теперь обрабатывает обновление всех виджетов на экране (работа, которую мы выполняли в onUpdate (), здесь выполняется). Это выглядит так:

public static void updateAllAppWidget(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    for (int appWidgetId : appWidgetIds) {
        updateAppWidget(context, appWidgetManager, appWidgetId);
    }
}

Теперь мы хотим обнаружить изменение размера виджета, мы делаем это как:

static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,int appWidgetId) {

    Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
    int width = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
    RemoteViews remoteView;
    if (width < 300) {
        remoteView= getViewForSmallerWidget(context);
    } else {
        remoteView= getViewForBiggerWidget(context);
    }
    appWidgetManager.updateAppWidget(appWidgetId, remoteView);

}

Как видите, для получения ширины мы используем метод getAppWidgetOptions (). Этот метод можно использовать и с любым другим вариантом. Итак, чтобы получить минимальную ширину виджета, я использовал код:

Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
int width = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);

Поэтому мы предоставляем удаленное представление updateAppWidget () в соответствии с нашими потребностями. По таблице размеров, которую я упомянул выше, я поставил проверку ширины виджета меньше 300, чтобы он занимал минимум 4 блока домашнего экрана. Если ширина меньше 300, я хочу показать меньший макет, иначе увеличенный вид виджета.

if (width < 300) {
    remoteView= getViewForSmallerWidget(context);
} else {
    remoteView= getViewForBiggerWidget(context);
}

Следует отметить, что я использовал 300 в качестве своей отметки, если я хочу, чтобы размер виджета был меньше 4 * 1. Он взят только из этой таблицы, но для того, чтобы легко его запомнить, я использую число, кратное 100. В большинстве случаев это сработает.

  • width ‹300: Размер будет 4 * 1.
  • width ‹200: Размер будет 3 * 1.
  • width ‹100: Размер будет 2 * 1.
  • По умолчанию minWidth составляет 1 * 1.

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

private static RemoteViews getViewForBiggerWidget(Context context) {
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.layout_widget_white_simple);

    Intent intent1 = new Intent(context, MainActivity.class);
    PendingIntent pendingIntent1 = PendingIntent.getActivity(context, 0, intent1, 0);
    views.setOnClickPendingIntent(R.id.widgetImageView, pendingIntent1);

    Intent intent2 = new Intent(context, SecondActivity.class);
    PendingIntent pendingIntent2 = PendingIntent.getActivity(context, 0, intent2, 0);
    views.setOnClickPendingIntent(R.id.clickTextView, pendingIntent2);

    return views;
}

private static RemoteViews getViewForSmallerWidget(Context context) {

    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.layout_widget_simple);

    Intent intent1 = new Intent(context, MainActivity.class);
    PendingIntent pendingIntent1 = PendingIntent.getActivity(context, 0, intent1, 0);
    views.setOnClickPendingIntent(R.id.widgetImageView, pendingIntent1);

    Intent intent2 = new Intent(context, SecondActivity.class);
    PendingIntent pendingIntent2 = PendingIntent.getActivity(context, 0, intent2, 0);
    views.setOnClickPendingIntent(R.id.clickTextView, pendingIntent2);

    return views;
}

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

Обновите данные / пользовательский интерфейс для виджета

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

Перейдите в ветку step_4-update_view_by_height.

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

int minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
if (minHeight < 100) {
    views.setViewVisibility(R.id.titleTextView, View.GONE);
}else{
    views.setViewVisibility(R.id.titleTextView, View.VISIBLE);

    Intent intent = new Intent(context, MainActivity.class);
    intent.putExtra("text","Coming from the Widget title click.");
    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    views.setOnClickPendingIntent(R.id.titleTextView, pendingIntent);
}

Здесь мы используем флаг PendingIntent FLAG_UPDATE_CURRENT, потому что мы хотели обновить текущее состояние MainActivity.

Обработка адаптеров для виджета

Все это простые представления, которые мы использовали для отображения в виджете, что, если мы хотим показать представление коллекции (представление списка, представление сетки или представление стека). StackView - это представление изображения, которое мы видим для некоторых приложений. например. Приложение Android Authority) Все использует ту же концепцию, поэтому я собираюсь использовать здесь просмотр списка.

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

Перейдите в ветку step_5-widget_list_view.

Здесь следует обратить внимание на несколько вещей:

  • мы будем использовать метод setRemoteAdapter () для присоединения адаптера к нашему ListView. Вы можете увидеть это в нашем WidgetProvider.
Intent intent = new Intent(context, ListViewWidgetService.class);
views.setRemoteAdapter(R.id.listView, intent);
  • Он запустит службу RemoteViewsService, которая запускает, RemoteViewsService.RemoteViewsFactory. Это n-интерфейс для адаптера между удаленным представлением коллекции, в нашем случае для ListView. Вы можете увидеть это в ListViewWidgetService.
public class ListViewWidgetService extends RemoteViewsService {

    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new AppWidgetListView (this.getApplicationContext(), WidgetDataModel.getDataFromSharedPrefs(getApplicationContext()));
    }
}
  • Мы обнаруживаем изменение размера виджета в onAppWidgetOptionsChanged (), и если ширина больше 300, мы запускаем службу.
Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
int width = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
if(width<300) {
    WidgetUpdateService.startActionUpdateAppWidgets(context,false);
}else{
    WidgetUpdateService.startActionUpdateAppWidgets(context,true);
}

В сервисе мы создаем данные и сохраняем их в sharedprefs. Вы можете обрабатывать данные так, как хотите. После этого же процесса получите экземпляр AppWidgetManager и вызовите updateAllAppWidget ().

private void handleActionUpdateListView() {
    WidgetDataModel.createSampleDataForWidget(getApplicationContext());

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
    int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(this, SampleAppWidgetProvider.class));

    SampleAppWidgetProvider.updateAllAppWidget(this, appWidgetManager,appWidgetIds);

    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.listView);

}

Теперь в классе адаптера AppWidgetListView. В основном есть 2 важных метода: getCount () для получения размера отображаемого списка и getViewAt () для получения удаленного представления определенной позиции.

@Override
public int getCount() {
    return dataList.size();
}

@Override
public RemoteViews getViewAt(int position) {
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.list_item_widget);

    views.setTextViewText(R.id.titleTextView, dataList.get(position).title);
    views.setTextViewText(R.id.subTitleTextView, dataList.get(position).subTitle);

    return views;
}

Теперь мы должны реализовать щелчок по элементу списка, чтобы выполнить задачу, здесь мы открываем действие с некоторыми данными. В remoteView щелчок по-разному работает с представлениями коллекции. Реализовать щелчок для каждого элемента - дорогостоящая задача, поэтому мы реализуем щелчок для полного представления списка в AppWidgetProvider.

Intent appIntent = new Intent(context, SecondActivity.class);
PendingIntent appPendingIntent = PendingIntent.getActivity(context, 0, appIntent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setPendingIntentTemplate(R.id.listView, appPendingIntent);

Затем мы добавляем дополнительные данные для нашего щелчка в адаптер, который мы создали в getViewAt. Это делается с помощью fillInIntent.

Intent fillInIntent = new Intent();
fillInIntent.putExtra("ItemTitle",dataList.get(position).title);
fillInIntent.putExtra("ItemSubTitle",dataList.get(position).subTitle);
views.setOnClickFillInIntent(R.id.parentView, fillInIntent);

Итак, после реализации щелчка наш метод getViewAt () выглядит так:

@Override
public RemoteViews getViewAt(int position) {
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.list_item_widget);

    views.setTextViewText(R.id.titleTextView, dataList.get(position).title);
    views.setTextViewText(R.id.subTitleTextView, dataList.get(position).subTitle);

    Intent fillInIntent = new Intent();
    fillInIntent.putExtra("ItemTitle",dataList.get(position).title);
    fillInIntent.putExtra("ItemSubTitle",dataList.get(position).subTitle);
    views.setOnClickFillInIntent(R.id.parentView, fillInIntent);
    return views;
}

Теперь, если вы запустите приложение, вы можете увидеть полную работу, щелкнув элемент списка.

Для получения дополнительной информации вы можете обратиться к: