編輯:關於Android編程
介紹:Android應用的Widget是應用程序窗口小部件(Widget)是微型的應用程序視圖,它可以被嵌入到其它應用程序中(比如桌面)並接收周期性的更新。你可以通過一個App Widget Provider來發布一個Widget。
圖片:首先上一張圖來給大家看一看效果。
2.1、AppWidgetProvider類
AppWidgetProvider 繼承自 BroadcastReceiver,它能接收 widget 相關的廣播,例如 widget 的更新、刪除、開啟和禁用等。
AppWidgetProvider中的廣播處理函數如下:
onUpdate()
當 widget 更新時被執行。同樣,當用戶首次添加 widget 時,onUpdate() 也會被調用,這樣 widget 就能進行必要的設置工作(如果需要的話) 。但是,如果定義了 widget 的 configure屬性(即android:config,後面會介紹),那麼當用戶首次添加 widget 時,onUpdate()不會被調用;之後更新 widget 時,onUpdate才會被調用。
onAppWidgetOptionsChanged()
當 widget 被初次添加 或者 當 widget 的大小被改變時,執行onAppWidgetOptionsChanged()。你可以在該函數中,根據 widget 的大小來顯示/隱藏某些內容。可以通過 getAppWidgetOptions() 來返回 Bundle 對象以讀取 widget 的大小信息,Bundle中包括以下信息:
OPTION_APPWIDGET_MIN_WIDTH – 包含 widget 當前寬度的下限,以dp為單位。
OPTION_APPWIDGET_MIN_HEIGHT – 包含 widget 當前高度的下限,以dp為單位。
OPTION_APPWIDGET_MAX_WIDTH – 包含 widget 當前寬度的上限,以dp為單位。
OPTION_APPWIDGET_MAX_HEIGHT – 包含 widget 當前高度的上限,以dp為單位。
onAppWidgetOptionsChanged() 是 Android 4.1 引入的。
onDeleted(Context, int[])
當 widget 被刪除時被觸發。
onEnabled(Context)
當第1個 widget 的實例被創建時觸發。也就是說,如果用戶對同一個 widget 增加了兩次(兩個實例),那麼onEnabled()只會在第一次增加widget時觸發。
onDisabled(Context)
當最後1個 widget 的實例被刪除時觸發。
onReceive(Context, Intent)
接收到任意廣播時觸發,並且會在上述的方法之前被調用。
總結,AppWidgetProvider 繼承於 BroadcastReceiver。實際上,App Widge中的onUpdate()、onEnabled()、onDisabled()等方法都是在 onReceive()中調用的,是onReceive()對特定事情的響應函數。
2.2、AppWidgetProviderInfo
AppWidgetProviderInfo描述一個App Widget元數據,比如App Widget的布局,更新頻率,以及AppWidgetProvider 類,這個文件是在res/xml中定義的,後綴為xml。下面是一個事例,還有AppWidgetProviderInfo中的一些常用的屬性介紹:
文件名:widget_weather.xml
示例說明:
minWidth 和minHeight
它們指定了App Widget布局需要的最小區域。 缺省的App Widgets所在窗口的桌面位置基於有確定高度和寬度的單元網格中。如果App Widget的最小長度或寬度和這些網格單元的尺寸不匹配,那麼這個App Widget將上捨入(上捨入即取比該值大的最接近的整數——譯者注)到最接近的單元尺寸。
注意:app widget的最小尺寸,不建議比 “4x4” 個單元格要大。關於app widget的尺寸,後面在詳細說明。
minResizeWidth 和 minResizeHeight
它們屬性指定了 widget 的最小絕對尺寸。也就是說,如果 widget 小於該尺寸,便會因為變得模糊、看不清或不可用。 使用這兩個屬性,可以允許用戶重新調整 widget 的大小,使 widget 的大小可以小於 minWidth 和 minHeight。
注意:
(01) 當 minResizeWidth 的值比 minWidth 大時,minResizeWidth 無效;當 resizeMode 的取值不包括 horizontal 時,minResizeWidth 無效。
(02) 當 minResizeHeight 的值比 minHeight 大時,minResizeHeight 無效;當 resizeMode 的取值不包括 vertical 時,minResizeHeight 無效。
updatePeriodMillis
它定義了 widget 的更新頻率。實際的更新時機不一定是精確的按照這個時間發生的。建議更新盡量不要太頻繁,最好是低於1小時一次。 或者可以在配置 Activity 裡面供用戶對更新頻率進行配置。 實際上,當updatePeriodMillis的值小於30分鐘時,系統會自動將更新頻率設為30分鐘!關於這部分,後面會詳細介紹。
注意: 當更新時機到達時,如果設備正在休眠,那麼設備將會被喚醒以執行更新。如果更新頻率不超過1小時一次,那麼對電池壽命應該不會造成多大的影響。 如果你需要比較頻繁的更新,或者你不希望在設備休眠的時候執行更新,那麼可以使用基於 alarm 的更新來替代 widget 自身的刷新機制。將 alarm 類型設置為 ELAPSED_REALTIME 或 RTC,將不會喚醒休眠的設備,同時請將 updatePeriodMillis 設為 0。
initialLayout
指向 widget 的布局資源文件
configure
可選屬性,定義了 widget 的配置 Activity。如果定義了該項,那麼當 widget 創建時,會自動啟動該 Activity。
previewImage
指定預覽圖,該預覽圖在用戶選擇 widget 時出現,如果沒有提供,則會顯示應用的圖標。該字段對應在 AndroidManifest.xml 中 receiver 的 android:previewImage 字段。由 Android 3.0 引入。
autoAdvanceViewId
指定一個子view ID,表明該子 view 會自動更新。在 Android 3.0 中引入。
resizeMode
指定了 widget 的調整尺寸的規則。可取的值有: “horizontal”, “vertical”, “none”。”horizontal”意味著widget可以水平拉伸,“vertical”意味著widget可以豎值拉伸,“none”意味著widget不能拉伸;默認值是”none”。Android3.1 引入。
widgetCategory
指定了 widget 能顯示的地方:能否顯示在 home Screen 或 lock screen 或 兩者都可以。它的取值包括:”home_screen” 和 “keyguard”。Android 4.2 引入。
initialKeyguardLayout
指向 widget 位於 lockscreen 中的布局資源文件。Android 4.2 引入。
其中比較重要的是:android:initialLayout=”@layout/widget_weather”這句話,這句話指向的是Widget的布局文件名。
就是一個普通的布局文件,不過長寬要設置合適。
如圖,我就給一張布局的圖片,布局代碼自行完成。
注意:這裡介紹一些關於布局的知識,也是摘自別人的博客,一起學習一下。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxibG9ja3F1b3RlPg0KCTxwPjGhosztvNMgd2lkZ2V0ILW9bG9jayBzY3JlZW7W0DwvcD4NCgk8cD7ErMjPx+m/9s/CKLy0srvJ6NbDYW5kcm9pZDp3aWRnZXRDYXRlZ29yecr00NQpo6xBbmRyb2lkyse9q3dpZGdldMztvNO1vSBob21lIHNjcmVlbiDW0KGjPGJyIC8+DQoJtavU2kFuZHJvaWQgNC4y1tCjrMj008O7p8+jzfsgd2lkZ2V0IL/J0tSxu8ztvNO1vWxvY2sgc2NyZWVu1tCjrL/J0tTNqLn9yejWwyB3aWRnZXQgtcQ8YnIgLz4NCglhbmRyb2lkOndpZGdldENhdGVnb3J5IMr00NSw/Lqsa2V5Z3VhcmTAtM3qs8mhozwvcD4NCgk8cD61scTjsNEgd2lkZ2V0IMztvNO1vWxvY2sgc2NyZWVu1tDKsaOsxOO/ycTcttTL/L340NC2qNbGu6+y2df3o6zS1Mf4sfDT2sztvNO1vWhvbWUgc2NyZWVu1tC1xMfpv/ahoyDE48TcubvNqLn9PGJyIC8+DQoJZ2V0QXBwV2lkZ2V0T3B0aW9ucygpIMC0vfjQ0MXQts8gd2lkZ2V0IMrHsbvM7bzTtb1sb2NrIHNjcmVlbtbQo6y7ucrHaG9tZSBzY3JlZW7W0KGjzai5/TxiciAvPg0KCWdldEFwcGxpY2F0aW9uT3B0aW9ucygpILvxyKEgQnVuZGxlttTP86OsyLu687bByKEgQnVuZGxlPGJyIC8+DQoJtcRPUFRJT05fQVBQV0lER0VUX0hPU1RfQ0FURUdPUlnWtaO6yPTWtc6qIFdJREdFVF9DQVRFR09SWV9IT01FX1NDUkVFTqOsINTyse3KvrjDPGJyIC8+DQoJd2lkZ2V0ILG7zO2807W9aG9tZSBzY3JlZW7W0KO7IMj01rXOqiBXSURHRVRfQ0FURUdPUllfS0VZR1VBUkSjrNTyse3KvrjDIHdpZGdldDxiciAvPg0KCbG7zO2807W9bG9jayBzY3JlZW7W0KGjPC9wPg0KCTxwPsHtzeKjrMTj06a4w86qzO2807W9bG9jayBzY3JlZW7W0LXEIHdpZGdldCC1pbbAyrnTw9K7uPZsYXlvdXSjrL/J0tTNqLn9PGJyIC8+DQoJYW5kcm9pZDppbml0aWFsS2V5Z3VhcmRMYXlvdXQgwLTWuLaooaO2+CB3aWRnZXQgzO2807W9aG9tZSBzY3JlZW7W0LXEbGF5b3V01PK/ydLUzai5/TxiciAvPg0KCWFuZHJvaWQ6aW5pdGlhbExheW91dCDAtNa4tqihozwvcD4NCgk8cD4yoaKyvL7WPGJyIC8+DQoJPGltZyBhbHQ9"這裡寫圖片描述" src="/uploadfile/Collfiles/20160505/20160505090853688.png" title="\" />
如上圖所示,典型的App Widget有三個主要組件:一個邊界框(A bounding box),一個框架(a Frame),和控件的圖形控件(Widget Controls)和其他元素。App Widget並不支持全部的視圖窗口,它只是支持視圖窗口的一個子集,後面會詳細說明支持哪些視圖窗口。
要設計美觀的App Widget,建議在“邊界框和框架之間(Widget Margins)”以及“框架和控件(Widget
Padding)”之間填留有空隙。在Android4.0以上的版本,系統為自動的保留這些空隙。
3、Widget窗口大小
在AppWidgetProviderInfo中已經介紹了,minWidth 和minHeight 用來指定了App
Widget布局需要的最小區域。缺省的App Widgets所在窗口的桌面位置基於有確定高度和寬度的單元網格中。如果App
Widget的最小長度或寬度和這些網格單元的尺寸不匹配,那麼這個App
Widget將上捨入(上捨入即取比該值大的最接近的整數——譯者注)到最接近的單元尺寸。
例如,很多手機提供4x4網格,平板電腦能提供8x7網格。當widget被添加到時,在滿足minWidth和minHeight約束的前提下,它將被占領的最小數目的細胞。
粗略計算minWidth和minHeight,可以參考下面表格:
Widget並不支持所有的布局和控件,而僅僅只是支持Android布局和控件的一個子集。
(01) App Widget支持的布局:
FrameLayout
LinearLayout
RelativeLayout
GridLayout
(02) App Widget支持的控件:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
文件名:weather_widget_info.xml
說明:
(01) android:previewImage,用於指定預覽圖片。即搜索到widget時,查看到的圖片。若沒有設置的話,系統為指定一張默認圖片。
(02) android:updatePeriodMillis 更新widget的時間間隔(ms)。在實際測試中,發現設置android:updatePeriodMillis的值為5秒時,不管用!跟蹤android源代碼,發現:
當android:updatePeriodMillis的值小於30分鐘時,會被設置為30分鐘。也就意味著,即使將它的值設為5秒,實際上也是30分鐘之後才更新。因此,我們若向動態的更新widget的某組件,最好通過service、AlarmManager、Timer等方式;本文采用的是service。
文件名:AppWidgetProvider.java
public class AppWidgetProvider extends android.appwidget.AppWidgetProvider {
Gson gson;
RecentWeatherBean recentWeatherBean;
RetDataBean retDataBean;
TodayBean todayBean;
GetDate getDate;
Context context;
//保存地址和ID
SharedPreferences sharedPreferences;
//啟動AppWidgetService服務對應的action
private final Intent SERVICE_INTENT = new Intent("android.appwidget.action.APP_WIDGET_SERVICE");
//更新widget的廣播對應的action
private final String ACTION_UPDATE_ALL = "com.llay.widget.UPDATE_ALL";
//onUpdate()在更新widget時,被執行(當用戶點擊wiget界面時候可以調用這個方法)
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int i = 0; i < appWidgetIds.length; i++) {
//創建一個Intent對象,跳轉到app界面
Intent intent = new Intent(context, GetLocationActivity.class);
//創建一個PendingIntent,包主intent
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
//獲取wiget布局
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_weather);
remoteViews.setOnClickPendingIntent(R.id.widget_line, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews);
}
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
//當widget被初次添加或者當widget的大小被改變時,被調用
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
}
//第一個widget被創建時調用
@Override
public void onEnabled(Context context) {
//在第一個widget被創建時,開啟服務
SERVICE_INTENT.setPackage("com.llay.admin.weather");
context.startService(SERVICE_INTENT);
super.onEnabled(context);
}
//最後一個widget被刪除時調用
@Override
public void onDisabled(Context context) {
//在最後一個widget被刪除時,終止服務
SERVICE_INTENT.setPackage("com.llay.admin.weather");
context.stopService(SERVICE_INTENT);
super.onDisabled(context);
}
//接收廣播的回調函數
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
this.context = context;
if (ACTION_UPDATE_ALL.equals(action)) {
getDate = new GetDate();
getDate.execute("http://apis.baidu.com/apistore/weatherservice/recentweathers");
} else {
super.onReceive(context, intent);
}
}
//請求數據
public class GetDate extends AsyncTask {
@Override
protected String doInBackground(String... params) {
sharedPreferences = context.getSharedPreferences("CityAndCode", Context.MODE_PRIVATE);
String recentJson = Utils.request(params[0], "cityname=" + sharedPreferences.getString("locationCityName", "南通") + "&cityid=" + sharedPreferences.getInt("locationCityCode", 101190501));
return recentJson;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
if (s != null) {
gson = new Gson();
recentWeatherBean = new RecentWeatherBean();
recentWeatherBean = gson.fromJson(s, RecentWeatherBean.class);
retDataBean = new RetDataBean();
retDataBean = recentWeatherBean.getRetData();
todayBean = new TodayBean();
todayBean = retDataBean.getToday();
//更新界面
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_weather);
switch (todayBean.getType()) {
case "晴":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.sunny);
break;
case "多雲":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.cloudy);
break;
case "陰":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.overcast);
break;
case "陣雨":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.shower);
break;
case "雷陣雨":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.thundershower);
break;
case "小雨":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.light_rain);
break;
case "中雨":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.moderate_rain);
break;
case "大雨":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.heavy_rain);
break;
case "暴雨":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.storm);
break;
case "大暴雨":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.heavy_storm);
break;
case "小到中雨":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.light_to_moderate_rain);
break;
case "中到大雨":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.moderate_to_heavy_rain);
break;
case "大到暴雨":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.heavy_to_storm);
break;
case "暴雨到大暴雨":
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.storm_to_heavy_storm);
break;
default:
remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.undefined);
break;
}
remoteViews.setTextViewText(R.id.widget_type, todayBean.getType());
remoteViews.setTextViewText(R.id.widget_curtemp, todayBean.getCurTemp());
remoteViews.setTextViewText(R.id.widget_date, todayBean.getDate());
remoteViews.setTextViewText(R.id.widget_location, retDataBean.getCity());
remoteViews.setTextViewText(R.id.widget_week, todayBean.getWeek());
remoteViews.setTextViewText(R.id.widget_lowtemp, todayBean.getLowtemp());
remoteViews.setTextViewText(R.id.widget_hightemp, todayBean.getHightemp());
remoteViews.setTextViewText(R.id.widget_fengxiang, todayBean.getFengxiang());
remoteViews.setTextViewText(R.id.widget_fengli, todayBean.getFengli());
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
ComponentName componentName = new ComponentName(context, AppWidgetProvider.class);
appWidgetManager.updateAppWidget(componentName, remoteViews);
}
}
}
}
說明:
(01)當我們創建第一個widget到桌面時,會執行onEnabled()。在onEnabled()中通過 context.startService(SERVICE_INTENT) 啟動服務AppWidgetService。服務的作用就是每隔5秒發送一個ACTION_UPDATE_ALL廣播給我們,用於更新widget中的數據。僅僅當我們創建第一個widget時才會啟動服務,因為onEnabled()只會在第一個widget被創建時才執行。
(02)當我們刪除最後一個widget到桌面時,會執行onDisabled()。在onDisabled()中通過 context.stopService(SERVICE_INTENT) 終止服務AppWidgetService。僅僅當我們刪除最後一個widget時才會終止服務,因為onDisabled()只會在最後一個widget被刪除時才執行。
(03)本工程中,每添加一個widget都會執行onUpdate()。例外情況:在定義android:configure的前提下,第一次添加widget時不會執行onUpdate(),而是執行android:configure中定義的activity。在onUpdate()方法中,有兩個類要特別注意:PendingIntent和RemoteViews。PendingIntent用戶包裹住一個Intent,然後再調用remoteViews.setOnClickPendingIntent(R.id.widget_line, pendingIntent);方法時,運行Intent。RemoteViews用戶獲取Widget中的控件,點擊Widget中的控件,調用remoteViews.setOnClickPendingIntent(R.id.widget_line, pendingIntent);方法,可以實現一些功能,如點擊Widget然後打開應用。
(04)onReceive()中,更新桌面的widget 以及響應按鈕點擊廣播。當收到ACTION_UPDATE_ALL廣播時,調用getDate.execute()方法來請求接口,然後再onPostExecute()中更新的widget的數據。
因為AppWidgetProvider原本就是一個BroadCastReceiver,需要在AndroidManifest.xml中注冊。
文件名:AppWidgetService.java
public class AppWidgetService extends Service {
//更新widget的廣播對應的action
private final String ACTION_UPDATE_ALL = "com.llay.widget.UPDATE_ALL";
//周期性更新widget的周期
private static final int UPDATE_TIME = 10000;
//周期性更新widget的線程
private UpdateThread mUpdateThread;
private Context mContext;
//更新周期的計數
private int count = 0;
@Override
public void onCreate() {
//創建並開啟線程UpdateThread
mUpdateThread = new UpdateThread();
mUpdateThread.start();
mContext = this.getApplicationContext();
super.onCreate();
}
@Override
public void onDestroy() {
//中斷線程,即結束線程。
if (mUpdateThread != null) {
mUpdateThread.interrupt();
}
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
//服務開始時,即調用startService()時,onStartCommand()被執行。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
//線程中發送廣播
private class UpdateThread extends Thread {
@Override
public void run() {
super.run();
try {
count = 0;
//一直在後台中發送廣播,action為"com.llay.widget.UPDATE_ALL"
while (true) {
count++;
Intent updateIntent = new Intent(ACTION_UPDATE_ALL);
mContext.sendBroadcast(updateIntent);
Thread.sleep(UPDATE_TIME);
}
} catch (InterruptedException e) {
//將InterruptedException定義在while循環之外,意味著拋出InterruptedException異常時,終止線程。
e.printStackTrace();
}
}
}
}
在AndroidManifest.xml中注冊Service
說明:
(01) onCreate() 在創建服務時被執行。它的作用是創建並啟動線程UpdateThread()。
(02) onDestroy() 在銷毀服務時被執行。它的作用是注銷線程UpdateThread()。
(03) 服務UpdateThread 每隔10秒,發送1個廣播ACTION_UPDATE_ALL。廣播ACTION_UPDATE_ALL在AppWidgetProvider被處理,用來更新widget中的數據。
1、當創建一個Widget時,調用onUpdate()方法和onEnabled()方法。
onUpdage()方法,用於用戶點擊桌面上的Widget,可以開發應用。核心代碼如下
for (int i = 0; i < appWidgetIds.length; i++) {
//創建一個Intent對象,跳轉到app界面
Intent intent = new Intent(context, GetLocationActivity.class);
//創建一個PendingIntent,包裹住intent
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
//獲取wiget布局
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_weather);
remoteViews.setOnClickPendingIntent(R.id.widget_line, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews);
}
super.onUpdate(context, appWidgetManager, appWidgetIds);
onEnabled()方法,用於新建第一個Widget時,發送Service對應的action,運行AppWidgetService類。核心代碼如下
//第一個widget被創建時調用
@Override
public void onEnabled(Context context) {
//在第一個widget被創建時,開啟服務
SERVICE_INTENT.setPackage("com.llay.admin.weather");
context.startService(SERVICE_INTENT);
super.onEnabled(context);
}
2、運行Service,每10S發送BroadCast對應的action,核心代碼如下
//線程中發送廣播
private class UpdateThread extends Thread {
@Override
public void run() {
super.run();
try {
count = 0;
//一直在後台中發送廣播,action為"com.llay.widget.UPDATE_ALL"
while (true) {
count++;
Intent updateIntent = new Intent(ACTION_UPDATE_ALL);
mContext.sendBroadcast(updateIntent);
Thread.sleep(UPDATE_TIME);
}
} catch (InterruptedException e) {
//將InterruptedException定義在while循環之外,意味著拋出InterruptedException異常時,終止線程。
e.printStackTrace();
}
}
}
3、在AppWidgetProvider中的onReceive()方法中接收,BroadCast發送的action,執行相應的更新數據的方法。核心代碼如下
//接收廣播的回調函數
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
this.context = context;
if (ACTION_UPDATE_ALL.equals(action)) {
getDate = new GetDate();
getDate.execute("http://apis.baidu.com/apistore/weatherservice/recentweathers");
} else {
super.onReceive(context, intent);
}
}
創建第一個Widget,發送action為”android.appwidget.action.APP_WIDGET_SERVICE”->
AndroidManifest.xml中收到此action,運行AppWidgetService.java文件->
AppWidgetService.java中每10S發送一個廣播action為”com.llay.widget.UPDATE_ALL”->
在AppWidgetProvider.java中的onReceive()方法,接收這個廣播action,執行更新->
刪除最後一個Widget,停止Service
效果圖展示,圖片有點卡,耐心看會,原程序是很流暢的實現步驟: 聲明變量 初始化畫筆、文本大小和坐標 onMeasure()適配wrap_content的寬高 on
本文主要記錄了Launcher3拖動時的流程和代碼記錄,在桌面圖標拖動時會引起圖標的重排,拖動時受影響的圖標在文中由item或cell來表示。 圖標點擊效果和搖動效
新的權限獲取方式除了要求像之前版本一樣在AndroidManifest文件中靜態申請之外,應用還需根據需要請求權限,方式采用向用戶顯示一個請求權限的對話框。這些被動態申
書接上篇 《Android網絡請求庫 - Say hello to OkHttp》,今天接著來簡單的看一下常用的網絡請求庫中的第二種庫:Volley。 Volley是谷歌