天天看點

android launcher

去年做了launcher相關的工作,看了很長時間。很多人都在修改launcher,但還沒有詳細的文檔,把自己積累的東西分享出來,大家一起積累。這份源碼是基于2.1的launcher2,以後版本雖有變化,但大概的原理一直還是保留了。

一、主要檔案和類

1.Launcher.java:launcher中主要的activity。

2.DragLayer.java:launcher layout的rootview。DragLayer實際上也是一個抽象的界面,用來處理拖動和對事件進行初步處理然後按情況分發下去,角色是一個controller。它首先用onInterceptTouchEvent(MotionEvent)來攔截所有的touch事件,如果是長按item拖動的話不把事件傳下去,直接交由onTouchEvent()處理,這樣就可以實作item的移動了,如果不是拖動item的話就把事件傳到目标view,交有目标view的事件處理函數做相應處理。如過有要對事件的特殊需求的話可以修改onInterceptTouchEvent(MotionEvent)來實作所需要的功能。

3. DragController.java:為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。

4.LauncherModel.java:輔助的檔案。裡面有許多封裝的對資料庫的操作。包含幾個線程,其中最主要的是ApplicationsLoader和DesktopItemsLoader。ApplicationsLoader在加載所有應用程式時使用,DesktopItemsLoader在加載workspace的時候使用。其他的函數就是對資料庫的封裝,比如在删除,替換,添加程式的時候做更新資料庫和UI的工作。

5.Workspace.java:抽象的桌面。由N個celllaout組成,從cellLayout更高一級的層面上對事件的處理。

6.LauncherProvider.java:launcher的資料庫,裡面存儲了桌面的item的資訊。在建立資料庫的時候會loadFavorites(db)方法,loadFavorites()會解析xml目錄下的default_workspace.xml檔案,把其中的内容讀出來寫到資料庫中,這樣就做到了桌面的預制。

7.CellLayout.java:組成workspace的view,繼承自viewgroup,既是一個dragSource,又是一個dropTarget,可以将它裡面的item拖出去,也可以容納拖動過來的item。在workspace_screen裡面定了一些它的view參數。

8.ItemInfo.java:對item的抽象,所有類型item的父類,item包含的屬性有id(辨別item的id),cellX(在橫向位置上的位置,從0開始),cellY(在縱向位置上的位置,從0開始) ,spanX(在橫向位置上所占的機關格),spanY(在縱向位置上所占的機關格),screen(在workspace的第幾屏,從0開始),itemType(item的類型,有widget,search,application等),container(item所在的)。

9.UserFolder.java: 使用者建立的檔案夾。可以将item拖進檔案夾,單擊時打開檔案夾,長按檔案夾上面标題處可以重命名檔案夾。

10.LiveFolder.java:系統自帶的檔案夾。從系統中建立出的如聯系人的檔案夾等。

11.DeleteZone:删除框。在平時是出于隐藏狀态,在将item長按拖動的時候會顯示出來,如果将item拖動到删除框位置時會删除item。DeleteZone實作了DropTarget和DragListener兩個接口。

12.LauncherSettings.java:字元串的定義。資料庫項的字元串定義,另外在這裡定義了container的類型,還有itemType的定義,除此還有一些特殊的widget(如search,clock的定義等)的類型定義。

二、主要子產品

1.界面模型:

Launcher的界面的rootview是DragLayer,它是一個FrameLayout,在它上面workspace(應該說是celllayout)占了絕大部分的空間,celllayout的參數檔案是workspace_screen.xml。workspace既是一個DropTarget又是一個DragSource,可以從AllAppGridView中拖出應用程式放在它上面,也可以把它裡面的item拖走删除或者拖到bottomabr裡面去。

2.Drop& Drag模型:

         DragSource:可以拖動的對象來源的容器,在launcher中主要有AllAppGridView,workspace等。

                   void onDropCompleted(View target, boolean success,int x,int y);

DropTarget:可以放置被拖動的對象的容器。在launcher中有folder,workspace,bottombar等,一個View既可以是Dragsource也可以是DropTarget。主要包含以下幾個接口:

boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);

acceptDrop函數用來判斷dropTarget是否可以接受item放置在自己裡面。

   void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);

          onDragEnter是item被拖動進入到一個dropTarget的時候的回調。

   void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);

          onDragOver是item在上一次位置和這一次位置所處的dropTarget相同的時候的回調。

   void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);

          onDragExit是item被拖出dropTarget時的回調。

boolean onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);

     onDrop是item被放置到dropTarget時的回調。 

函數的調用模式為:

DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);

if (dropTarget != null) {

              /**

               * 當這一次的 target 跟上一次相同時,根據坐标來移動item

               */

                if (mLastDropTarget == dropTarget) {

                    dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1],

                        (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);

                } else {

                  /**

                 * 當上一次的位置跟這一次不同而且上一次的位置不為空,說明item移           *動出了,将上次的 View根據上次的坐标重新排列,并根據目前坐标重排*目前的*/

                    if (mLastDropTarget != null) {

                        mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],

                                (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);

                    }

                    dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1],

                }

            } else {//如果這一次為 null ,上一次不為 null ,那麼把上一次坐标位置的 cell 去掉

                if (mLastDropTarget != null) {

                    mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],

                            (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);

            }

            //記錄上次的droptarget

            mLastDropTarget = dropTarget;

3.Touch event總結:

                   由于launcher的事件比較多比較複雜,是以在事件處理的時候一般采用rootview先用onInterceptTouchEvent(MotionEvent)攔截所有的touch事件,經過判斷後分發給childview。

判斷的規則如下:

        a.down事件首先會傳遞到onInterceptTouchEvent()方法

       b.如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後return false,那麼後續的move, up等事件将繼續會先傳遞給該ViewGroup,之後才和down事件一樣傳遞給最終的目标view的onTouchEvent()處理。

       c.如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後return true,那麼後續的move, up等事件将不再傳遞給onInterceptTouchEvent(),而是和down事件一樣傳遞給該ViewGroup的onTouchEvent()處理,注意,目标view将接收不到任何事件。

       d.如果最終需要處理事件的view的onTouchEvent()傳回了false,那麼該事件将被傳遞至其上一層次的view的onTouchEvent()處理。

       e.如果最終需要處理事件的view 的onTouchEvent()傳回了true,那麼後續事件将可以繼續傳遞給該view的onTouchEvent()處理。

三、幾種問題的解決方式

1.将所有的應用都排列在桌面上

         将所有的應用都排列在桌面是通過首先建立一個三維的boolean型全局數組來記錄item的排列情況,第一維是屏數,第二維是縱向上的排列情況,第三維是橫向的排列情況,如果那個位置被item所占用就标記為1,否則标記為0.在啟動時把全局數組初始化為0,然後在添加的時候把相應的位置置1.凡是涉及到workspace上item的變化,比如移動、添加、删除操作時都需要維護數組,保持數組的正确性,因為在安裝新程式時依據數組的狀态去判斷把item加到什麼位置。

2.動态增加螢幕

動态增加螢幕是通過worksapce .addchild(view)的方式實作。基本思路是:首先預先規定所允許的最大的螢幕數,然後在需要增加螢幕而且目前螢幕數沒有超過最大螢幕數的時候通過(CellLayout)mInflater.inflate(R.layout.workspace_screen,null)建立一個celllayout執行個體出來,然後通過addchild把它加入進去。在螢幕上的item被删除時通過從最後一屏起判斷螢幕上是否有item,如果有的話保留,沒有的話則删除最後一屏,以此類推。

3.預制桌面

         a.添加普通的應用程式快捷方式:

         在../res/xml下的default_workspace.xml檔案中加入預設要放置的普通的應用程式。加入的格式為:

<favorite

launcher:packageName="... "    //應用的packageName  

launcher:className="... "      //應用啟動時的第一個activity 

launcher:screen="..."         //放置在第幾屏(放在workspace的時候需要,從0開始,0為第一屏,1為第二屏,以此類推...)

launcher:x="..."               //放置x方向的位置(在列中的位置)

launcher:y="..." />           //放置y方向的位置(在行中的位置)

packageName和className可以通過點選程式,然後在列印出的log中找到comp={...},例如如下資訊:

comp={com.estrongs.android.taskmanager/com.estrongs.android.taskmanager.TaskManager}。其中com.estrongs.android.taskmanager為packageName, com.estrongs.android.taskmanager.TaskManager為className。

workspace的布局如下:

(0,0)

(1,0)

(2,0)

(3,0)

(4,0)

(0,1)

(1,1)

(2,1)

(3,1)

(4,1)

(0,2)

(1,2)

(2,2)

(3,2)

(4,2)

         b.添加widget:

         在../package/apps/VLauncher/res/xml下的default_workspace.xml檔案中加入預設要放置的普通的應用程式。加入的格式為:

<widget

launcher:packageName="..."       //widget的packageName

launcher:className=" ..."       //實作 widget的 receiver 類的名稱.

    launcher:container="..."        //放置的位置(隻能為desktop)

        launcher:screen="..."        //放置在第幾屏上

        launcher:x="..."              //放置的x位置

        launcher:y="..."              //放置的y位置

        launcher:spanx="..."         //在x方向上所占格數

        launcher:spany="..."/>       //在y方向上所占格數

例如,要在第3屏的第一行第二列放置開始放置一個x方向上占兩個機關格,y方向上占兩個機關格的時鐘,可以加入以下代碼:

<appwidget

launcher:packageName="com.android.alarmclock"      launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"

        launcher:container="desktop"

        launcher:screen="2"

        launcher:x="1"

        launcher:y="0"

        launcher:spanx="2"

        launcher:spany="2"/> 

4.改變主界面的排列方式

         要修改桌面的排列方式,如下,先根據橫豎屏設定修改workspace_screen.xml裡shortAxisCells和longAxisCells的參數,然後在Launcher.java中修改NUMBER_CELLS_X和NUMBER_CELLS_Y的值,在2.3版本中剛開始往資料庫中添加item的時候會去判斷,如果不修改NUMBER_CELLS_X和NUMBER_CELLS_Y的話會導緻一部分的item顯示不出來,導緻預制apk的失敗。

5.增加worksapce上的屏數

         要增加屏數,首先在根據橫豎屏在launcher.xml中的<com.android.launcher.Workspace 中删除或增加 <include android:id="@+id/cellN" layout="@layout/workspace_screen" />,然後在Launcher.java中修改SCREEN_COUNT的值即可。

四、xml檔案

       1.workspace_screen.xml

              launcher:cellWidth="95dip"

           cell(即item)的寬

        launcher:cellHeight="93dip"

        launcher:longAxisStartPadding="25dip"

較長(螢幕的寬和高中較大的那一方向,根據橫豎屏方向有所不同)方向上距離起點的像素數

        launcher:longAxisEndPadding="55dip"

較長(螢幕的寬和高中較大的那一方向,根據橫豎屏方向有所不同)方向上距離終點的像素數

        launcher:shortAxisStartPadding="20dip"

較短(螢幕的寬和高中較大的那一方向,根據橫豎屏方向有所不同)方向上距離起點的像素數

        launcher:shortAxisEndPadding="120dip"

        launcher:shortAxisCells="3"

較短的方向上可以容納的cell的數量

        launcher:longAxisCells="5"

較長的方向上可以容納的cell的數量

shortAxisCells和longAxisCells決定一個workspace(即CellLayout)上可以容納的item的個數為shortAxisCells*longAxisCells.

2. application_boxed.xml

       所有應用程式和系統檔案夾中item的定義。

3.application.xml

       Workspace的item的layout定義。