我们常见的桌面小插件,例如一个桌面上系统setgings开关组合,可以控制蓝牙,wifi是否开启,例如一个桌面的小天气等等;这些都是Appwidget的使用例子。
下面介绍如何使用Appwidget;
在使用的过程中涉及到一些关键类,下面一一列举:
该类是BroadcastReceiver的子类,里面的onReceive方法里实现了对几个常用的action的监听;
例如:
AppWidgetManager.ACTION_APPWIDGET_UPDATE
AppWidgetManager.ACTION_APPWIDGET_DELETED
AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED
AppWidgetManager.ACTION_APPWIDGET_ENABLED
AppWidgetManager.ACTION_APPWIDGET_DISABLED
AppWidgetManager.ACTION_APPWIDGET_RESTORED
当接收到其中一个时,会调用对应的空的实现方法,你可以在你继承该AppWidgetProvider的类中
有选择的重写如下方法:
onReceive(Context, Intent):
除了上面的几种action监听外,你还可以自己定义一些来监听。
void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds):
当需要提供RemoteViews时调用
void onDeleted(Context context, int[] appWidgetIds)
当widget实例被删除时调用
void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, Bundle newOptions)
当widget的大小发生改变时调用,
void onEnabled(Context context)
当第一个widget被实例化时调用,
void onDisabled(Context context)
最后一个widget实例被删除时调用,
void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds)
当widget实例备份恢复时调用,
用来更新widiget的状态和获取已经安装的widget的信息,AppWidgetManager getInstance(Context context)获取实例。
该类中也有一些方法可以看看
updateAppWidget(int[] appWidgetIds, RemoteViews views)可以在ACTION_APPWIDGET_UPDATE执行
其中views里面的view持有的bitmap所占内存不能超过screen width x screen height x 4 x 1.5字节
notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId)
更新数据
List<AppWidgetProviderInfo> getInstalledProviders()
获取已经安装的AppWidget
AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId)
根据id获取Info
<span style="font-size:14px;"><appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="40dp" android:minHeight="40dp" android:updatePeriodMillis="86400000" android:previewImage="@drawable/preview" android:initialLayout="@layout/example_appwidget" android:configure="com.example.android.ExampleAppWidgetConfigure" android:resizeMode="horizontal|vertical" android:widgetCategory="home_screen|keyguard" android:initialKeyguardLayout="@layout/example_keyguard"> </appwidget-provider></span>一些常用属性介绍:
<span style="font-size:14px;">resizeMode :RESIZE_NONE,RESIZE_HORIZONTAL,RESIZE_VERTICAL,RESIZE_BOTH:在某些方向上的大小是否可调 widgetCategory:WIDGET_CATEGORY_HOME_SCREEN,WIDGET_CATEGORY_KEYGUARD:在桌面或者锁屏界面显示 ComponentName:对应的是widget在manifest中的name属性 minWidth,minHeight:最小宽和高,单位dp minResizeWidth 和 minResizeHeight : 使用这两个属性,可以允许用户重新调整 widget 的大小,使 widget 的大小可以小于 minWidth 和 minHeight。 updatePeriodMillis,更新频率,单位ms,最好设置为不要比1小时更短的时间,如果短于30分钟,系统还是只会30分钟一次。这个使系统实现的更新机制。 如果我们要更短的话,就只有使用service,AlarmManager initialLayout,widget添加到桌面时的初始布局 initialKeyguardLayout,widget在添加到锁屏界面的初始布局,只有当category是在锁屏类型时有效。 configure:定义了 widget 的配置 Activity previewImage: 指定预览图,该预览图在用户选择 widget 时出现,如果没有提供,则会显示应用的图标</span>
默认情况下(即不设置android:widgetCategory属性),Android是将widget添加到 home screen 中。但在Android 4.2中,若用户希望 widget 可以被添加到lock screen中,可以通过设置 widget 的 android:widgetCategory 属性包含keyguard来完成。
当你把 widget 添加到lock screen中时,你可能对它进行定制化操作,以区别于添加到home screen中的情况。 你能够通过 getAppWidgetOptions() 来进行判断 widget 是被添加到lock screen中,还是home screen中。通过 getApplicationOptions() 获取 Bundle对象,然后读取 Bundle 的OPTION_APPWIDGET_HOST_CATEGORY值:若值为 WIDGET_CATEGORY_HOME_SCREEN, 则表示该 widget 被添加到home screen中; 若值为 WIDGET_CATEGORY_KEYGUARD,则表示该 widget 被添加到lock screen中。
布局中的问题:
布局时要留有widget的margin,padding
大小设置:70 × n − 30,n为多少行或列
支持的根布局:
FrameLayout
LinearLayout
RelativeLayout
GridLayout
支持的控件:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
首先要定义一个AppWidgetProvider ,这里继承该类;
ExampleAppWidgetProvider.java
<span style="font-size:14px;">package com.example.sample; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import android.annotation.SuppressLint; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.widget.RemoteViews; import android.widget.Toast; @SuppressLint("NewApi") public class ExampleAppWidgetProvider extends AppWidgetProvider { // 保存 widget 的id的HashSet,每新建一个 widget 都会为该 widget 分配一个 id。 private static Set idsSet = new HashSet(); // 按钮信息 private static final int BUTTON_SHOW = 1; // 图片数组 private static final int[] ARR_IMAGES = { R.drawable.sample_0, R.drawable.sample_1, R.drawable.sample_2, R.drawable.sample_3, R.drawable.sample_4, R.drawable.sample_5, R.drawable.sample_6, R.drawable.sample_7, }; private static final String TAG = "Provider"; private static Intent intent; // 第一个widget被创建时调用 @Override public void onEnabled(Context context) { Log.d(TAG, "onEnabled"); // 在第一个 widget 被创建时,开启服务 intent = new Intent(context, ExampleAppWidgetService.class); context.startService(intent); } // 最后一个widget被删除时调用 @Override public void onDisabled(Context context) { Log.d(TAG, "onDisabled"); // 在最后一个 widget 被删除时,终止服务 context.stopService(intent); } @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); final String action = intent.getAction(); Log.d(TAG, "OnReceive:Action: " + action); if (ExampleAppWidgetService.UPDATE_WIDGET_ACTION.equals(action)) { // “更新”广播 updateAllAppWidgets(context, AppWidgetManager.getInstance(context), idsSet); } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) { // “按钮点击”广播 Uri data = intent.getData(); int buttonId = Integer.parseInt(data.getSchemeSpecificPart()); if (buttonId == BUTTON_SHOW) { Log.d(TAG, "Button wifi clicked"); Toast.makeText(context, "Button Clicked", Toast.LENGTH_SHORT) .show(); } } } // onUpdate() 在更新 widget 时,被执行, @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { Log.d(TAG, "onUpdate(): appWidgetIds.length=" + appWidgetIds.length); // 每次 widget 被创建时,对应的将widget的id添加到set中 for (int appWidgetId : appWidgetIds) { idsSet.add(Integer.valueOf(appWidgetId)); } prtSet(); } // 当 widget 被初次添加 或者 当 widget 的大小被改变时,被调用 @Override public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); } // widget被删除时调用 @Override public void onDeleted(Context context, int[] appWidgetIds) { Log.d(TAG, "onDeleted(): appWidgetIds.length=" + appWidgetIds.length); // 当 widget 被删除时,对应的删除set中保存的widget的id for (int appWidgetId : appWidgetIds) { idsSet.remove(Integer.valueOf(appWidgetId)); } prtSet(); } // 更新所有的 widget private void updateAllAppWidgets(Context context, AppWidgetManager appWidgetManager, Set set) { Log.d(TAG, "updateAllAppWidgets(): size=" + set.size()); // widget 的id int appID; // 迭代器,用于遍历所有保存的widget的id Iterator it = set.iterator(); while (it.hasNext()) { appID = ((Integer) it.next()).intValue(); // 随机获取一张图片 int index = (new java.util.Random().nextInt(ARR_IMAGES.length)); Log.d(TAG, "onUpdate(): index=" + index); // 获取 example_appwidget.xml 对应的RemoteViews RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.example_appwidget); // 设置显示图片 remoteView.setImageViewResource(R.id.iv_show, ARR_IMAGES[index]); // 设置点击按钮对应的PendingIntent:即点击按钮时,发送广播。 remoteView.setOnClickPendingIntent(R.id.btn_show, getPendingIntent(context, BUTTON_SHOW)); // 更新 widget appWidgetManager.updateAppWidget(appID, remoteView); } } private PendingIntent getPendingIntent(Context context, int buttonId) { Intent intent = new Intent(); intent.setClass(context, ExampleAppWidgetProvider.class); intent.addCategory(Intent.CATEGORY_ALTERNATIVE); intent.setData(Uri.parse("custom:" + buttonId)); PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0); return pi; } // 调试用:遍历set private void prtSet() { int index = 0; int size = idsSet.size(); Iterator it = idsSet.iterator(); Log.d(TAG, "total:" + size); while (it.hasNext()) { Log.d(TAG, index + " -- " + ((Integer) it.next()).intValue()); } } } </span>
接着在res下建立xml文件夹,在文件夹下定义一个文件来描述该provider
example_appwidget_info.xml
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/example_appwidget" android:initialKeyguardLayout="@layout/example_appwidget" android:minHeight="180dp" android:minWidth="180dp" android:previewImage="@drawable/preview" android:resizeMode="horizontal|vertical" android:widgetCategory="home_screen|keyguard" > </appwidget-provider></span>
provider队友的布局;example_appwidget.xml
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="horizontal" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="HomeScreen Widget" /> <Button android:id="@+id/btn_show" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Show" /> </LinearLayout> <ImageView android:id="@+id/iv_show" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"/> </LinearLayout> </span>
ExampleAppWidgetService.java
<span style="font-size:14px;">package com.example.sample; import android.app.Service; import android.content.Intent; import android.os.IBinder; public class ExampleAppWidgetService extends Service { public static final String UPDATE_WIDGET_ACTION = "com.example.sample.ExampleAppWidgetProvider.UPDATE_ACTION"; private Thread mUpdateThread; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); updateWidget(); } @Override public void onDestroy() { super.onDestroy(); // 中断线程,即结束线程。 if (mUpdateThread != null) { mUpdateThread.interrupt(); } } private void updateWidget() { mUpdateThread = new Thread() { public void run() { while (true) { try { Thread.sleep(5 * 1000); //每隔5s更新一次 } catch (InterruptedException e) { e.printStackTrace(); } sendBroadcast(new Intent(UPDATE_WIDGET_ACTION)); } }; }; mUpdateThread.start(); } } </span>接着在manifest里声明,provider,service
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.sample" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="22" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name="com.example.sample.ExampleAppWidgetProvider" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> <action android:name="com.example.sample.ExampleAppWidgetProvider.UPDATE_ACTION"/> </intent-filter> <!-- 描述appwidget的属性信息--> <meta-data android:name="android.appwidget.provider" android:resource="@xml/example_appwidget_info" /> </receiver> <service android:name="com.example.sample.ExampleAppWidgetService"></service> </application> </manifest></span>