要想做好launcher,對其基本的架構瞭解是必須的,在這篇文章裡,簡單的介紹下launcher中類的構成和架構,首先,這篇文章中很多資源均從互連網中獲得,感謝網友們的無私奉獻,大家都共用,進步才會更快。
第一步,先看兩張從網上找來的launcher的架構圖。
第二張:
通過這兩張圖,簡單的說下:
整個launcher,準確來說應該是homescreen更為合適,是一個包含三個child view的FrameLayout(com.android.launcher.DragLayer)。
第一個child就是案頭com.android.launcher.Workspace。這個案頭又包含5個child。每個child就對應一個桌 面。這就是你在Android上看到的五個案頭。每個案頭上可以放置下列對象:應用捷徑,appwidget和folder。(第一張圖應該是以前版本的,在2.2以前的版本應該是只有三個案頭,在2.3中,有五個案頭)
第二個child是一個SlidingDrawer控制項,這個控制項由兩個子控制群組成。一個是 com.android.launcher.HandleView,就是Android案頭下方的把手,當點擊這個把手時,另一個子控 件,com.android.launcher.AllAppsGridView就會彈出,這個子控制項列出系統中當前安裝的所有類型為 category.launcher的Activity。
第三個child是com.android.launcher.DeleteZone。當使用者在案頭上長按一個widget時,把手位置就會出現一個垃圾桶形狀的控制項,就是這個控制項。(其實在2.2以後的版本中,在左下方和右下方添加了頁面標記,來告訴使用者當前在哪個案頭,其代碼在launcher.xml代碼中,
<ImageView android:id="@+id/previous_screen" android:layout_width="93dip" android:layout_height="@dimen/button_bar_height" android:layout_gravity="bottom|left" android:layout_marginLeft="6dip" android:scaleType="center" android:src="@drawable/home_arrows_left" android:onClick="previousScreen" android:focusable="true" android:clickable="true" /> <ImageView android:id="@+id/next_screen" android:layout_width="93dip" android:layout_height="@dimen/button_bar_height" android:layout_gravity="bottom|right" android:layout_marginRight="6dip" android:scaleType="center" android:src="@drawable/home_arrows_right" android:onClick="nextScreen" android:focusable="true" android:clickable="true" />
下面就是launcher中主要類的簡介:
AddAdapter:添加案頭元素的適配器,維護了live fold , widget , shortcut , wallpaper 4個ListItem , 長按案頭會顯示該列表
AllAppsGridView:Icon列表的的主介面,繼承gridView。
ApplicationInfo:一個可開機應用。
ApplicationsAdapter:gridview的adapter。
BubbleTextView:一個定製了的textview,主要用於顯示應用表徵圖。
DeleteZone:luancher的刪除地區,繼承ImageView。在平時是出於隱藏狀態,在將item長按拖動的時候會顯示出來,如果將item拖動到刪除框位置時會刪除item。 DeleteZone實現了DropTarget和DragListener兩個介面。
DragController:拖動控制介面。為Drag定義的一個介面。包含一個介面,兩個方法和兩個靜態常量。介面為DragListener(包含onDragStart(),onDragEnd()兩個函數),onDragStart()是在剛開始拖動的時候被調用,onDragEnd()是在拖動完成時被調用。在launcher中典型的應用是DeleteZone,在長按拖動item時調用onDragStart()顯示,在拖動結束的時候onDragEnd()隱藏。兩個函數包括startDrag()和setDragItemInfo().startDrag()用於在拖動是傳遞要拖動的item的資訊以及拖動的方式,setDragItemInfo()用於傳遞item的參數資訊(包括位置以及大小)。兩個常量為DRAG_ACTION_MOVE,DRAG_ACTION_COPY來標識拖動的方式,DRAG_ACTION_MOVE為移動,表示在拖動的時候需要刪除原來的item,DRAG_ACTION_COPY為複製型的拖動,表示保留被拖動的item。
DragLayer:整個launcher的父節點,繼承FrameLayout,實現介面DrayController,是內部支援拖拽的viewgroup。DragLayer實際上也是一個抽象的介面,用來處理拖動和對事件進行初步處理然後按情況分發下去,角色是一個controller。它首先用onInterceptTouchEvent(MotionEvent)來攔截所有的touch事件,如果是長按item拖動的話不把事件傳下去,直接交由onTouchEvent()處理,這樣就可以實現item的移動了,如果不是拖動item的話就把事件傳到目標view,交有目標view的事件處理函數做相應處理。如過有要對事件的特殊需求的話可以修改onInterceptTouchEvent(MotionEvent)來實現所需要的功能。
DragSource:拖動源介面,定義了void onDropCompleted(View target, boolean success)。
DropTarget:拖動目標,定義很多拖動過程需要的方法:onDrop,onDragEnter,onDragOver,onDragExit,acceptDrop。
FastBitmapDrawable:工具
Folder:Icons的集合
FolderIcon:出現在workspace的icon 代表了一個folder
FolderInfo: ItemInfo子類
HandleView:launcher抽屜的開關,不過在android2.2已經沒用抽屜了。
InstallShortcutReceiver,UninstallShortcutReceiver:一個broadcastrecier
ItemInfo:代表Launcher中一個Item(例如folder)對item的抽象,所有類型item的父類,item包含的屬性有id(標識item的id),cellX(在橫向位置上的位置,從0開始),cellY(在縱向位置上的位置,從0開始) ,spanX(在橫向位置上所佔的單位格),spanY(在縱向位置上所佔的單位格),screen(在workspace的第幾屏,從0開始),itemType(item的類型,有widget,search,application等),container(item所在的)。
Launcher:整個launcher的程式的入口,代碼量最大的一個檔案。
LauncherApplication:在VM中設定參數
LauncherAppWidgetHost,LauncherAppWidgetHostView,:Widget相關
LauncherModel: MVC中的M,裡面有許多封裝的對資料庫的操作。包含幾個線程,其中最主要的是ApplicationsLoader和DesktopItemsLoader。ApplicationsLoader在載入所有應用程式時使用,DesktopItemsLoader在載入workspace的時候使用。其他的函數就是對資料庫的封裝,比如在刪除,替換,添加程式的時候做更新資料庫和UI的工作。
LauncherProvider:launcher的資料庫,一個contentprovider裡面儲存了案頭的item的資訊。在建立資料庫的時候會loadFavorites(db)方法,loadFavorites()會解析xml目錄下的default_workspace.xml檔案,把其中的內容讀出來寫到資料庫中,這樣就做到了案頭的預製。
LauncherSettings:設定相關的工具,資料庫項的字串定義,另外在這裡定義了container的類型,還有itemType的定義,除此還有一些特殊的widget(如search,clock的定義等)的類型定義。
LiveFolder,LiveFolderAdapter,LiveFolderIcon,LiveFolderInfo: livefolder相關
Search: 搜尋
UserFolder,UserFolderInfo:檔案夾包含applications,shortcuts
Utilities:小工具
WallpaperChooser:選擇wallpaper的activity
Workspace:整個介面layout,幾個視窗就是他下面的子節點。
widget : 代表啟動的widget執行個體,例如搜尋
在案頭中,有一下四種類型的對象:
1. ITEM_SHORTCUT,應用捷徑,對應實現布局檔案R.layout.application
2. ITEM_APPWIDGET,app widget 案頭組件
3. ITEM_LIVE_FOLDER,檔案夾
--UserFolderInfo 對應實現布局檔案R.layout.folder_icon
--LiveFolderInfo 對應實現布局檔案R.layout.live_folder_icon
4. ITEM_WALLPAPER,牆紙。
下面,我們詳細的來說一下launcher裡面的詳細功能:
1.DragLayer--DragLayer繼承FrameLayout,並在此基礎上組合了DragController實現拖放功能,DragLayer主要監聽下面兩個使用者事件onInterceptTouchEvent和onTouchEvent
並交給DragController進行處理,DragController根據是否在拖放中等資訊控制控制項拖放過程處理。DragLayer 是Launcher這個activity的頂層view,其實在Launcher2這個應用只有Laucher.java這麼一個activity。
2.DeleteZone--
<com.android.launcher2.DeleteZone android:id="@+id/delete_zone" android:layout_width="@dimen/delete_zone_size" android:layout_height="@dimen/delete_zone_size" android:paddingTop="@dimen/delete_zone_padding" android:layout_gravity="bottom|center_horizontal" android:scaleType="center" android:src="@drawable/delete_zone_selector" android:visibility="invisible" launcher:direction="horizontal" />
在launcher.xml中,可以發現,DeleteZone預設是不顯示的android:visibility="invisible",但是我們每次開始拖放表徵圖的時候DeleteZone就顯示了,這個功能是如何?的呢?在代碼中可以發現DeleteZone實現了DragController.DragListener介面,
public class DeleteZone extends ImageView implements DropTarget, DragController.DragListener
DragListener提供兩個介面方法,
onDragStart:隱藏把手,顯示DeleteZone
onDragEnd:顯示把手,隱藏DeleteZone
在DeleteZone中,看一下代碼:
public void onDragStart(DragSource source, Object info, int dragAction) { final ItemInfo item = (ItemInfo) info; if (item != null) { mTrashMode = true; createAnimations(); final int[] location = mLocation; getLocationOnScreen(location); mRegion.set(location[0], location[1], location[0] + mRight - mLeft, location[1] + mBottom - mTop); mDragController.setDeleteRegion(mRegion); mTransition.resetTransition(); startAnimation(mInAnimation); mHandle.startAnimation(mHandleOutAnimation); setVisibility(VISIBLE); } } public void onDragEnd() { if (mTrashMode) { mTrashMode = false; mDragController.setDeleteRegion(null); startAnimation(mOutAnimation); mHandle.startAnimation(mHandleInAnimation); setVisibility(GONE); } }
分別在開始DragController開始拖放和結束拖放的時候被調用.
另外DeleteZone實現了DropTarget介面的onDrop方法
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { final ItemInfo item = (ItemInfo) dragInfo; if (item.container == -1) return; if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { if (item instanceof LauncherAppWidgetInfo) { mLauncher.removeAppWidget((LauncherAppWidgetInfo) item); } } else { if (source instanceof UserFolder) { final UserFolder userFolder = (UserFolder) source; final UserFolderInfo userFolderInfo = (UserFolderInfo) userFolder.getInfo(); // Item must be a ShortcutInfo otherwise it couldn't have been in the folder // in the first place. userFolderInfo.remove((ShortcutInfo)item); } } if (item instanceof UserFolderInfo) { final UserFolderInfo userFolderInfo = (UserFolderInfo)item; LauncherModel.deleteUserFolderContentsFromDatabase(mLauncher, userFolderInfo); mLauncher.removeFolder(userFolderInfo); } else if (item instanceof LauncherAppWidgetInfo) { final LauncherAppWidgetInfo launcherAppWidgetInfo = (LauncherAppWidgetInfo) item; final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost(); if (appWidgetHost != null) { final int appWidgetId = launcherAppWidgetInfo.appWidgetId; // Deleting an app widget ID is a void call but writes to disk before returning // to the caller... new Thread("deleteAppWidgetId") { public void run() { appWidgetHost.deleteAppWidgetId(launcherAppWidgetInfo.appWidgetId); } }.start(); } } LauncherModel.deleteItemFromDatabase(mLauncher, item); }
當把表徵圖拖放到DeleteZone,就會調用DeleteZone,實現的onDrop方法對應用表徵圖進行刪除處理。
3.螢幕左右移動按鈕,就是使用的ImageView,
<ImageView android:id="@+id/previous_screen" android:layout_width="93dip" android:layout_height="@dimen/button_bar_height" android:layout_gravity="bottom|left" android:layout_marginLeft="6dip" android:scaleType="center" android:src="@drawable/home_arrows_left" android:onClick="previousScreen" android:focusable="true" android:clickable="true" /> <ImageView android:id="@+id/next_screen" android:layout_width="93dip" android:layout_height="@dimen/button_bar_height" android:layout_gravity="bottom|right" android:layout_marginRight="6dip" android:scaleType="center" android:src="@drawable/home_arrows_right" android:onClick="nextScreen" android:focusable="true" android:clickable="true" />
注意三點,
--1.案頭左右移動時Drawable的變換,變換表徵圖列表可查看home_arrows_right.xml
,ImageView通過把drawable傳遞給worksapce,當案頭切換時通過調用Drawable.setLevel()方法實現不同表徵圖顯示。
--2.當點擊實現左右案頭切換,查看上面的布局檔案中android:onClick="previousScreen",該屬性定義了一個 onClick事件響應函數,在Launcher.java中的788行。
@SuppressWarnings({"UnusedDeclaration"}) publicvoid previousScreen(View v) { if(!isAllAppsVisible()) { mWorkspace.scrollLeft(); } }
--3.在(Launcher.setupViews)中添加了長按事件OnLongClickListener有當長按會執行launcher.onlongclick方法,方法執行顯示5個案頭的預覽微縮圖顯示。
4.RelativeLayout--android:id="@+id/all_apps_button_cluster",如前面右邊灰色豎狀條,它是一個相對布局對象,上面承載了三個view
中間是一個HandleView,是一個進入allappview的按鈕,HandleView的左面是撥號,右面是瀏覽器兩個ImageView。
--HandleView
--1.點擊事件 傳遞給Launcher.onClick進行處理 顯示應用菜單view
--2.長按事件 傳遞給Launcher.onLongClick進行處理,方法執行顯示5個案頭的預覽微縮圖顯示
--撥號或者瀏覽器
--onClick響應:android:onClick="launchHotSeat"
5.Workspace--使用者案頭包括5個workspace_screen,launcher:defaultScreen="2"在前面已經說過,表示預設案頭是第三個。
workspace繼承了viewgroup,5個workspace_screen作為它的child,值得注意它只接收CellLayout類型的child,workspace重寫了addview函數,添加了非CellLayout的child將拋異常
--Workspace長按事件由launcher.onLongClick來監聽
--Workspace實現了DropTarget, DragSource兩個介面,意味著Workspace既是拖放源,又是拖放目的地
--Workspace實現DragScroller介面,DragScroller介面提供兩個方法
void scrollLeft()和void scrollRight()在拖放過程被DragController調用實現案頭的左右滾動。
--CellLayout Workspace下的一個案頭布局,CellLayout也是ViewGroup的子類,
Workspace下有5個CellLayout順序排列,Workspace下布局檔案:android:scrollbars="horizontal"決定了5個CellLayout排列是橫向還是縱向的
<com.android.launcher2.CellLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" android:layout_width="match_parent" android:layout_height="match_parent" android:hapticFeedbackEnabled="false" launcher:cellHeight="@dimen/workspace_cell_height"//每個cell的高度 launcher:longAxisStartPadding="8dip"//cell距離父view CellLayout左邊距 launcher:longAxisEndPadding="78dip"//cell距離父view CellLayout右邊距 launcher:shortAxisStartPadding="0dip"//cell距離父view CellLayout上邊距 launcher:shortAxisEndPadding="0dip"//cell距離父view CellLayout下邊距 launcher:shortAxisCells="4"//CellLayout cells行數 launcher:longAxisCells="4"//CellLayout cells列數 />
當縱向的控制項不夠cells排列時,cell將產生重疊,橫向不產生重疊,橫向每個cell間隔至少為0
--CellLayout覆蓋重新實現了onMeasure方法,和onlayout方法,它限定了child view 使用的布局參數類型為CellLayout.LayoutParams因此企圖通過修改
workspace_screen.xml來改變它的案頭布局是不會得以成功的,你必須修改CellLayout類
--CellLayout.LayoutParams說明,CellLayout.LayoutParams下有幾個成員需要說明一下
--cellX:該child view佔用的第幾列的cell(若橫向佔用多個cell,表示最左邊的cellx)
--cellY: 該child view佔用的第幾行的cell(若縱向佔用多個cell,表示最上邊的celly)
--cellHSpan:橫向跨越的列數
--cellVSpan: 縱向跨越行數
--isDragging:該child是否正在被拖動
--regenerateId:是否重建view id
最後以網上淘來的launcher類的關係來結束這篇文章: