4

I have a widget (AppWidgetProvider) and i want to know if there is a way to support multiple clicks.Example:

1)If is the first click on the widget, then the ImageButton of the widget changes (for example, changes the color).

2)If is the second time, then open an Activity.

-- There is some way to handle click events inside AppWidgetProvider?

My code:

public class MyWidgetProvider extends AppWidgetProvider
{


    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int N = appWidgetIds.length;

        for (int i=0; i<N; i++) {
            int appWidgetId = appWidgetIds[i];    

            Intent intent = new Intent(context, MyActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);


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

            views.setOnClickPendingIntent(R.id.asdf, pendingIntent);

             appWidgetManager.updateAppWidget(appWidgetId, views);
        }    
    }
}

My widget is working fine. When i click the ImageButton(R.id.asdf), it goes to the activity MyActivity.

Id like to know how can i handle click events on my widget to make a different action (example: change the color of the ImageButton) instead of go to some activity. Is there some other way to some click handle besides setOnClickPendingIntent()?

guilherme.minglini
  • 564
  • 2
  • 6
  • 20
  • Could you post your code and indicate what's not working? – Cheezmeister Feb 21 '11 at 20:04
  • Do you mean you have an `AppWidget` as in what you'd put on your home screen or a `Widget` as in a user interface component like a `Button` or `CheckBox`? – Blrfl Feb 21 '11 at 20:08

7 Answers7

5

Maybe this could help. It works for me:

public class WidgetProvider extends AppWidgetProvider {

private static final int DOUBLE_CLICK_DELAY = 500;

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {


    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
    Intent intent = new Intent(context, getClass());
    intent.setAction("Click");
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
    views.setOnClickPendingIntent(R.id.image, pendingIntent);
    appWidgetManager.updateAppWidget(appWidgetIds, views);
    context.getSharedPreferences("widget", 0).edit().putInt("clicks", 0).commit();

}

@Override
public void onReceive(final Context context, Intent intent) {

    if (intent.getAction().equals("Click")) {

        int clickCount = context.getSharedPreferences("widget", Context.MODE_PRIVATE).getInt("clicks", 0);
        context.getSharedPreferences("widget", Context.MODE_PRIVATE).edit().putInt("clicks", ++clickCount).commit();

        final Handler handler = new Handler() {
            public void handleMessage(Message msg) {

                int clickCount = context.getSharedPreferences("widget", Context.MODE_PRIVATE).getInt("clicks", 0);

                if (clickCount > 1) Toast.makeText(context, "doubleClick", Toast.LENGTH_SHORT).show();
                else Toast.makeText(context, "singleClick", Toast.LENGTH_SHORT).show();

                context.getSharedPreferences("widget", Context.MODE_PRIVATE).edit().putInt("clicks", 0).commit();
            }
        };

        if (clickCount == 1) new Thread() {
            @Override
            public void run(){
                try {
                    synchronized(this) { wait(DOUBLE_CLICK_DELAY); }
                    handler.sendEmptyMessage(0);
                } catch(InterruptedException ex) {}
            }
        }.start();
    }

    super.onReceive(context, intent);

}

}

almisoft
  • 2,153
  • 2
  • 25
  • 33
2

I did it this way:

1)If is the first click on the widget, then the ImageButton of the widget changes

2)If is the second time, then open an Activity and return to the inicial ImageButton state.

Im handling click events with setOnClickPendingIntent:

private int[] RESOURCES = {R.drawable.button1,R.drawable.button2};

@Override
        public void onUpdate(Context context, AppWidgetManager mgr, int[] appWidgetIds) {


            ComponentName me = new ComponentName(context, MyWidgetProvider.class);
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.my_widget);

            Intent widgetIntent = new Intent(context, MyWidgetProvider.class);
            Intent myIntent= new Intent(context, MyOtherActivity.class); 

            widgetIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
            widgetIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

            PendingIntent pendingIntent;

            if(clicks == 0)
            {
                clicks = 1;
                remoteViews.setImageViewResource(R.id.my_image_button, RESOURCES[0]);
                pendingIntent = PendingIntent.getBroadcast(context, 0, widgetIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            }
            else if(clicks == 1)
            {
                clicks = 2;
                remoteViews.setImageViewResource(R.id.my_image_button, RESOURCES[1]);
                pendingIntent = PendingIntent.getActivity(context, 0, myIntent,0);
            }
            else //clicks == 2
            {
                clicks = 0;
                remoteViews.setImageViewResource(R.id.my_image_button, RESOURCES[0]);
                pendingIntent = PendingIntent.getBroadcast(context, 0, widgetIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            }

            remoteViews.setOnClickPendingIntent(R.id.my_image_button, pendingIntent);

            mgr.updateAppWidget(me, remoteViews);
        }

        @Override
        public void onEnabled(Context context) {
            clicks = 0;     
            super.onEnabled(context);

        }
guilherme.minglini
  • 564
  • 2
  • 6
  • 20
  • Any chance you could post the complete code for this app? I may be trying to figure out doing something similar, but I've been stuck for a while. – Thane Anthem Apr 16 '11 at 00:53
1

Unlike Button and its subclasses, AppWidget doesn't have the concept of an onClickListener. AppWidgets have to provide a PendingIntent for the application that hosts them to fire when the widget is clicked. If you wanted to track multiple clicks, you would need to have a receiver that filters on an intent specific to your widget and keeps track of how many times it has received it.

Slightly-less-relevant: You might reconsider your behavior model. Android is a one-click-to-take-action environment that doesn't have the same "click-once-to-select" concept like you'd find in, say, Windows. By emulating that behavior, your widget won't behave like all of the other UI elements and may cause confusion. Additionally, if there are two of your widgets on the screen and the user taps one and then the other, both will appear "selected," which probably isn't what you want.

Blrfl
  • 6,817
  • 1
  • 25
  • 25
  • I have built a widget that contains an ImageButton. How can I access the imagebutton from an AppWidgetProvider and add an click listener on it? In an Activity I would just use findViewById() and setOnClickListener(), but I can't do that in the AppWidgetProvider. – guilherme.minglini Feb 21 '11 at 20:49
  • You can set a `PendingIntent` for any element in your layout. Just make sure you set each one up differently so you can tell which one fired. – Blrfl Feb 21 '11 at 22:07
0

This is what I'm doing. Works beautifully =]

public class MyWidgetProvider extends AppWidgetProvider {

    //Length of allowed time in between clicks in milliseconds
    private static final long DOUBLE_CLICK_WINDOW = 400;
    private static volatile long firstClickTimeReference;

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int N = appWidgetIds.length;

        for (int i=0; i<N; i++) {
            int appWidgetId = appWidgetIds[i];

            long currentSystemTime = System.currentTimeMillis();

            if(currentSystemTime - firstClickTimeReference <= DOUBLE_CLICK_WINDOW) {
                //double click happened in less than 400 miliseconds
                //so let's start our activity
                Intent intent = new Intent(context, MainActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity( intent );
            } else {
                firstClickTimeReference = currentSystemTime;



                RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
                        R.layout.control_widget);

                Intent intent = new Intent(context, WidgetProvider.class);
                intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

                PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
                        0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                remoteViews.setOnClickPendingIntent(R.id.actionButton, pendingIntent);
                appWidgetManager.updateAppWidget(widgetId, remoteViews);
            }
        }
    }
}
Benjamin
  • 76
  • 4
0

Why not just count the clicks yourself?

private class MyAwesomeClickListener implements OnClickListener {
    private int clicks = 0;
    @Override
    public void onClick(View v) {
        ++clicks;
        //do some cool stuff
    }

}
Cheezmeister
  • 4,895
  • 3
  • 31
  • 37
0

Don't count it in a listener (it's ridiculous, you don't know when your widget will be recycled from the memory and you will lose the value of int clicks)

Persist the number of clicks (add +1 each time it is clicked, restart to 0 when you reach the end of the click "cycle", the end of different click behaviors).

You could persist them in a database, with serialization, or with the shared preferences (I guess the preferences are the easiest way)

dsdsa
  • 31
  • 1
  • When the widget is recycled, what's left to count? Why bother persisting anything? – Cheezmeister Feb 21 '11 at 20:05
  • Sorry, my question question was not very clear. I want to know if there is some way to handle click events inside AppWidgetProvider? – guilherme.minglini Feb 21 '11 at 20:06
  • because the user can tap it once (change the color), navigate here and there on his phone, go back to the home screen and tap it again (to open an activity). Just because there has been a pause of X minutes during which there wasn't any widget, doesn't mean that the user has to redone all of his actions :) – dsdsa Feb 21 '11 at 20:07
  • @user616 - sure, just read the doc for the App Widgets, it's only 2-3 pages, and the interesting (for you) part starts in the middle : http://developer.android.com/guide/topics/appwidgets/index.html#Providers – dsdsa Feb 21 '11 at 20:12
0

The solution by almisoft provides a double-click feel rather than simply successive clicks. And it works. Unfortunately you also get a lint message telling you that the 'handler class should be static or leaks might occur'. The solution is to use a weak reference and static handler - a generic version is here. Converting almisoft's code gives:

public class WidgetProvider extends AppWidgetProvider {

private static final int DOUBLE_CLICK_DELAY = 500;
private static Context mContext;
private static int mClickCount;

private static class MyHandler extends Handler {
  //Using a weak reference means you won't prevent garbage collection
  private final WeakReference<WidgetProvider> myClassWeakReference; 
  public MyHandler(WidgetProvider myClassInstance) {
    myClassWeakReference = new WeakReference<WidgetProvider>(myClassInstance);
  }
  @Override
  public void handleMessage(Message msg) {
    WidgetProvider myWidget = myClassWeakReference.get();
    if (myWidget != null) {
        //...do work here as in almisoft original...
        int clickCount = mContext.getSharedPreferences("widget", Context.MODE_PRIVATE).getInt("clicks", 0);
        if (clickCount > 1) {
          Toast.makeText(mContext, "doubleClick", Toast.LENGTH_SHORT).show();
        } else {
          Toast.makeText(mContext, "singleClick", Toast.LENGTH_SHORT).show();
        }
        mContext.getSharedPreferences("widget", Context.MODE_PRIVATE).edit().putInt("clicks", 0).commit();
    }
  }
}

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
    Intent intent = new Intent(context, getClass());
    intent.setAction("Click");
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
    views.setOnClickPendingIntent(R.id.image, pendingIntent);
    appWidgetManager.updateAppWidget(appWidgetIds, views);
    context.getSharedPreferences("widget", 0).edit().putInt("clicks", 0).commit();
}

@Override
public void onReceive(final Context context, Intent intent) {
    mContext=context;
    mIntent=intent;
    if (intent.getAction().equals("Click")) {
        int clickCount = context.getSharedPreferences("widget", Context.MODE_PRIVATE).getInt("clicks", 0);
        context.getSharedPreferences("widget", Context.MODE_PRIVATE).edit().putInt("clicks", ++clickCount).commit();
        final Handler handler=new MyHandler(this);
        if (clickCount == 1) new Thread() {
            @Override
            public void run(){
                try {
                    synchronized(this) { wait(DOUBLE_CLICK_DELAY); }
                    handler.sendEmptyMessage(0);
                } catch(InterruptedException ex) {}
            }
        }.start();
      }
    super.onReceive(context, intent);
    }

}
marzetti
  • 369
  • 4
  • 8