Lesson 120. Widgets. Click processing

Lesson 120. Widgets. Click processing


In this lesson:

– Click on the widget

We continue the theme of widgets. A widget that shows information is good, we can do it now. But in addition, the widget can still respond to clicks.

Since we do not have direct access to the view components of the widget, we will normally not use click handlers. But RemoteViews, which we use to work with view, allows you to customize the view’s click response. He uses PendingIntent to do this. That is, we can hang up a Activity, Service, or BroadcastReceiver call by clicking the widget. In this lesson, let’s make a simple but meaningful example that reflects the various techniques of click response.

Let’s create a widget consisting of two texts and three click zones.

The first text will show the time of the last update, and the second will show the number of clicks on the third click zone.

The first area will be when you click to open the Configuration Activity. This is useful when you want to allow the user to customize the widget after installation. We will configure the format displayed in the first line of time.

The second click area will simply update the widget, thus changing the time in the first text.

Each click on the third zone will increase by one click counter and update the widget. This will change the second text that reflects the current value of the counter.

For simplicity, of course, you could break this example into three separate widgets. But I decided to do it all in one, to show clearly that one widget can perform different actions in response to clicking on different view.

Let’s create a project without Activity:

Project name: P1201_ClickWidget
Build Target: Android 2.3.3
Application name: ClickWidget
Package name: ru.startandroid.develop.p1201clickwidget

strings.xml:

Config
Update
Count
Ok

Widget Layout File widget.xml:



	
	
	
	
	
	
	
	
	
	

The first two TextViews are texts and the last three are text areas.

Layout file for the configuration screen config.xml:



	
	
	

Date input field and confirmation button

Configuration screen class ConfigActivity.java:

package ru.startandroid.develop.p1201clickwidget;

import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

public class ConfigActivity extends Activity {

  public final static String WIDGET_PREF = "widget_pref";
  public final static String WIDGET_TIME_FORMAT = "widget_time_format_";
  public final static String WIDGET_COUNT = "widget_count_";

  int widgetID = AppWidgetManager.INVALID_APPWIDGET_ID;
  Intent resultValue;
  SharedPreferences sp;
  EditText etFormat;

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // извлекаем ID конфигурируемого виджета
    Intent intent = getIntent();
    Bundle extras = intent.getExtras();
    if (extras != null) {
      widgetID = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
          AppWidgetManager.INVALID_APPWIDGET_ID);
    }
    // и проверяем его корректность
    if (widgetID == AppWidgetManager.INVALID_APPWIDGET_ID) {
      finish();
    }

    // формируем intent ответа
    resultValue = new Intent();
    resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetID);

    // отрицательный ответ
    setResult(RESULT_CANCELED, resultValue);

    setContentView(R.layout.config);
    
    sp = getSharedPreferences(WIDGET_PREF, MODE_PRIVATE);
    etFormat = (EditText) findViewById(R.id.etFormat);
    etFormat.setText(sp.getString(WIDGET_TIME_FORMAT + widgetID, "HH:mm:ss"));
    
    int cnt = sp.getInt(ConfigActivity.WIDGET_COUNT + widgetID, -1);
    if (cnt == -1) sp.edit().putInt(WIDGET_COUNT + widgetID, 0);
  }
  
  public void onClick(View v){
    sp.edit().putString(WIDGET_TIME_FORMAT + widgetID, etFormat.getText().toString()).commit();
    //MyWidget.updateWidget(this, AppWidgetManager.getInstance(this), widgetID);
    setResult(RESULT_OK, resultValue);
    finish();
  }
}

There is nothing new for us here.

IN onCreate we extract and check the instance ID of the widget for which the configuration screen has opened. Next, we form a negative response to the back button. We read the time format and put it in EditText. We read the value of the counter and, if this value is not already in the Preferences, then we write there 0.

IN onClick we save in the Preferences format with EditText, update the widget, generate a positive response and leave.

The widget update code is still commented out because we do not yet have a MyWidget class. Now let’s create and you can uncomment.

widget class MyWidget.java:

package ru.startandroid.develop.p1201clickwidget;

import java.sql.Date;
import java.text.SimpleDateFormat;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.widget.RemoteViews;

public class MyWidget extends AppWidgetProvider {

  final static String ACTION_CHANGE = "ru.startandroid.develop.p1201clickwidget.change_count";

  public void onUpdate(Context context, AppWidgetManager appWidgetManager,
      int[] appWidgetIds) {
    super.onUpdate(context, appWidgetManager, appWidgetIds);
    // обновляем все экземпляры
    for (int i : appWidgetIds) {
      updateWidget(context, appWidgetManager, i);
    }
  }

  public void onDeleted(Context context, int[] appWidgetIds) {
    super.onDeleted(context, appWidgetIds);
    // Удаляем Preferences
    Editor editor = context.getSharedPreferences(
        ConfigActivity.WIDGET_PREF, Context.MODE_PRIVATE).edit();
    for (int widgetID : appWidgetIds) {
      editor.remove(ConfigActivity.WIDGET_TIME_FORMAT + widgetID);
      editor.remove(ConfigActivity.WIDGET_COUNT + widgetID);
    }
    editor.commit();
  }

  static void updateWidget(Context ctx, AppWidgetManager appWidgetManager,
      int widgetID) {
    SharedPreferences sp = ctx.getSharedPreferences(
        ConfigActivity.WIDGET_PREF, Context.MODE_PRIVATE);

    // Читаем формат времени и определяем текущее время
    String timeFormat = sp.getString(ConfigActivity.WIDGET_TIME_FORMAT
        + widgetID, null);
    if (timeFormat == null) return;
    SimpleDateFormat sdf = new SimpleDateFormat(timeFormat);
    String currentTime = sdf.format(new Date(System.currentTimeMillis()));

    // Читаем счетчик
    String count = String.valueOf(sp.getInt(ConfigActivity.WIDGET_COUNT
        + widgetID, 0));

    // Помещаем данные в текстовые поля
    RemoteViews widgetView = new RemoteViews(ctx.getPackageName(),
        R.layout.widget);
    widgetView.setTextViewText(R.id.tvTime, currentTime);
    widgetView.setTextViewText(R.id.tvCount, count);

    // Конфигурационный экран (первая зона)
    Intent configIntent = new Intent(ctx, ConfigActivity.class);
    configIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
    configIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetID);
    PendingIntent pIntent = PendingIntent.getActivity(ctx, widgetID,
        configIntent, 0);
    widgetView.setOnClickPendingIntent(R.id.tvPressConfig, pIntent);

    // Обновление виджета (вторая зона)
    Intent updateIntent = new Intent(ctx, MyWidget.class);
    updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
    updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
        new int[] { widgetID });
    pIntent = PendingIntent.getBroadcast(ctx, widgetID, updateIntent, 0);
    widgetView.setOnClickPendingIntent(R.id.tvPressUpdate, pIntent);

    // Счетчик нажатий (третья зона)
    Intent countIntent = new Intent(ctx, MyWidget.class);
    countIntent.setAction(ACTION_CHANGE);
    countIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetID);
    pIntent = PendingIntent.getBroadcast(ctx, widgetID, countIntent, 0);
    widgetView.setOnClickPendingIntent(R.id.tvPressCount, pIntent);

    // Обновляем виджет
    appWidgetManager.updateAppWidget(widgetID, widgetView);
  }

  public void onReceive(Context context, Intent intent) {
    super.onReceive(context, intent);
    // Проверяем, что это intent от нажатия на третью зону
    if (intent.getAction().equalsIgnoreCase(ACTION_CHANGE)) {

      // извлекаем ID экземпляра
      int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
      Bundle extras = intent.getExtras();
      if (extras != null) {
        mAppWidgetId = extras.getInt(
            AppWidgetManager.EXTRA_APPWIDGET_ID,
            AppWidgetManager.INVALID_APPWIDGET_ID);

      }
      if (mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
        // Читаем значение счетчика, увеличиваем на 1 и записываем
        SharedPreferences sp = context.getSharedPreferences(
            ConfigActivity.WIDGET_PREF, Context.MODE_PRIVATE);
        int cnt = sp.getInt(ConfigActivity.WIDGET_COUNT + mAppWidgetId,  0);
        sp.edit().putInt(ConfigActivity.WIDGET_COUNT + mAppWidgetId,
                ++cnt).commit();

        // Обновляем виджет
        updateWidget(context, AppWidgetManager.getInstance(context),
            mAppWidgetId);
      }
    }
  }

}

But here it is a little more complicated.

IN onUpdate we are updating everything that requires an instance update onDelete we clean up Preferences after deleting instances.

method updateWidget is responsible for updating a specific instance of the widget. This is where we customize the look and feel of the click.

First we read the time format settings (which were saved in the configuration screen), take the current time and convert it to a line according to the format. Also from the settings we read the value of the counter. We create RemoteViews and place the time and counter to the corresponding TextView.

The following is a click processing setup. The mechanism is simple. First, we prepare an Intent that contains some data and knows where it should go. We are packing this Intent at PendingIntent. Next, we set PendingIntent to the specific view component by the setOnClickPendingIntent method. And when this view is clicked, the system will retrieve Intent from PendingIntent and send it to its destination.

Our widget has three zones for touch. For each of them we form a separate Intent and PendingIntent.

The first zone – after clicking should open the Configuration Activity. We create an Intent that will call our Activity, place the ID data (to let the screen know which instance it is configuring), pack in PendingIntent and map the view component of the first zone.

Second zone – after the click, the widget on which the click was made should be updated. We create an Intent that will call our widget class, add an action = ACTION_APPWIDGET_UPDATE, place the ID data (this instance is updated), pack in PendingIntent and map the view component of the second zone.

The third zone – after pressing should increase by one the counter of pressure. We create an Intent that will call our widget class, add it our own action = ACTION_CHANGE, place the ID data (to work with the counter of this instance), pack in PendingIntent and map the view component of the third zone.

Now clicking on the first zone will bring up a configuration screen. Clicking on a friend will update the widget. But clicking on the third will not lead to anything, because our MyWidget class knows how to work with Intent with action type ACTION_APPWIDGET_UPDATE, ACTION_APPWIDGET_DELETED and more. And we sent him his left action.

So we need to teach him to understand our Intent. Remember that MyWidget is an AppWidgetProvider extension and AppWidgetProvider is a BroadcastReceiver extension. So we can implement the onReceive method ourselves, in which we catch our action and perform the actions we need.

In the method onReceive we will surely follow the parent’s onReceive method, otherwise updates and other standard widget events will simply stop working. Next, we check that intent contains our action, read and check the ID from it, read from the settings of the counter value, increase by one, write back to the settings and update the widget instance. It will read the new setting counter value and display it.

Did you note that when creating PendingIntent, we used the instance of the widget instance as a requestCode? I explain why this is done. Suppose we create two instances of a widget. The former creates and creates its own PendingIntent for refresh, counter, and configuration. These PendingIntent contain action and extra data. A second copy is now created. It is also trying to build its PendingIntent with the same action and other extra data. Here we recall a past lesson, namely the system’s default behavior. If the created PendingIntent is similar to the existing one, then the created one will become a copy of the existing one. That is, all PendingIntent of the second instance will receive extra data from Intent of the first. In extra-data we have an instance ID. So the second instance of the widget will update the time / counter and open the configuration screen of the first instance. If interesting, you can put zeros instead of IDs when creating PendingIntent and make sure everything is right. To avoid this, we use requestCode. I hope this point is understandable because most of the last lesson was written for this)

Now remember to uncomment the widget upgrade code in class ConfigActivity in the onClick method. Otherwise, nothing will work.

Let’s create a metadata file xml / widget_metadata.xml:



The widget will be vertical. The number 0 in updatePeriodMillis indicates that the widget will NOT be updated by the system. We will update it ourselves.

It remains to register classes in manifestos. The following code snippet should appear:


	
		
		
	
	
	


	
		
		
	

If there is no icon and text for the Receiver, it will take them from the program.

That is, our widget will have a standard system icon and program name – ClickWidget.

We all save and install the application.

For clarity, let’s create a couple of instances of the widget. The settings in the configuration screen will still default.

The widgets reflect the time when they were last updated and the click counter.

now press Update on both widgets, the time will be updated. And, pressing Count, You change the counter value and the widget displays it. Along with the counter, by the way, time is also updated because it is updated every time the widget is updated.

by clicking on Config, We get to the configuration screen. Here you can change the display time format. We configure the first copy to display only hours and minutes

and the other seconds

it turned out that way

I suggest you finish the widget yourself so that when you click on the Count, only the counter is updated and the time does not change. Also, try adding another (fourth) zone, which when clicked would open, for example, www.google.com in the browser.

Just in case I’ll say the following clearly. In our example, when clicked, we called our classes through Intent. But I think it is clear to everyone that you can call anything that Intent will allow, there are no restrictions.

In the next lesson:

– create a list widget




Discuss in the forum [31 replies]
Related Posts
Lesson 190. Notifications. channels

Android Oreo (API 26) has the ability to create message channels. In this lesson we will understand how to Read more

Lesson 189. Notifications. message grouping

Android 7 (API 24) has the ability to group messages. Even if you don't explicitly implement it, the system Read more

Lesson 188. Notifications. custom messages

Android enables us to create a layout for messages ourselves. Consider a simple example: layout / notification.xml Height 64dp Read more

Lesson 187. Notifications. Action buttons. Reply.

Android 4.1 has the ability to add buttons to messages. This is done using the addAction method. Intent deleteIntent Read more

Leave a Comment