说说Android桌面(Launcher应用)背后的故事(九)——让我的桌面多姿多彩
到這里我們的Launcher已經可以跑起來了,而且效果也如系統Launcher一般,但是,遺憾的是,我們的桌面上似乎都是一個摸樣的Shortcut,而再看看系統桌面上,Search框,天氣控件啊,各種大小參差不齊,界面上的控件豐富多彩。桌面上除了一個個Shortcut之外,還應該有各種大小不一的控件——Widget。
??????? 要想讓我們的桌面也支持Widget,我們就要對Widget這個東西稍加研究一翻。Widget是一種特殊的獨立體,可以嵌入在另一個應用中,只要這個應用實現為一個WidgetHost,那么它就可以容納各式各樣的Widget。Android已經提供了一套Widget開發接口,包括兩個方面:一個是創建Widget,一個是創建WidgetHost。為了避免直接將復雜的代碼加入到已經實現的功能當中,我們先通過兩個小Demo來分別看看如何開發一個Widget,以及如何實現一個WidgetHost,這樣,通過這兩個Demo,再將實現支持Widget的代碼添加到我們的Launcher中,就比較容易理解了。
?????? 在此申明:以下兩個Demo,從本人的個人代碼庫中取得,由于時間較久,不知道是收集網上的,還是純粹自己寫的。如果您發現是您的功勞,本人深表抱歉,但是您的Demo寫的很有參考價值,借用一下,在此說聲:Thank you!
一、開發一個Widget——桌面小部件
開發一個Widget,做如下準備
1、appwidget-provider:在res\xml\目錄下創建一個.xml文件,里面內容如下:
[html] view plaincopy
<?xml version="1.0" encoding="utf-8"?> ?
<appwidget-provider ?
? xmlns:android="http://schemas.android.com/apk/res/android" ?
? android:minWidth="294dip" ?
? android:minHeight="150dip" ?
? android:updatePeriodMillis="0" ?
? android:initialLayout="@layout/widget_demo"> ?
???? ?
</appwidget-provider> ?
解釋一下:這個文件主要定義了小部件需要在桌面上占據的大小以及指定小部件所用的布局文件。其中android:updatePeriodMillis指的是每隔多長時間更新一下小部件。這個值在1.5之后的版本中好像至少要30分鐘以上才會生效,但是,本人沒有試過。只知道,將其設置成幾秒,幾分鐘是肯定無效的。
2、繼承AppWidgetProvider,實現自己需要的邏輯:不要被其名字給蒙騙了,它是一個BroadcastReceiver,它實現了onReceive方法,同時,提供了幾個自己的方法:
??????? * onUpdate(Context,AppWidgetManager,appWidgetIds)
??????? * onDeleted(Context context, int[] appWidgetIds)
??????? * onEnabled(Context context)
??????? * onDisabled(Context context)
具體每個方法的含義,自己查下文檔。我們經常開發的時候需要的就是實現onUpdate方法,這個方法就是更新添加到桌面上的Widget
3、在manifest中配置:
[html] view plaincopy
<receiver android:name="DemoAppWidgetProvider"> ?
??? <meta-data android:name="android.appwidget.provider" ?
??????????? android:resource="@xml/widget_demo" />??? ?
??? <intent-filter> ?
??????? <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> ?
??? </intent-filter> ?
?
</receiver> ?
其中指定了一個meta-data,用來指定Widget說明文件。同時,intent-filter中使用android.appwidget.action.APPWIDGET_UPDATE這個Action。
4、Widget的布局文件:
[html] view plaincopy
<?xml version="1.0" encoding="utf-8"?> ?
<LinearLayout ?
? xmlns:android="http://schemas.android.com/apk/res/android" ?
? android:orientation="vertical" ?
? android:layout_width="match_parent" ?
? android:layout_height="match_parent"> ?
??? <LinearLayout? ?
??????? android:orientation="vertical" ?
??????? android:layout_width="fill_parent" ?
??????? android:layout_height="100dip" ?
??????? android:background="#F3F3F3"> ?
???????? ?
??????? <TextView android:id="@+id/demo" ?
??????????? android:layout_width="wrap_content" ?
??????????? android:layout_height="wrap_content" ?
??????????? android:layout_marginLeft="20dip" ?
??????????? android:layout_marginTop="20dip" ?
??????????? android:text="this is a demo" ?
??????????? android:textSize="20sp" ?
??????????? android:textStyle="bold"/>??? ?
?
??? </LinearLayout> ?
???? ?
??? <LinearLayout? ?
??????? android:orientation="horizontal" ?
??????? android:layout_width="fill_parent" ?
??????? android:layout_height="50dip" ?
??????? android:background="@drawable/widget_bottom"> ?
???????? ?
??????? <Button android:id="@+id/pre" ?
??????????? android:layout_width="wrap_content" ?
??????????? android:layout_height="wrap_content" ?
??????????? android:background="@drawable/pre_bg" ?
??????????? android:layout_marginLeft="20dip" ?
??????????? android:layout_marginTop="2dip"/> ?
???????????? ?
??????? <Button android:id="@+id/next" ?
??????????? android:layout_width="wrap_content" ?
??????????? android:layout_height="wrap_content" ?
??????????? android:background="@drawable/next_bg" ?
??????????? android:layout_marginLeft="20dip" ?
??????????? android:layout_marginTop="2dip"/>???????????? ?
???????????? ?
??? </LinearLayout> ?
???? ?
???? ?
</LinearLayout> ?
說明:這個就是正常的布局文件。但是關于布局文件的規格系統文檔中有個指導,需要自己按照自導文檔去看看關于橫豎屏中定義Widget需要注意的問題。主要是大小問題。
除了上面這些,你可能還需要一個Service。假如,要完成的操作比較耗時,則需要創建一個Service來完成主要的功能。這個Demo中就創建了一個Service。
下面直接上代碼了:
[java] view plaincopy
public class DemoAppWidgetProvider extends AppWidgetProvider { ?
???? ?
??? public static final ComponentName APPWIDGET_COMPONENT = new ComponentName("demo.widget", "demo.widget.DemoAppWidgetProvider"); ?
???? ?
??? public void onUpdate(Context context, AppWidgetManager appWidgetManager ?
??????????? , int[] appWidgetIds) { ?
???????? ?
??????? /**
???????? * 當Widget被添加到桌面的時候執行
???????? */ ?
???????? ?
??????? final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_demo); ?
???????? ?
??????? linkButtons(context, views); ?
???????? ?
??????? final AppWidgetManager gm = AppWidgetManager.getInstance(context); ?
???????? ?
??????? if(appWidgetIds != null){ ?
??????????? gm.updateAppWidget(appWidgetIds, views); ?
??????? }else{ ?
??????????? gm.updateAppWidget(APPWIDGET_COMPONENT, views); ?
??????? } ?
???????? ?
??????? //啟動DemoService ?
??????? context.startService(new Intent(ActionDefinition.ACTION_APP_WIDGET_SERVICE)); ?
??? } ?
?
??? /**
???? * 為按鈕綁定事件
???? * @param context
???? * @param views
???? */ ?
??? private void linkButtons(Context context, RemoteViews views) { ?
???????? ?
??????? final ComponentName serviceName = new ComponentName(context, DemoService.class); ?
??????? Intent intent = null; ?
??????? PendingIntent pIntent = null; ?
???????? ?
??????? //為pre按鈕綁定onClick事件 ?
??????? intent = new Intent(ActionDefinition.ACTION_APP_WIDGET_PREV); ?
??????? intent.setComponent(serviceName); ?
??????? pIntent = PendingIntent.getService(context, 0, intent, 0); ?
?
??????? views.setOnClickPendingIntent(R.id.pre, pIntent); ?
???????? ?
??????? //為next按鈕綁定onClick事件 ?
??????? intent = new Intent(ActionDefinition.ACTION_APP_WIDGET_NEXT); ?
??????? intent.setComponent(serviceName); ?
??????? pIntent = PendingIntent.getService(context, 0, intent, 0); ?
??????? views.setOnClickPendingIntent(R.id.next, pIntent); ?
???????? ?
??? } ?
上面是繼承AppWidgetProvider 實現了一個AppWidgetProvider ,在onUpdate方法中綁定了兩個按鈕的事件行為。所有的行為似乎都是由RemoteView來完成的,但是RemoteView并不是真正顯示在桌面上的你看到的View,它只是一個封裝了一些操作和描述,在Widget和WidgetHost之間傳遞信息。
上面程序中,使用了一個Service,如下:
[java] view plaincopy
/**
?* 該Service負責更新App Widget;同時處理pre按鈕和next按鈕的onClick事件
?* @author liner
?*
?*/ ?
public class DemoService extends Service { ?
?
??? private String[] contentDemos = new String[]{ ?
??????????? "Demo1", ?
??????????? "Demo2", ?
??????????? "Demo3", ?
??????????? "Demo4" ?
??? }; ?
???? ?
??? private int currentDisplayItem = 0; ?
???? ?
??? public void onCreate(){ ?
??????? super.onCreate(); ?
??????? Log.v("DemoService", "onCreate execute"); ?
?
??? } ?
???? ?
??? public void onStart(Intent intent, int startId){ ?
??????? super.onStart(intent, startId); ?
??????? Log.v("DemoService", "onStart execute"); ?
???????? ?
??????? //這里獲取Action信息,判斷其是哪個行為 ?
??????? String action = intent.getAction(); ?
???????? ?
??????? if(action.equals(ActionDefinition.ACTION_APP_WIDGET_PREV)){ ?
???????????? ?
??????????? doPrev(); ?
???????????? ?
??????? }else if(action.equals(ActionDefinition.ACTION_APP_WIDGET_NEXT)){ ?
???????????? ?
??????????? doNext(); ?
???????????? ?
??????? }else{ //if(action.equals(ActionDefinition.ACTION_APP_WIDGET_SERVICE)) ?
??????????? notifyWidget(); ?
??????? } ?
??? } ?
???? ?
?
??? //通知更新 ?
??? private void notifyWidget() { ?
??????? ComponentName widget = new ComponentName(this, DemoAppWidgetProvider.class); ?
??????? AppWidgetManager manager = AppWidgetManager.getInstance(this); ?
???????? ?
??????? RemoteViews views = buildUpdateViews(); ?
???????? ?
??????? manager.updateAppWidget(widget, views); ?
??? } ?
?
??? //每次更新的時候,都是創建一個RemoteView來完成更新的 ?
??? private RemoteViews buildUpdateViews() { ?
??????? RemoteViews views = new RemoteViews(this.getPackageName(), R.layout.widget_demo); ?
???????? ?
??????? views.setTextViewText(R.id.demo, contentDemos[currentDisplayItem]); ?
???????? ?
??????? return views; ?
??? } ?
?
??? private void doNext() { ?
??????? if(currentDisplayItem >= contentDemos.length -1){ ?
??????????? currentDisplayItem = 0; ?
??????? }else{ ?
??????????? currentDisplayItem++; ?
??????? } ?
???????? ?
??????? notifyWidget(); ?
??? } ?
?
??? private void doPrev() { ?
???????? ?
??????? if(currentDisplayItem > 0){ ?
??????????? currentDisplayItem--; ?
??????? }else{ ?
??????????? currentDisplayItem = contentDemos.length-1; ?
??????? } ?
???????? ?
??????? notifyWidget(); ?
??? } ?
?
??? @Override ?
??? public IBinder onBind(Intent intent) { ?
??????? return null; ?
??? } ?
?
} ?
當Widget被添加到桌面上的時候,onUpdate方法執行了過后,我們點擊Widget上面的pre和next按鈕,實際上都觸發了Service,通過Service來完成更新操作的。
到這里,我們知道如何開發一個Widget了。下面我們再來看看如何開發一個可以容納Widget的應用。效果如下:
?
二、開發一個WidgetHost——讓我的應用也可以海納百川
為了開發一個WidgetHost,我們首先需要一個可以容納各種Widget的布局控件,系統Launcher中使用的是CellLayout,那么我們也繼承ViewGroup簡單實現一個Layout。
?
[java] view plaincopy
public class WidgetLayout extends ViewGroup { ?
???? ?
??? private int[] cellInfo = new int[2]; ?
???? ?
??? private OnLongClickListener mLongClickListener; ?
???? ?
??? public WidgetLayout(Context context){ ?
??????? this(context, null); ?
??? } ?
???? ?
??? public WidgetLayout(Context context, AttributeSet attrs){ ?
??????? this(context, attrs, 0); ?
??? } ?
?
??? public WidgetLayout(Context context, AttributeSet attrs, int defStyle) { ?
??????? super(context, attrs, defStyle); ?
?
??? } ?
???? ?
??? //@1:當長按的時候觸發該動作,記錄長按的位置 ?
??? public boolean dispatchTouchEvent(MotionEvent event){ ?
??????? cellInfo[0] = (int)event.getX(); ?
??????? cellInfo[1] = (int)event.getY(); ?
???????? ?
??????? Log.e("event:", cellInfo[0]+","+cellInfo[1]); ?
??????? return super.dispatchTouchEvent(event); ?
??? } ?
???? ?
??? //@2:當用戶選擇了某個widget時,觸發這個動作,將其所選的widget(child)添加到桌面上 ?
??? public void addInScreen(View child, int width, int height){ ?
??????? LayoutParams params = new LayoutParams(width, height); ?
??????? params.x = cellInfo[0]; ?
??????? params.y = cellInfo[1]; ?
??????? //params.width = width ?
??????? child.setOnLongClickListener(mLongClickListener); ?
???????? ?
??????? Log.e("size", "x,y,width,height"+params.x+","+params.y+","+params.width+","+params.height); ?
???????? ?
??????? addView(child, params); ?
??? } ?
???? ?
??? //@3:測量每個孩子的寬度和高度 ?
??? public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ ?
??????? //super.onMeasure(widthMeasureSpec, heightMeasureSpec); ?
???????? ?
??????? final int count = getChildCount(); ?
??????? LayoutParams lp = null; ?
???????? ?
??????? for(int i=0; i<count;i++){ ?
??????????? View child = getChildAt(i); ?
??????????? lp = (LayoutParams)child.getLayoutParams(); ?
??????????? Log.e("onMeasure:w,h", lp.width+","+lp.height); ?
??????????? child.measure(MeasureSpec.makeMeasureSpec(lp.width,MeasureSpec.EXACTLY),? ?
??????????????????? MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY)); ?
??????? } ?
???????? ?
??????? setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); ?
??? } ?
?
??? //@4:將每個孩子按照其layoutparams中定義的進行布局 ?
??? @Override ?
??? protected void onLayout(boolean changed, int l, int t, int r, int b) { ?
??????? final int count = getChildCount(); ?
??????? LayoutParams lp = null; ?
???????? ?
??????? for(int i=0; i<count;i++){ ?
??????????? View child = getChildAt(i); ?
??????????? lp = (LayoutParams)child.getLayoutParams(); ?
??????????? child.layout(lp.x, lp.y, lp.x+lp.width, lp.y+lp.height); ?
??????? }??????? ?
??? } ?
???? ?
??? public static class LayoutParams extends ViewGroup.LayoutParams{ ?
???????? ?
??????? int x; ?
???????? ?
??????? int y; ?
???????? ?
??????? public LayoutParams(int width, int height) { ?
??????????? super(width, height); ?
??????????? this.width = width; ?
??????????? this.height = height; ?
??????? } ?
???????? ?
??? } ?
?
} ?
有了可以容納的地方,那么我們怎么實現呢?系統Launcher是,長按桌面的時候彈出一個Dialog,然后,里面有一項是:Add Widget。我們直接從這個開始,調用系統自帶的Widget選擇程序來選擇。完整代碼如下:
[java] view plaincopy
/**
?* 這里示例如何選擇已安裝的Widget,并且將選擇的Widget添加到指定的位置
?* @author liner
?*
?*/ ?
public class AppWidgetHostDemoActivity extends Activity { ?
??? private static final int APPWIDGET_HOST_ID = 0x200; ?
???? ?
??? private static final int REQUEST_ADD_WIDGET = 1; ?
??? private static final int REQUEST_CREATE_WIDGET = 2; ?
???? ?
??? private AppWidgetHost mWidgetHost; ?
???? ?
??? private AppWidgetManager mWidgetMananger; ?
???? ?
??? private WidgetLayout mLayout; ?
???? ?
??? @Override ?
??? public void onCreate(Bundle savedInstanceState) { ?
??????? super.onCreate(savedInstanceState); ?
???????? ?
??????? mWidgetHost = new AppWidgetHost(getApplicationContext(), APPWIDGET_HOST_ID); ?
??????? mWidgetMananger = AppWidgetManager.getInstance(getApplicationContext()); ?
???????? ?
??????? mLayout = new WidgetLayout(this); ?
??????? mLayout.setOnLongClickListener(new View.OnLongClickListener() { ?
???????????? ?
??????????? @Override ?
??????????? public boolean onLongClick(View v) { ?
???????????????? ?
??????????????? selectWidgets(); ?
???????????????? ?
??????????????? return false; ?
??????????? } ?
??????? }); ?
???????? ?
??????? setContentView(mLayout); ?
???????? ?
??????? //開始監聽Widget的變化 ?
???????? ?
??????? mWidgetHost.startListening(); ?
??? } ?
???? ?
??? public void onActivityResult(int requestCode, int resultCode, Intent data){ ?
??????? if(resultCode == RESULT_OK){ ?
???????????? ?
??????????? switch (requestCode) { ?
??????????? case REQUEST_ADD_WIDGET: ?
??????????????? addWidget(data); ?
??????????????? break; ?
??????????? case REQUEST_CREATE_WIDGET: ?
??????????????? createWidget(data); ?
??????????????? break; ?
??????????? default: ?
??????????????? break; ?
??????????? } ?
???????????? ?
??????? }else if(requestCode == REQUEST_CREATE_WIDGET && resultCode == RESULT_CANCELED && data != null){ ?
??????????? int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); ?
??????????? if(appWidgetId != -1){ ?
??????????????? mWidgetHost.deleteAppWidgetId(appWidgetId); ?
??????????? } ?
??????? } ?
??? }??? ?
?
??? private void createWidget(Intent data) { ?
??????? //獲取選擇的widget的id ?
??????? int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); ?
???????? ?
??????? //獲取所選的Widget的AppWidgetProviderInfo信息 ?
??????? AppWidgetProviderInfo appWidget = mWidgetMananger.getAppWidgetInfo(appWidgetId); ?
???????? ?
??????? //根據AppWidgetProviderInfo信息,創建HostView ?
??????? View hostView = mWidgetHost.createView(this, appWidgetId, appWidget); ?
???????? ?
??????? //將HostView添加到桌面 ?
??????? mLayout.addInScreen(hostView, appWidget.minWidth, appWidget.minHeight); ?
??? } ?
?
??? /**
???? * 添加選擇的widget。需要判斷其是否含有配置,如果有,需要首先進入配置
???? * @param data
???? */ ?
??? private void addWidget(Intent data) { ?
??????? int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); ?
??????? AppWidgetProviderInfo appWidget = mWidgetMananger.getAppWidgetInfo(appWidgetId); ?
???????? ?
??????? Log.d("AppWidget", "configure:"+appWidget.configure); ?
???????? ?
??????? if(appWidget.configure != null){ ?
??????????? //有配置,彈出配置 ?
??????????? Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); ?
??????????? intent.setComponent(appWidget.configure); ?
??????????? intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); ?
???????????? ?
??????????? startActivityForResult(intent, REQUEST_CREATE_WIDGET); ?
???????????? ?
??????? }else{ ?
??????????? //沒有配置,直接添加 ?
??????????? onActivityResult(REQUEST_CREATE_WIDGET, RESULT_OK, data); ?
??????? } ?
???????? ?
???????????????? ?
??? } ?
?
??? /**
???? * 顯示系統中已經存在的Widget信息,供用戶選擇
???? */ ?
??? protected void selectWidgets() { ?
??????? int widgetId = mWidgetHost.allocateAppWidgetId(); ?
???????? ?
??????? Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK); ?
??????? pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); ?
???????? ?
??????? startActivityForResult(pickIntent, REQUEST_ADD_WIDGET); ?
??? } ?
???? ?
???? ?
} ?
效果:
到這里,應該可以理解了,要讓我們的Launcher也可以容納各式各樣的Widget,我們至少需要加上上面類似的代碼。但是,還有什么問題亟待解決呢?
1、CellLayout如何計算當前添加的Widget需要占據的單元格數目
2、CellLayout如何分配當前添加的Widget占據的單元格數目
3、CellLayout中已經沒有足夠的區域來容納這么大小的Widget怎么辦?
有了前面的代碼,下面我們的任務就可以聚集在上面三個問題上了,解決了這三個問題,再加入之前的代碼,就可以讓我們的桌面也如系統桌面一樣,可以容納各式各樣的小部件了。
下一篇就將解決這些問題...
轉載于:https://www.cnblogs.com/tangchenglin/archive/2012/07/30/2615298.html
總結
以上是生活随笔為你收集整理的说说Android桌面(Launcher应用)背后的故事(九)——让我的桌面多姿多彩的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jquery的一个代码
- 下一篇: liferay-ui:search-co