android widget (desktop widget) implementation

This article describes how to implement a widget yourself and various precautions.

 

First, let's take a look at the layout supported by widgets, this is the first "pit"

The layout and view types supported by widgets are very limited, only the following types are supported

FrameLayout
LinearLayout
RelativeLayout
GridLayout
And the following widget classes:

AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper

ViewStub

That is to say, if you want to draw a dividing line, you can only select a view on it to change the width and height to set the background.

If you want to make a toggle button, you can only use imageview to send custom broadcasts, and change the state by receiving broadcasts (as to why not directly change the image, see later)

 

Next, let's look at the widget's logic processing component, AppWidgetProvider

AppWidgetProvider is used to receive widget events, and then handle widgets differently according to different events.

AppWidgetProvider is essentially a broadcast receiver. It handles some system broadcasts about widgets in the onReceive method. The app only needs to rewrite the relevant methods.

So, in addition to handling widget-related broadcasts, you can also handle custom broadcasts and other system broadcasts.

First, let's configure the receiver in the manifest

<receiver android:name=".TestWidgetProvider" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        <action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
        <action android:name="android.appwidget.action.APPWIDGET_DISABLED" />
        <action android:name="com.test.widgetdemo.UPDATE_START" />
        <action android:name="com.test.widgetdemo.UPDATE_END" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/app_widget"/>
</receiver>

The resource in meta-data specifies widgets in more detail.

<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:configure="com.test.widgetdemo.WidgetCheckActivity"
    android:initialLayout="@layout/widget"
    android:minHeight="89dip"
    android:minWidth="282dip"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="1800000"/>

Key configuration:

1. The relationship between the number of minHeight/minWidth cells and the minimum width and height: 70 × n − 30 (unit dip)

2.  Update frequency updatePeriodMillis, up to 30 minutes/time, more than 30 minutes/time, the system will automatically re-assign

3. configure When adding a widget, open this activity to check or configure the widget. For example, you can check the login status, account settings, etc. here.

public class WidgetCheckActivity extends Activity {

    protected int mAppWidgetId;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);

        if (savedInstanceState == null) {
            Intent intent = getIntent();
            mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
        } else {
            mAppWidgetId = savedInstanceState.getInt ("widgetId");
        }

        Toast.makeText(this, "pass", Toast.LENGTH_LONG).show();

        Intent resultValue = new Intent();
        resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
        setResult(RESULT_OK, resultValue);
        finish();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("widgetId", mAppWidgetId);
    }
}

The above code demonstrates how to get the appWidgetId and save it in onSaveInstanceState if needed.

When this activity is completed, you need to return to the appWidgetId and close the activity, otherwise the widget will not be added.

 

 

View changing properties, states and events cannot use view-related methods, all views can only be manipulated through the RemoteViews object.

RemoteViews is an object of your layout map, use it to change view properties, states and events based on your view id. for example:

views.setTextViewText(textview_id, "文本");
views.setTextColor(textview_id, text color);
views.setImageViewResource (imageview_id, graphic piece);
views.setViewVisibility(view_id, display state);
views.setOnClickPendingIntent(view_id, click event);

Therefore, when I said that I did not change the background or src directly through the event when making a toggle button, it is because the setting here is not an OnClickListener, but a PendingIntent. We have no way to change the background directly, we can only return through this PendingIntent, and then change the background or src.

There are also some properties that RemoteViews do not provide for direct modification, but provide methods that wrap reflection, such as changing the background:

views.setInt(view_id, "setBackgroundResource", resId);

Related are setLong/setShort/setBoolean/setChar/setByte/setString/setUri/setBitmap/setBundle/setIntent, etc., no longer listed one by one

 

AppWidgetProvider has several callback methods, only a few are listed here

add widget

1. The first widget on the screen

The system will send the broadcast ACTION_APPWIDGET_ENABLED

2. The widgets added later will not have broadcasts such as enable or add. However, each time a widget is added, the configuration activity will still be opened. You can send your own initialization broadcast through the configuration activity. Note: Receive this broadcast and the system's update broadcast sequence indefinite

 

delete widget

1. Delete widget system will send ACTION_APPWIDGET_DELETED broadcast

2. Delete the last widget system will send ACTION_APPWIDGET_DISABLED broadcast

 

When updating the widget, the system will send the ACTION_APPWIDGET_UPDATE broadcast

When the widget size changes, the system will send the ACTION_APPWIDGET_OPTIONS_CHANGED broadcast

 

 

So, if we want to make a refresh button, our steps should be like this

First layout, an ImageView and a ProgressBar

<FrameLayout
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical|right">
    <ImageView
        android:id="@+id/refresh"
        android:layout_width="17dip"
        android:layout_height="17dip"
        android:layout_gravity="center"
        android:scaleType="fitXY"
        android:src="@drawable/app_widget_refresh"/>
    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="17dip"
        android:layout_height="17dip"
        android:layout_gravity="center"
        android:indeterminateDrawable="@drawable/app_widget_progress"
        android:indeterminateDuration="2000"
        android:visibility="gone"/>
</FrameLayout>

Then initialize the View state and set the event listener, then update the widget

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] ids = getAppWidgetIds(context);
int N = ids.length;
for (int i = 0; i < N; i++) {
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
    views.setOnClickPendingIntent(R.id.refresh, makeUpdateStartPendingIntent(context));
    appWidgetManager.updateAppWidget(ids[i], views);
}

Here make a PendingIntent that starts the Service

private static PendingIntent makeUpdateStartPendingIntent(Context context) {
    Intent intent = new Intent(context, WidgetHelperService.class);
    intent.putExtra("action", ACTION_UPDATE_START);
    return PendingIntent.getService(context, REQUEST_REFRESH, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}

Then send the broadcast through the service. As for why not send the broadcast directly, I will tell you later.

 

This is where we can receive and process this broadcast through AppWidgetProvider for updating data operations and refreshing the view

The following is just to simulate the action of refreshing the data. After two seconds, the refresh button restores the previous state.

private static void showProgressBar(final Context context, boolean show) {
    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    int[] ids = getAppWidgetIds(context);
    int N = ids.length;
    for (int i = 0; i < N; i++) {
        final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
        if (show) {
            views.setViewVisibility(R.id.progress_bar, View.VISIBLE);
            views.setViewVisibility(R.id.refresh, View.GONE);
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    context.sendBroadcast(new Intent(ACTION_UPDATE_END));
                }
            }, 2000L);
        } else {
            views.setViewVisibility(R.id.progress_bar, View.GONE);
            views.setViewVisibility(R.id.refresh, View.VISIBLE);
        }
        appWidgetManager.partiallyUpdateAppWidget(ids[i], views);
    }
}

Of course, in this example, we can operate the view directly in the run function without broadcasting

 

There are two ways to update widgets

AppWidgetManager.updateAppWidget(int appWidgetId, RemoteViews views)
AppWidgetManager.partiallyUpdateAppWidget(int appWidgetId, RemoteViews views)

Notice:

1. If you find that sometimes the view event cannot be responded, then you may not call partiallyUpdateAppWidget before calling partiallyUpdateAppWidget. PartiallyUpdateAppWidget is invalid before the widget does not call updateAppWidget (the actual partial version is valid, 5.0+?) . 

2. If you find that the view event still fails to respond sometimes, then you may have set a PendingIntent for the view to send a broadcast through PendingIntent.getBroadcast. It should be noted that after the app is forcibly stopped, the view in the widget can no longer send broadcasts. , but the activity and service can be opened, so my approach is to open the service through the view, and then send the broadcast through the service

 

Finally, why did you not use PendingIntent.getBroadcast directly but use PendingIntent.getService when you set an event for the view and wanted to send a broadcast?

When the app is forcibly stopped, the app in higher versions of android can no longer receive broadcasts, no matter which registration form is used to register the receiver. The same is true after some manufacturers' roms kill the app. So we can only use roundabout tactics. The user clicks the widget trigger event in the launcher to open our own service. At this time, our app has been resurrected, and then we broadcast to ourselves, and we can receive it.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326151226&siteId=291194637