android Launcher那點事兒(二)

應朋友要求,把Launcher應用再詳細解說一下。

首先,我們需要去LauncherApplication裡面看一下,因為這裡沒有兩個成員變量對我們這一講非常重要,它們就是

    public LauncherModel mModel;
    public IconCache mIconCache;

在LauncherApplication的onCreate()創建

        mIconCache = new IconCache(this);
        mModel = new LauncherModel(this, mIconCache);

IconCache很明顯是應用Icon緩存,這個一會我們會講到,LauncherModel是BroadcastReceiver,用來接受應用添加、刪除、變動等等的廣播,來做響應的操作。我們去LauncherModel的構造函數中看看

    LauncherModel(LauncherApplication app, IconCache iconCache) {
        mAppsCanBeOnExternalStorage = !Environment.isExternalStorageEmulated();
        mApp = app;
        mAllAppsList = new AllAppsList(iconCache);
        mIconCache = iconCache;

        mDefaultIcon = Utilities.createIconBitmap(
                mIconCache.getFullResDefaultActivityIcon(), app);

        final Resources res = app.getResources();
        mAllAppsLoadDelay = res.getInteger(R.integer.config_allAppsBatchLoadDelay);
        mBatchSize = res.getInteger(R.integer.config_allAppsBatchSize);
        Configuration config = res.getConfiguration();
        mPreviousConfigMcc = config.mcc;
    }

這裡的mAllAppsList也就是AllAppsList,是用來存儲我們應用信息的,十分重要,我們Launcher的一半操作是圍繞這它進行的,在AllAppsList類中的重要變量

    /** The list off all apps. */
    public ArrayList data =
            new ArrayList(DEFAULT_APPLICATIONS_NUMBER);
    /** The list of apps that have been added since the last notify() call. */
    public ArrayList added =
            new ArrayList(DEFAULT_APPLICATIONS_NUMBER);
    /** The list of apps that have been removed since the last notify() call. */
    public ArrayList removed = new ArrayList();
    /** The list of apps that have been modified since the last notify() call. */
    public ArrayList modified = new ArrayList();

    private IconCache mIconCache;

從名字就可以知道這些是用來存儲什麼的,這裡不再一一解釋,我們看看ApplicationInfo裡面都存儲瞭應用的什麼信息

    /**
     * The application name.
     */
    CharSequence title;

    /**
     * The intent used to start the application.
     */
    Intent intent;

    /**
     * A bitmap version of the application icon.
     */
    Bitmap iconBitmap;

    /**
     * The time at which the app was first installed.
     */
    long firstInstallTime;

    ComponentName componentName;

名字title、啟動的Intent、iconBitmap、第一次安裝時間firstInstallTime、ComponentName等等,具體這些幹什麼的,不用我一一解釋瞭吧。

好瞭,LauncherApplication我們就看到這裡,然後我們進入Launcher中,Launcher是一個Activity,所以我們就先看它的onCreate函數,我們隻截取我們關心的代碼

        LauncherApplication app = ((LauncherApplication)getApplication());
        mModel = app.setLauncher(this);

        mIconCache = app.getIconCache();
        mDragController = new DragController(this);

這裡獲取我們剛才創建的LauncherApplication,然後獲取到剛才創建的LauncherModel並把我們的Launcher傳進去,接著獲取到我們剛才創建的IconCache,最後就是創建DragController,這是我們拖動圖標時用到的,後面會講解到。由於Widget的管理和app差不多,這裡就不再講解隻講app。接著

        if (!mRestoring) {
            mModel.startLoader(this, true);
        }

去加載我們的應用

    public void startLoader(Context context, boolean isLaunching) {
        synchronized (mLock) {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "startLoader isLaunching=" + isLaunching);
            }

            // Don't bother to start the thread if we know it's not going to do anything
            if (mCallbacks != null && mCallbacks.get() != null) {
                // If there is already one running, tell it to stop.
                // also, don't downgrade isLaunching if we're already running
                isLaunching = isLaunching || stopLoaderLocked();
                mLoaderTask = new LoaderTask(context, isLaunching);
                sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                sWorker.post(mLoaderTask);
            }
        }
    }

啟動瞭一個線程LoaderTask來加載我們的應用,我們直接去LoaderTask的run()方法中查看

            keep_running: {
                // Elevate priority when Home launches for the first time to avoid
                // starving at boot time. Staring at a blank home is not cool.
                synchronized (mLock) {
                    if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
                            (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
                    android.os.Process.setThreadPriority(mIsLaunching
                            ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
                }
                if (loadWorkspaceFirst) {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                    loadAndBindWorkspace();
                } else {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps");
                    loadAndBindAllApps();
                }

                if (mStopped) {
                    break keep_running;
                }

                // Whew! Hard work done.  Slow us down, and wait until the UI thread has
                // settled down.
                synchronized (mLock) {
                    if (mIsLaunching) {
                        if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
                        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    }
                }
                waitForIdle();

                // second step
                if (loadWorkspaceFirst) {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                    loadAndBindAllApps();
                } else {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace");
                    loadAndBindWorkspace();
                }

                // Restore the default thread priority after we are done loading items
                synchronized (mLock) {
                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                }
            }

這裡就決定瞭是先加載Apps還是先加載Workspace,這隻我們隻講解Apps,也就是loadAndBindAllApps()

        private void loadAndBindAllApps() {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
            }
            if (!mAllAppsLoaded) {
            	loadAllAppsFromPersistence();
                loadAllAppsByBatch();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mAllAppsLoaded = true;
                }
            } else {
                onlyBindAllApps();
            }
        }

loadAllAppsFromPersistence()是從數據庫加載應用,由於代碼不同,可能有的沒有這一過程,loadAllAppsByBatch()解析應用來加載應用,onlyBindAllApps()跳過瞭更新應用列表的過程,是loadAllAppsByBatch()的部分代碼,不再講解,這裡我們隻講解loadAllAppsByBatch()

        private void loadAllAppsByBatch() {
            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

            // Don't use these two variables in any of the callback runnables.
            // Otherwise we hold a reference to them.
            final Callbacks oldCallbacks = mCallbacks.get();
            if (oldCallbacks == null) {
                // This launcher has exited and nobody bothered to tell us.  Just bail.
                Log.w(TAG, "LoaderTask running with no launcher (loadAllAppsByBatch)");
                return;
            }

            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

            final PackageManager packageManager = mContext.getPackageManager();
            List apps = null;

            int N = Integer.MAX_VALUE;

            int startIndex;
            int i=0;
            int batchSize = -1;
            while (i < N && !mStopped) {
                if (i == 0) {
                    mAllAppsList.clear();
                    final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    apps = packageManager.queryIntentActivities(mainIntent, 0);
//                    for (ResolveInfo resolveInfo : apps) {
//                    	ActivityInfo activityInfo = resolveInfo.activityInfo;
//                    	if(activityInfo == null){
//                        	Log.v("Launcher", "loadAllAppsByBatch resolvePackageName:" + resolveInfo.resolvePackageName);
//                    	}else{
//                        	Log.v("Launcher", "loadAllAppsByBatch activityInfo:" + activityInfo.loadLabel(packageManager).toString());
//                    	}
//            		}
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "queryIntentActivities took "
                                + (SystemClock.uptimeMillis()-qiaTime) + "ms");
                    }
                    if (apps == null) {
                        return;
                    }
                    N = apps.size();
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "queryIntentActivities got " + N + " apps");
                    }
                    if (N == 0) {
                        // There are no apps?!?
                        return;
                    }
                    if (mBatchSize == 0) {
                        batchSize = N;
                    } else {
                        batchSize = mBatchSize;
                    }

                    final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    Collections.sort(apps,
                            new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "sort took "
                                + (SystemClock.uptimeMillis()-sortTime) + "ms");
                    }
                }

                final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

                startIndex = i;
                for (int j=0; i<N && j<batchSize; j++) {
                    // This builds the icon bitmaps.
                    mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
                            mIconCache, mLabelCache));
                    i++;
                }

                final boolean first = i  0 && i  0 ? " (including delay)" : ""));
            }
        }

首先獲取Action為Intent.ACTION_MAIN,Category為Intent.CATEGORY_LAUNCHER的所有Apps

            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

            final PackageManager packageManager = mContext.getPackageManager();
            List apps = null;

                    mAllAppsList.clear();
                    final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    apps = packageManager.queryIntentActivities(mainIntent, 0);

並且把我們mAllAppsList清空,這個mAllAppsList我們前面說過,這裡不再贅述,然後

                for (int j=0; i<N && j<batchSize; j++) {
                    // This builds the icon bitmaps.
                    mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
                            mIconCache, mLabelCache));
                    i++;
                }

去加載我們的應用,ApplicationInfo之前我們也說過,但是前面隻看瞭它存儲瞭我們應用的什麼信息,這裡我們去看看它的構造函數

    public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
            HashMap labelCache) {
        final String packageName = info.activityInfo.applicationInfo.packageName;

        this.componentName = new ComponentName(packageName, info.activityInfo.name);
        this.container = ItemInfo.NO_ID;
        this.setActivity(componentName,
                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

        try {
            int appFlags = pm.getApplicationInfo(packageName, 0).flags;
            if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
                flags |= DOWNLOADED_FLAG;

                if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
                    flags |= UPDATED_SYSTEM_APP_FLAG;
                }
            }
            firstInstallTime = pm.getPackageInfo(packageName, 0).firstInstallTime;
        } catch (NameNotFoundException e) {
            Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);
        }

        iconCache.getTitleAndIcon(this, info, labelCache);
    }

首先,給ApplicationInfo中的componentName和container變量賦值,然後

        this.setActivity(componentName,
                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

也就是

    final void setActivity(ComponentName className, int launchFlags) {
        intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        intent.setComponent(className);
        intent.setFlags(launchFlags);
        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
    }

給ApplicationInfo的intent賦值,這裡我們可以看出通過intent就可以啟動我們相對應的應用瞭。接著就是給ApplicationInfo的flags和firstInstallTime賦值,這些都不再詳細解說,我們詳細看看iconCache.getTitleAndIcon(this, info, labelCache)也就是

    public void getTitleAndIcon(ApplicationInfo application, ResolveInfo info,
            HashMap labelCache) {
        synchronized (mCache) {
            CacheEntry entry = cacheLocked(application.componentName, info, labelCache);

            application.title = entry.title;
            application.iconBitmap = createBitmap(application.componentName, entry.icon,
                    application);
        }
    }

CacheEntry也就是

    private static class CacheEntry {
        public Bitmap icon;
        public String title;
    }

隻保存瞭應用的圖標和名字。cacheLocked(application.componentName, info, labelCache)也就是

    private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
            HashMap labelCache) {
        CacheEntry entry = mCache.get(componentName);
        if (entry == null) {
            entry = new CacheEntry();

            mCache.put(componentName, entry);

            ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
            if (labelCache != null && labelCache.containsKey(key)) {
                entry.title = labelCache.get(key).toString();
            } else {
                entry.title = info.loadLabel(mPackageManager).toString();
                if (labelCache != null) {
                    labelCache.put(key, entry.title);
                }
            }
            if (entry.title == null) {
                entry.title = info.activityInfo.name;
            }

            entry.icon = Utilities.createIconBitmap(
                    getFullResIcon(info), mContext);
        }
        return entry;
    }

首先從我們的緩存中獲取,如果有就直接返回,如果沒有就去獲取。獲取title也一樣,先從緩存中獲取,如果有就使用,如果沒有就從應用的信息中獲取,這裡我們可以更改應用在Launcher中顯示的名字,這些都容易理解,不做過多解釋。接下來就是Icon的獲取

            entry.icon = Utilities.createIconBitmap(
                    getFullResIcon(info), mContext);

我們看看getFullResIcon也就是

    public Drawable getFullResIcon(ResolveInfo info) {
        Resources resources;
        try {
            resources = mPackageManager.getResourcesForApplication(
                    info.activityInfo.applicationInfo);
        } catch (PackageManager.NameNotFoundException e) {
            resources = null;
        }
        if (resources != null) {
            int iconId = info.activityInfo.getIconResource();
            if (iconId != 0) {
                return getFullResIcon(resources, iconId);
            }
        }
        return getFullResDefaultActivityIcon();
    }

首先獲取到我們加載應用的資源信息

            resources = mPackageManager.getResourcesForApplication(
                    info.activityInfo.applicationInfo);

如果獲取資源不成功就返回getFullResDefaultActivityIcon(),如果獲取資源成功

        if (resources != null) {
            int iconId = info.activityInfo.getIconResource();
            if (iconId != 0) {
                return getFullResIcon(resources, iconId);
            }
        }

就得到相應應用圖標的ID,然後返回getFullResIcon也就是

    public Drawable getFullResIcon(Resources resources, int iconId) {
        Drawable d;
        try {
            d = resources.getDrawableForDensity(iconId, mIconDpi);
        } catch (Resources.NotFoundException e) {
            d = null;
        }

        return (d != null) ? d : getFullResDefaultActivityIcon();
    }

通過圖標的ID獲取到圖片Drawable返回,如果獲取圖片不成功同樣返回getFullResDefaultActivityIcon(),也就是

    public Drawable getFullResDefaultActivityIcon() {
        return getFullResIcon(Resources.getSystem(),
                com.android.internal.R.mipmap.sym_def_app_icon);
    }

這個資源是我們framework下面的一張圖片,也就是我們經常見的那個android小人人,可能不同代碼這個圖片有改動。如果想要把Launcher的圖標改成我們想要的就可在這部分動手腳瞭,例如我們獲取到一個我們準備好的圖片,然後返回就可以瞭,這些不再多講。現在回到cacheLocked中,還看

            entry.icon = Utilities.createIconBitmap(
                    getFullResIcon(info), mContext);

這句話,剛才我們分析瞭getFullResIcon(info)返回一個Drawable,現在我們看看Utilities.createIconBitmap,Utilities.createIconBitmap是一個重構函數,一個傳進去Bitmap,一個傳進去Drawable,我們看傳進去Drawable的函數

    static Bitmap createIconBitmap(Drawable icon, Context context) {
        synchronized (sCanvas) { // we share the statics :-(
            if (sIconWidth == -1) {
                initStatics(context);
            }

            int width = sIconWidth;
            int height = sIconHeight;

            if (icon instanceof PaintDrawable) {
                PaintDrawable painter = (PaintDrawable) icon;
                painter.setIntrinsicWidth(width);
                painter.setIntrinsicHeight(height);
            } else if (icon instanceof BitmapDrawable) {
                // Ensure the bitmap has a density.
                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
                Bitmap bitmap = bitmapDrawable.getBitmap();
                if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
                }
            }
            int sourceWidth = icon.getIntrinsicWidth();
            int sourceHeight = icon.getIntrinsicHeight();

            if (sourceWidth > 0 && sourceHeight > 0) {
                // There are intrinsic sizes.
                if (width < sourceWidth || height  sourceHeight) {
                        height = (int) (width / ratio);
                    } else if (sourceHeight > sourceWidth) {
                        width = (int) (height * ratio);
                    }
                } else if (sourceWidth < width && sourceHeight  use default size
            int textureWidth = sIconTextureWidth;
            int textureHeight = sIconTextureHeight;

            final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
                    Bitmap.Config.ARGB_8888);
            final Canvas canvas = sCanvas;
            canvas.setBitmap(bitmap);

            final int left = (textureWidth-width) / 2;
            final int top = (textureHeight-height) / 2;

            if (false) {
                // draw a big box for the icon for debugging
                canvas.drawColor(sColors[sColorIndex]);
                if (++sColorIndex >= sColors.length) sColorIndex = 0;
                Paint debugPaint = new Paint();
                debugPaint.setColor(0xffcccc00);
                canvas.drawRect(left, top, left+width, top+height, debugPaint);
            }

            sOldBounds.set(icon.getBounds());
            icon.setBounds(left, top, left+width, top+height);
            icon.draw(canvas);
            icon.setBounds(sOldBounds);
            canvas.setBitmap(null);

            return bitmap;
        }
    }

很明顯在這裡對圖片大小等做瞭修整吧,如果要控制Launcher圖標顯示就在這裡做手腳吧,如可以添加一個背景圖片什麼的,或顯示大小什麼的,不再多說。然後我們回到getTitleAndIcon,接著看

            application.title = entry.title;
            application.iconBitmap = createBitmap(application.componentName, entry.icon,
                    application);

就是給我們的ApplicationInfo中的title和iconBitmap賦值瞭。這裡有疑問瞭?我們的圖標圖片不是已經做好瞭,這裡怎麼又createBitmap呢?我們去看看

    public Bitmap createBitmap(ComponentName componentName, Bitmap bitmap,
            ApplicationInfo application) {
        if (!componentName.getPackageName().equals("com.android.mms")) {
            return bitmap;
        }
        //return the Bitmap with unRead Tip
        return MessageManager.getInstance(mContext).createMmsBitmap(bitmap, application);
    }

這下我們恍然大悟瞭吧,這是在做什麼呢?是在把我們未讀短信的個數顯示在圖標上,明白瞭吧。如果我們要把未接電話的個數顯示在圖片上就可以在這裡動手腳瞭,至於怎麼獲取到未讀短信,未接電話個數的,怎麼把數字做到圖片上的,這些都是學習android的必備知識,不在詳細解說。不懂的可以順這代碼看看就知道瞭。到此我們一個應用的ApplicationInfo制作完成瞭。然後就是循環的問題瞭,我們回到LauncherModel中loadAllAppsByBatch繼續看

                for (int j=0; i<N && j<batchSize; j++) {
                    // This builds the icon bitmaps.
                    mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
                            mIconCache, mLabelCache));
                    i++;
                }

剛才我們用瞭大量的篇幅講解瞭制作一個ApplicationInfo的過程,希望大傢都能明白。接著我們看看mAllAppsList.add也就是

    public void add(ApplicationInfo info) {
        if (findActivity(data, info.componentName)) {
            return;
        }
        data.add(info);
        added.add(info);
    }

把我們制作好的ApplicationInfo給瞭AllAppsList的data和added這兩個我們前面說過,這裡不再多說。繼續看LauncherModel中loadAllAppsByBatch後面的內容

                final boolean first = i <= batchSize;
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                final ArrayList added = mAllAppsList.added;
                mAllAppsList.added = new ArrayList();

                mHandler.post(new Runnable() {
                    public void run() {
                        final long t = SystemClock.uptimeMillis();
                        if (callbacks != null) {
                        	isNeedSave = true;
                            if (first) {
                                callbacks.bindAllApplications(added);
                            } else {
                                callbacks.bindAppsAdded(added);
                            }
                            if (DEBUG_LOADERS) {
                                Log.d(TAG, "bound " + added.size() + " apps in "
                                    + (SystemClock.uptimeMillis() - t) + "ms");
                            }
                        } else {
                            Log.i(TAG, "not binding apps: no Launcher activity");
                        }
                    }
                });

把我們的mAllAppsList.added給瞭一個新申請的ArrayList也就是added,然後把我們的mAllAppsList.added重新申請,置空。最後又啟動瞭一個線程來加載和顯示我們的應用瞭,也就是

                            if (first) {
                                callbacks.bindAllApplications(added);
                            } else {
                                callbacks.bindAppsAdded(added);
                            }

這兩個差不多,最終走向一樣,我們隻看一個bindAllApplications,bindAllApplications是在Launcher中實現的

    public void bindAllApplications(final ArrayList apps) {
        // Remove the progress bar entirely; we could also make it GONE
        // but better to remove it since we know it's not going to be used
        View progressBar = mAppsCustomizeTabHost.
            findViewById(R.id.apps_customize_progress_bar);
        if (progressBar != null) {
            ((ViewGroup)progressBar.getParent()).removeView(progressBar);
        }
        if (LOGD) Log.d(TAG, "bindAllApplications " + apps.toString());
        // We just post the call to setApps so the user sees the progress bar
        // disappear-- otherwise, it just looks like the progress bar froze
        // which doesn't look great
        mAppsCustomizeTabHost.post(new Runnable() {
            public void run() {
                if (mAppsCustomizeContent != null) {
                    mAppsCustomizeContent.setApps(apps);
                }
            }
        });
    }

這裡出現兩個佈局mAppsCustomizeTabHost和mAppsCustomizeContent,這裡插個小曲,來說一下Launcher佈局,我們不從最底層說起,我們從DragLayout這層說起,顧名思義是拖動層瞭,這一層包含AppsCustomizeTabHost也就是主菜單,Hotseat最下面的那一行應用圖標,Workspace就是待機界面瞭等等吧,其它不重要的就不說瞭。接著說說Workspace,Workspace包含很多CellLayout,CellLayout就是我們在待機左右滑動時的頁,CellLayout又包含CellLayoutChildren,CellLayoutChildren包含許多LauncherAppWidgetHostView接下來就是我們的Widget瞭,CellLayoutChildren還包含BubbleTextView,就是我們的App圖標瞭,這就是Workspace的構造。接著說說主菜單,也就是AppsCustomizeTabHost,AppsCustomizeTabHost之上有很多層,不再解釋,直接到AppsCustomizePagedView層,AppsCustomizePagedView包含很多PagedViewCellLayout,PagedViewCellLayout就是我們在主菜單左右滑動出現的頁瞭,PagedViewCellLayout之上是PagedViewCellLayoutChildren,PagedViewCellLayoutChildren包含很多PagedViewIcon也就是我們的應用圖標。這樣我們就清楚瞭Launcher的大致佈局瞭。好瞭,我們接著說mAppsCustomizeTabHost和mAppsCustomizeContent,這兩個也就是AppsCustomizeTabHost和AppsCustomizePagedView,通過上面的解釋可以明白它們之間的關系瞭,我們這裡要加載應用,就是去AppsCustomizePagedView中加載瞭,也就是mAppsCustomizeContent.setApps(apps)瞭

    public void setApps(ArrayList list) {
        mApps = list;
        Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
        updatePageCounts();

        // The next layout pass will trigger data-ready if both widgets and apps are set, so
        // request a layout to do this test and invalidate the page data when ready.
        LauncherModel.cacheAllApp(mContext, mApps);
        if (testDataReady()) requestLayout();
        invalidatePageData();
    }

首先重新計算我們的page頁個數

    private void updatePageCounts() {
        mNumWidgetPages = (int) Math.ceil(mWidgets.size() /
                (float) (mWidgetCountX * mWidgetCountY));
        mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
    }

接著備份我們的應用信息到數據庫LauncherModel.cacheAllApp(mContext, mApps),也就我們前面說的從數據庫加載應用的過程,由於代碼不同,有的代碼沒有這一部分,所以不做講解,好處就是開機加載應用圖標比較快。然後就是更新我們佈局,就可以把我們的應用顯示出來瞭。而invalidatePageData()是什麼呢?就是Launcher頁面都放滿瞭圖標,就新增一頁,來放圖標,最終還是通過requestLayout()來從新分佈佈局刷新顯示瞭。至此我們的圖標就顯示出來瞭。

接著我們說說當點擊圖標的時候怎樣啟動應用的。這個分為兩個,一個是點擊主菜單圖標,一個點擊待機圖標。我麼先說點擊待機圖標也就是Workspace圖標,這個事件響應再Launcher中,也就是

    public void onClick(View v) {
        // Make sure that rogue clicks don't get through while allapps is launching, or after the
        // view has detached (it's possible for this to happen if the view is removed mid touch).
        if (v.getWindowToken() == null) {
            return;
        }

        if (mWorkspace.isSwitchingState()) {
            return;
        }

        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
            // Open shortcut
            final Intent intent = ((ShortcutInfo) tag).intent;
            int[] pos = new int[2];
            v.getLocationOnScreen(pos);
            intent.setSourceBounds(new Rect(pos[0], pos[1],
                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
            boolean success = startActivitySafely(intent, tag);

            if (success && v instanceof BubbleTextView) {
                mWaitingForResume = (BubbleTextView) v;
                mWaitingForResume.setStayPressed(true);
            }
        } else if (tag instanceof FolderInfo) {
            if (v instanceof FolderIcon) {
                FolderIcon fi = (FolderIcon) v;
                handleFolderClick(fi);
            }
        } else if (v == mAllAppsButton) {
            if (mState == State.APPS_CUSTOMIZE) {
                showWorkspace(true);
            } else {
                onClickAllAppsButton(v);
            }
        }
    }

先說一下ShortcutInfo,ShortcutInfo的信息是從ApplicationInfo信息中獲取的,至於是怎麼獲取,這裡就不再解釋,童鞋們可以自己研究。所以這裡的((ShortcutInfo) tag).intent信息大傢就很明白瞭,是可以啟動一個應用的,前面說過,不再解釋。至於下面的FolderInfo就是點擊文件夾時做瞭什麼,這裡不講解瞭,自己研究。然後看看主菜單點擊時是如何啟動應用的,這部分在AppsCustomizePagedView中

    public void onClick(View v) {
        // When we have exited all apps or are in transition, disregard clicks
        if (!mLauncher.isAllAppsCustomizeOpen() ||
                mLauncher.getWorkspace().isSwitchingState()) return;

        if (v instanceof PagedViewIcon) {
            // Animate some feedback to the click
            final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
            // bug 211336 begin
            //if (OptConfig.LC_RAM_SUPPORT) {
                // remove the animation when click
                mLauncher.startActivitySafely(appInfo.intent, appInfo);
            //} else {
            //    animateClickFeedback(v, new Runnable() {
            //        @Override
            //        public void run() {
            //            mLauncher.startActivitySafely(appInfo.intent, appInfo);
            //        }
            //    });
            //}
            // bug 211336 end
        } else if (v instanceof PagedViewWidget) {
            // Let the user know that they have to long press to add a widget
            Toast.makeText(getContext(), R.string.long_press_widget_to_add,
                    Toast.LENGTH_SHORT).show();

            // Create a little animation to show that the widget can move
            float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
            final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
            AnimatorSet bounce = new AnimatorSet();
            ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY);
            tyuAnim.setDuration(125);
            ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f);
            tydAnim.setDuration(100);
            bounce.play(tyuAnim).before(tydAnim);
            bounce.setInterpolator(new AccelerateInterpolator());
            bounce.start();
        }
    }

兩部分,一部分是點擊主菜單圖標,一部分是點擊Widget,也就是長按Widget添加到Workspace待機,這裡隻說點擊主菜單圖標也就是PagedViewIcon,單點擊PagedViewIcon的時候就會獲取到響應的ApplicationInfo信息,通過ApplicationInfo的intent來啟動一個應用是完全可以的,這個我們前面已經強調過很多次瞭。

點擊圖標啟動應用就講這麼多,不再多講,接下來我們說說拖動圖標或者widget。當我們長按時就可以從待機移動圖標或widget,還可一從主菜單把圖標或widget移動到待機,這個過程是怎麼一回事呢?這裡做一下講解。先說在workspace待機拖動圖標,我們從長按事件說起,這個在Launcher中

    public boolean onLongClick(View v) {
        if (mState != State.WORKSPACE) {
            return false;
        }

        if (isWorkspaceLocked()) {
            return false;
        }

        if (!(v instanceof CellLayout)) {
            v = (View) v.getParent().getParent();
        }

        resetAddInfo();
        CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
        // This happens when long clicking an item with the dpad/trackball
        if (longClickCellInfo == null) {
            return true;
        }

        // The hotseat touch handling does not go through Workspace, and we always allow long press
        // on hotseat items.
        final View itemUnderLongClick = longClickCellInfo.cell;
        boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
        if (allowLongPress && !mDragController.isDragging()) {
            if (itemUnderLongClick == null) {
                // User long pressed on empty space
                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                startWallpaper();
            } else {
                if (!(itemUnderLongClick instanceof Folder)) {
                    // User long pressed on an item
                    mWorkspace.startDrag(longClickCellInfo);
                }
            }
        }
        return true;
    }

也就是mWorkspace.startDrag(longClickCellInfo)其它內容不做講解,感興趣可以看看。mWorkspace.startDrag(longClickCellInfo)也就是

    void startDrag(CellLayout.CellInfo cellInfo) {
        View child = cellInfo.cell;

        // Make sure the drag was started by a long press as opposed to a long click.
        if (!child.isInTouchMode()) {
            return;
        }

        mDragInfo = cellInfo;
        child.setVisibility(GONE);

        child.clearFocus();
        child.setPressed(false);

        final Canvas canvas = new Canvas();

        // We need to add extra padding to the bitmap to make room for the glow effect
        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;

        // The outline is used to visualize where the item will land if dropped
        mDragOutline = createDragOutline(child, canvas, bitmapPadding);
        beginDragShared(child, this);
    }

創建一個Bitmap為mDragOutline這個mDragOutline保存的是原始的圖片,等到UP的時候也就是松手的時候會用到。然後就是beginDragShared也就是

    public void beginDragShared(View child, DragSource source) {
        Resources r = getResources();

        // We need to add extra padding to the bitmap to make room for the glow effect
        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;

        // The drag bitmap follows the touch point around on the screen
        final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);

        final int bmpWidth = b.getWidth();

        mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
        final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
        int dragLayerY = mTempXY[1] - bitmapPadding / 2;

        Point dragVisualizeOffset = null;
        Rect dragRect = null;
        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
            int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
            int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
            int top = child.getPaddingTop();
            int left = (bmpWidth - iconSize) / 2;
            int right = left + iconSize;
            int bottom = top + iconSize;
            dragLayerY += top;
            // Note: The drag region is used to calculate drag layer offsets, but the
            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
            dragVisualizeOffset = new Point(-bitmapPadding / 2, iconPaddingTop - bitmapPadding / 2);
            dragRect = new Rect(left, top, right, bottom);
        } else if (child instanceof FolderIcon) {
            int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
            dragRect = new Rect(0, 0, child.getWidth(), previewSize);
        }

        // Clear the pressed state if necessary
        if (child instanceof BubbleTextView) {
            BubbleTextView icon = (BubbleTextView) child;
            icon.clearPressedOrFocusedBackground();
        }

        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);
        b.recycle();
    }

創建一個拖動的Bitmap這個和剛才的mDragOutline不同,這個Bitmap透明度、大小等等有變化的。然後就是

        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);

也就是

    public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
            DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion) {
        if (PROFILE_DRAWING_DURING_DRAG) {
            android.os.Debug.startMethodTracing("Launcher");
        }

        // Hide soft keyboard, if visible
        if (mInputMethodManager == null) {
            mInputMethodManager = (InputMethodManager)
                    mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
        }
        mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);

        for (DragListener listener : mListeners) {
            listener.onDragStart(source, dragInfo, dragAction);
        }

        final int registrationX = mMotionDownX - dragLayerX;
        final int registrationY = mMotionDownY - dragLayerY;

        final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
        final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;

        mDragging = true;

        mDragObject = new DropTarget.DragObject();

        mDragObject.dragComplete = false;
        mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
        mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
        mDragObject.dragSource = source;
        mDragObject.dragInfo = dragInfo;

        mVibrator.vibrate(VIBRATE_DURATION);

        final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                registrationY, 0, 0, b.getWidth(), b.getHeight());

        if (dragOffset != null) {
            dragView.setDragVisualizeOffset(new Point(dragOffset));
        }
        if (dragRegion != null) {
            dragView.setDragRegion(new Rect(dragRegion));
        }

        dragView.show(mMotionDownX, mMotionDownY);
        handleMoveEvent(mMotionDownX, mMotionDownY);
    }

創建一個DragView然後顯示dragView.show,長按的時候會震動一下也就是在這裡mVibrator.vibrate(VIBRATE_DURATION),最終handleMoveEvent(mMotionDownX, mMotionDownY)也就是

    private void handleMoveEvent(int x, int y) {
        mDragObject.dragView.move(x, y);

        // Drop on someone?
        final int[] coordinates = mCoordinatesTemp;
        DropTarget dropTarget = findDropTarget(x, y, coordinates);
        mDragObject.x = coordinates[0];
        mDragObject.y = coordinates[1];
        if (dropTarget != null) {
            DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject);
            if (delegate != null) {
                dropTarget = delegate;
            }

            if (mLastDropTarget != dropTarget) {
                if (mLastDropTarget != null) {
                    mLastDropTarget.onDragExit(mDragObject);
                }
                dropTarget.onDragEnter(mDragObject);
            }
            dropTarget.onDragOver(mDragObject);
        } else {
            if (mLastDropTarget != null) {
                mLastDropTarget.onDragExit(mDragObject);
            }
        }
        mLastDropTarget = dropTarget;

        // After a scroll, the touch point will still be in the scroll region.
        // Rather than scrolling immediately, require a bit of twiddling to scroll again
        final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
        mDistanceSinceScroll +=
            Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
        mLastTouch[0] = x;
        mLastTouch[1] = y;

        if (x  slop) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) {
                    mScrollRunnable.setDirection(SCROLL_LEFT);
                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                }
            }
        } else if (x > mScrollView.getWidth() - mScrollZone) {
            if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) {
                    mScrollRunnable.setDirection(SCROLL_RIGHT);
                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                }
            }
        } else {
            if (mScrollState == SCROLL_WAITING_IN_ZONE) {
                mScrollState = SCROLL_OUTSIDE_ZONE;
                mScrollRunnable.setDirection(SCROLL_RIGHT);
                mHandler.removeCallbacks(mScrollRunnable);
                mDragScroller.onExitScrollArea();
            }
        }
    }

當開始拖動的時候也就開始分發ACTION_MOVE消息,也就是

    public boolean onTouchEvent(MotionEvent ev) {
        if (!mDragging) {
            return false;
        }

        final int action = ev.getAction();
        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
        final int dragLayerX = dragLayerPos[0];
        final int dragLayerY = dragLayerPos[1];

        switch (action) {
        case MotionEvent.ACTION_DOWN:
            // Remember where the motion event started
            mMotionDownX = dragLayerX;
            mMotionDownY = dragLayerY;

            if ((dragLayerX  mScrollView.getWidth() - mScrollZone)) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
            } else {
                mScrollState = SCROLL_OUTSIDE_ZONE;
            }
            break;
        case MotionEvent.ACTION_MOVE:
            handleMoveEvent(dragLayerX, dragLayerY);
            break;
        case MotionEvent.ACTION_UP:
            // Ensure that we've processed a move event at the current pointer location.
            handleMoveEvent(dragLayerX, dragLayerY);

            mHandler.removeCallbacks(mScrollRunnable);
            if (mDragging) {
                drop(dragLayerX, dragLayerY);
            }
            endDrag();
            break;
        case MotionEvent.ACTION_CANCEL:
            cancelDrag();
            break;
        }

        return true;
    }

這裡的MotionEvent.ACTION_MOVE消息,一直重復handleMoveEvent,當松手的時候就是MotionEvent.ACTION_UP瞭。我們還先回到handleMoveEvent看看

mDragObject.dragView.move(x, y);

這個就是拖動的過程瞭,也就是

    void move(int touchX, int touchY) {
        DragLayer.LayoutParams lp = mLayoutParams;
        lp.x = touchX - mRegistrationX + (int) mOffsetX;
        lp.y = touchY - mRegistrationY + (int) mOffsetY;
        mDragLayer.requestLayout();
    }

一直更改坐標,然後更新。然後還回到handleMoveEvent下面的內容是什麼呢?大致解釋一下不再深入解析,就是當drop也就是UP松手時做的一下事情,和當移動到邊緣時切換到下一頁,這些不再講解。然後我們回到MotionEvent.ACTION_UP消息,也就是

            handleMoveEvent(dragLayerX, dragLayerY);

            mHandler.removeCallbacks(mScrollRunnable);
            if (mDragging) {
                drop(dragLayerX, dragLayerY);
            }
            endDrag();

handleMoveEvent我們剛才已經說過瞭,endDrag()時拖動結束釋放資源,我們單看drop(dragLayerX, dragLayerY)也就是

    private void drop(float x, float y) {
        final int[] coordinates = mCoordinatesTemp;
        final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);

        mDragObject.x = coordinates[0];
        mDragObject.y = coordinates[1];
        boolean accepted = false;
        if (dropTarget != null) {
            mDragObject.dragComplete = true;
            dropTarget.onDragExit(mDragObject);
            if (dropTarget.acceptDrop(mDragObject)) {
                dropTarget.onDrop(mDragObject);
                accepted = true;
            }
        }
        mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, accepted);
    }

也就是dropTarget.onDrop(mDragObject),其它內容不做詳解,都是放松手後做瞭一些處理,我們隻看看dropTarget.onDrop(mDragObject),DropTarget是個接口,在Workspace中實現

    public void onDrop(DragObject d) {
        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
                mDragViewVisualCenter);

        // We want the point to be mapped to the dragTarget.
        if (mDragTargetLayout != null) {
            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
                mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter);
            } else {
                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
            }
        }

        CellLayout dropTargetLayout = mDragTargetLayout;

        int snapScreen = -1;
        if (d.dragSource != this) {
            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
                    (int) mDragViewVisualCenter[1] };
            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
        } else if (mDragInfo != null) {
            final View cell = mDragInfo.cell;

            if (dropTargetLayout != null) {
                // Move internally
                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
                long container = hasMovedIntoHotseat ?
                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
                int screen = (mTargetCell[0] = 0 && mTargetCell[1] >= 0) {
                    if (hasMovedLayouts) {
                        // Reparent the view
                        /* Modify 112809 Spreadst of 112809 Monkey start */
                        if(getParentCellLayoutForView(cell) != null){
                            getParentCellLayoutForView(cell).removeView(cell);
                        }else{
                            Log.d(TAG,"this view not be added to CellLayout");
                        }
                        addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
                                mDragInfo.spanX, mDragInfo.spanY);
                    }

                    // update the item's position after drop
                    final ItemInfo info = (ItemInfo) cell.getTag();
                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
                    dropTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
                    lp.cellX = mTargetCell[0];
                    lp.cellY = mTargetCell[1];
                    cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen,
                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));

                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
                            cell instanceof LauncherAppWidgetHostView) {
                        final CellLayout cellLayout = dropTargetLayout;
                        // We post this call so that the widget has a chance to be placed
                        // in its final location

                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
                        if (pinfo != null && pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
                            final Runnable resizeRunnable = new Runnable() {
                                public void run() {
                                    DragLayer dragLayer = mLauncher.getDragLayer();
                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
                                }
                            };
                            post(new Runnable() {
                                public void run() {
                                    if (!isPageMoving()) {
                                        resizeRunnable.run();
                                    } else {
                                        mDelayedResizeRunnable = resizeRunnable;
                                    }
                                }
                            });
                        }
                    }
                    ItemInfo modelItem = null;
                    if(info != null) {
                        modelItem = LauncherModel.sItemsIdMap.get(info.id);
                    }
                    if(modelItem == null){
                        /**Bug141020 Bug146476 start.if the item has been deleted from db ,such as stk1 ,stk2,
                         *  just return,if the item is Folder and there is no other Shorcut except stk1 ,stk2 
                         *  delete the Emputy Folder**/
                        if(cell instanceof FolderIcon){
                            FolderIcon folder= (FolderIcon)cell;
                            ArrayList folderItem = folder.mFolder.getItemsInReadingOrder();
                            if(folderItem.size() == 0){
                                getParentCellLayoutForView(cell).removeView(cell);
                            }
                        }
                        return;
                    }
                    LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX,
                            lp.cellY);
                }
            }

            final CellLayout parent = (CellLayout) cell.getParent().getParent();

            // Prepare it to be animated into its new position
            // This must be called after the view has been re-parented
            final Runnable disableHardwareLayersRunnable = new Runnable() {
                @Override
                public void run() {
                    mAnimatingViewIntoPlace = false;
                    updateChildrenLayersEnabled();
                }
            };
            mAnimatingViewIntoPlace = true;
            if (d.dragView.hasDrawn()) {
                int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
                setFinalScrollForPageChange(snapScreen);
                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
                        disableHardwareLayersRunnable);
                resetFinalScrollForPageChange(snapScreen);
            } else {
                cell.setVisibility(VISIBLE);
            }
            parent.onDropChild(cell);
        }
    }

這個函數比較大,就不一一解釋瞭,大概說一下,分為三種情況,第一如果是從主菜單拖到workspace待機的走onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d)這裡,如果是在workspace拖動的,分兩種情況,一種就是沒有把該圖標拖到另外一頁,就更新刷新就完瞭,如果拖到瞭下一頁就走

                        addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
                                mDragInfo.spanX, mDragInfo.spanY);

addInScreen也就是

    void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY,
            boolean insert) {
        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
            if (screen = getChildCount()) {
                Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
                    + " (was " + screen + "); skipping child");
                return;
            }
        }

        final CellLayout layout;
        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
            layout = mLauncher.getHotseat().getLayout();
            child.setOnKeyListener(null);

            // Hide folder title in the hotseat
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(false);
            }

            if (screen < 0) {
                screen = mLauncher.getHotseat().getOrderInHotseat(x, y);
            } else {
                // Note: We do this to ensure that the hotseat is always laid out in the orientation
                // of the hotseat in order regardless of which orientation they were added
                x = mLauncher.getHotseat().getCellXFromOrder(screen);
                y = mLauncher.getHotseat().getCellYFromOrder(screen);
            }
        } else {
            // Show folder title if not in the hotseat
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(true);
            }

            layout = (CellLayout) getChildAt(screen);
            child.setOnKeyListener(new IconKeyEventListener());
        }

        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
        if (lp == null) {
            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
        } else {
            lp.cellX = x;
            lp.cellY = y;
            lp.cellHSpan = spanX;
            lp.cellVSpan = spanY;
        }

        if (spanX < 0 && spanY < 0) {
            lp.isLockedToGrid = false;
        }

        // Get the canonical child id to uniquely represent this view in this screen
        int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY);
        boolean markCellsAsOccupied = !(child instanceof Folder);
        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
            // TODO: This branch occurs when the workspace is adding views
            // outside of the defined grid
            // maybe we should be deleting these items from the LauncherModel?
            Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
        }

        if (!(child instanceof Folder)) {
            child.setHapticFeedbackEnabled(false);
            child.setOnLongClickListener(mLongClickListener);
        }
        if (child instanceof DropTarget) {
            mDragController.addDropTarget((DropTarget) child);
        }
    }

這裡做瞭一些計算,拖動的是什麼,放在哪裡等等吧,然後就layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)這裡的layout是CellLayout所以layout.addViewToCellLayout也就是

    public boolean addViewToCellLayout(
            View child, int index, int childId, LayoutParams params, boolean markCells) {
        final LayoutParams lp = params;

        // Generate an id for each view, this assumes we have at most 256x256 cells
        // per workspace screen
        if (lp.cellX >= 0 && lp.cellX = 0 && lp.cellY <= mCountY - 1) {
            // If the horizontal or vertical span is set to -1, it is taken to
            // mean that it spans the extent of the CellLayout
            if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
            if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;

            child.setId(childId);

            mChildren.addView(child, index, lp);

            if (markCells) markCellsAsOccupiedForView(child);

            return true;
        }
        return false;
    }

也就是mChildren.addView(child, index, lp)瞭,這裡的mChildren也就是CellLayoutChildren,CellLayoutChildren我們前面說過瞭,就不再說瞭,至此一個移動過程結束。現在我們回過頭來看看如果是從主菜單拖到workspace待機是怎麼一個過程,這個過程主要是從主菜單到workspace的轉換過程,我們還是從長按事件開始,從主菜單長按事件應該在AppsCustomizePagedView裡面,但是這裡沒有,我們去它的父類PagedViewWithDraggableItems中尋找,也就是

    @Override
    public boolean onLongClick(View v) {
        // Return early if this is not initiated from a touch
        if (!v.isInTouchMode()) return false;
        // Return early if we are still animating the pages
        if (mNextPage != INVALID_PAGE) return false;
        // When we have exited all apps or are in transition, disregard long clicks
        if (!mLauncher.isAllAppsCustomizeOpen() ||
                mLauncher.getWorkspace().isSwitchingState()) return false;

        return beginDragging(v);
    }

也就是beginDragging,beginDragging在其子類AppsCustomizePagedView中重寫瞭,也就是

    @Override
    protected boolean beginDragging(View v) {
        // Dismiss the cling
        mLauncher.dismissAllAppsCling(null);

        if (!super.beginDragging(v)) return false;

        // Go into spring loaded mode (must happen before we startDrag())
        mLauncher.enterSpringLoadedDragMode();

        if (v instanceof PagedViewIcon) {
            beginDraggingApplication(v);
        } else if (v instanceof PagedViewWidget) {
            beginDraggingWidget(v);
        }
        return true;
    }

mLauncher.enterSpringLoadedDragMode()是做什麼的呢?就是隱藏主菜單,顯示workspace待機,這樣就從顯示上切換到workspace瞭,但是實質還沒切換到workspace,這個後面會講到,然後就是區分開拖動的是PagedViewIcon(App圖標),還是PagedViewWidget(widget圖標)。這裡我們隻看App圖標,也就是beginDraggingApplication(v)

    private void beginDraggingApplication(View v) {
        mLauncher.getWorkspace().onDragStartedWithItem(v);
        mLauncher.getWorkspace().beginDragShared(v, this);
    }

這裡就是實質上切換到workspace瞭,先看上面一句mLauncher.getWorkspace().onDragStartedWithItem(v)也就是

    public void onDragStartedWithItem(View v) {
        final Canvas canvas = new Canvas();

        // We need to add extra padding to the bitmap to make room for the glow effect
        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;

        // The outline is used to visualize where the item will land if dropped
        mDragOutline = createDragOutline(v, canvas, bitmapPadding);
    }

這裡同樣創建瞭一個Bitmap為mDragOutline,和剛才我們講解workspace拖動一樣啦,就不再說瞭,然後看看下句mLauncher.getWorkspace().beginDragShared(v, this)也就是

    public void beginDragShared(View child, DragSource source) {
        Resources r = getResources();

        // We need to add extra padding to the bitmap to make room for the glow effect
        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;

        // The drag bitmap follows the touch point around on the screen
        final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);

        final int bmpWidth = b.getWidth();

        mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
        final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
        int dragLayerY = mTempXY[1] - bitmapPadding / 2;

        Point dragVisualizeOffset = null;
        Rect dragRect = null;
        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
            int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
            int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
            int top = child.getPaddingTop();
            int left = (bmpWidth - iconSize) / 2;
            int right = left + iconSize;
            int bottom = top + iconSize;
            dragLayerY += top;
            // Note: The drag region is used to calculate drag layer offsets, but the
            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
            dragVisualizeOffset = new Point(-bitmapPadding / 2, iconPaddingTop - bitmapPadding / 2);
            dragRect = new Rect(left, top, right, bottom);
        } else if (child instanceof FolderIcon) {
            int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
            dragRect = new Rect(0, 0, child.getWidth(), previewSize);
        }

        // Clear the pressed state if necessary
        if (child instanceof BubbleTextView) {
            BubbleTextView icon = (BubbleTextView) child;
            icon.clearPressedOrFocusedBackground();
        }

        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);
        b.recycle();
    }

又到瞭這裡瞭,這個和剛才workspace拖動是一樣的瞭,也不做解釋瞭,然後就進入mDragController.startDrag再然後就是handleMoveEvent循環瞭,然後就是拖到適當位置MotionEvent.ACTION_UP消息瞭,然後就是drop,dropTarget.onDrop這些過程和workspace拖動過程都一樣瞭,唯獨到瞭Workspace的onDrop中不同,也就是我們前面提到的,當從主菜單托出圖標是會走onDropExternal(touchXY, d.dragInfo,
dropTargetLayout, false, d)也就是

    private void onDropExternal(final int[] touchXY, final Object dragInfo,
            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
        final Runnable exitSpringLoadedRunnable = new Runnable() {
            @Override
            public void run() {
                mLauncher.exitSpringLoadedDragModeDelayed(true, false);
            }
        };

        ItemInfo info = (ItemInfo) dragInfo;
        int spanX = info.spanX;
        int spanY = info.spanY;
        if (mDragInfo != null) {
            spanX = mDragInfo.spanX;
            spanY = mDragInfo.spanY;
        }

        final long container = mLauncher.isHotseatLayout(cellLayout) ?
                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
        final int screen = indexOfChild(cellLayout);
        if (!mLauncher.isHotseatLayout(cellLayout) && screen != mCurrentPage
                && mState != State.SPRING_LOADED) {
            snapToPage(screen);
        }

        if (info instanceof PendingAddItemInfo) {
            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;

            boolean findNearestVacantCell = true;
            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
                        cellLayout, mTargetCell);
                if (willCreateUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout, mTargetCell,
                        true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
                                mDragTargetLayout, mTargetCell)) {
                    findNearestVacantCell = false;
                }
            }
            if (findNearestVacantCell) {
                    mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], spanX, spanY, null,
                        cellLayout, mTargetCell);
            }

            Runnable onAnimationCompleteRunnable = new Runnable() {
                @Override
                public void run() {
                    // When dragging and dropping from customization tray, we deal with creating
                    // widgets/shortcuts/folders in a slightly different way
                    switch (pendingInfo.itemType) {
                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                        mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
                                container, screen, mTargetCell, null);
                        break;
                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                        mLauncher.processShortcutFromDrop(pendingInfo.componentName,
                                container, screen, mTargetCell, null);
                        break;
                    default:
                        throw new IllegalStateException("Unknown item type: " +
                                pendingInfo.itemType);
                    }
                    cellLayout.onDragExit();
                }
            };

            // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
            // location and size on the home screen.
            RectF r = estimateItemPosition(cellLayout, pendingInfo,
                    mTargetCell[0], mTargetCell[1], spanX, spanY);
            int loc[] = new int[2];
            loc[0] = (int) r.left;
            loc[1] = (int) r.top;
            setFinalTransitionTransform(cellLayout);
            float cellLayoutScale =
                    mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(cellLayout, loc);
            resetTransitionTransform(cellLayout);

            float dragViewScale =  Math.min(r.width() / d.dragView.getMeasuredWidth(),
                    r.height() / d.dragView.getMeasuredHeight());
            // The animation will scale the dragView about its center, so we need to center about
            // the final location.
            loc[0] -= (d.dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
            loc[1] -= (d.dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;

            mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, loc,
                    dragViewScale * cellLayoutScale, onAnimationCompleteRunnable);
        } else {
            // This is for other drag/drop cases, like dragging from All Apps
            View view = null;

            switch (info.itemType) {
            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                if (info.container == NO_ID && info instanceof ApplicationInfo) {
                    // Came from all apps -- make a copy
                    info = new ShortcutInfo((ApplicationInfo) info);
                }
                view = mLauncher.createShortcut(R.layout.application, cellLayout,
                        (ShortcutInfo) info);
                break;
            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
                        (FolderInfo) info, mIconCache);
                break;
            default:
                throw new IllegalStateException("Unknown item type: " + info.itemType);
            }

            // First we find the cell nearest to point at which the item is
            // dropped, without any consideration to whether there is an item there.
            if (touchXY != null) {
                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
                        cellLayout, mTargetCell);
                d.postAnimationRunnable = exitSpringLoadedRunnable;
                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, true,
                        d.dragView, d.postAnimationRunnable)) {
                    return;
                }
                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, d, true)) {
                    return;
                }
            }

            if (touchXY != null) {
                // when dragging and dropping, just find the closest free spot
                mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, null,
                        cellLayout, mTargetCell);
            } else {
                cellLayout.findCellForSpan(mTargetCell, 1, 1);
            }
            addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX,
                    info.spanY, insertAtFirst);
            cellLayout.onDropChild(view);
            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
            cellLayout.getChildrenLayout().measureChild(view);

            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen,
                    lp.cellX, lp.cellY);

            if (d.dragView != null) {
                // We wrap the animation call in the temporary set and reset of the current
                // cellLayout to its final transform -- this means we animate the drag view to
                // the correct final location.
                setFinalTransitionTransform(cellLayout);
                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
                        exitSpringLoadedRunnable);
                resetTransitionTransform(cellLayout);
            }
        }
    }

這裡也分為兩個部分一部分是PendingAddItemInfo,PendingAddItemInfo是Widget有關的,這裡不再詳解,而我們的應用圖標又會走到

            addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX,
                    info.spanY, insertAtFirst);

這裡,addInScreen我們上面已經講解過瞭,這裡就不再贅述瞭。其它的內容不再講解,有興趣自己研究吧。至此我們的拖動過程就講解完瞭。

這篇中我們講解瞭Launcher圖標的加載過程,點擊圖標進入應用的過程,拖動圖標的過程,至於安裝應用、卸載應用、更新應用、壁紙、widget等等其它Launcher內容,如果有需以後再講解吧。

還是那句話,給大師們茶餘飯後取樂,給後來者拋磚引玉,不要在背後罵我就謝天謝地瞭。

發佈留言