編輯:關於Android編程
應朋友要求,把Launcher應用再詳細解說一下。
首先,我們需要去LauncherApplication裡面看一下,因為這裡沒有兩個成員變量對我們這一講非常重要,它們就是
public LauncherModel mModel; public IconCache mIconCache;
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; }
/** 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;
/** * 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;
好了,LauncherApplication我們就看到這裡,然後我們進入Launcher中,Launcher是一個Activity,所以我們就先看它的onCreate函數,我們只截取我們關心的代碼
LauncherApplication app = ((LauncherApplication)getApplication()); mModel = app.setLauncher(this); mIconCache = app.getIconCache(); mDragController = new DragController(this);
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); } } }
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); } }
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(); } }
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(); Listapps = 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 0 && i < N) { try { if (DEBUG_LOADERS) { Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms"); } Thread.sleep(mAllAppsLoadDelay); } catch (InterruptedException exc) { } } } if (DEBUG_LOADERS) { Log.d(TAG, "cached all " + N + " apps in " + (SystemClock.uptimeMillis()-t) + "ms" + (mAllAppsLoadDelay > 0 ? " (including delay)" : "")); } }
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); final PackageManager packageManager = mContext.getPackageManager(); Listapps = null;
mAllAppsList.clear(); final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; apps = packageManager.queryIntentActivities(mainIntent, 0);
for (int j=0; i
去加載我們的應用,ApplicationInfo之前我們也說過,但是前面只看了它存儲了我們應用的什麼信息,這裡我們去看看它的構造函數public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache, HashMap
首先,給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
CacheEntry也就是private static class CacheEntry { public Bitmap icon; public String title; }
只保存了應用的圖標和名字。cacheLocked(application.componentName, info, labelCache)也就是private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info, HashMap
首先從我們的緩存中獲取,如果有就直接返回,如果沒有就去獲取。獲取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) { // It's too big, scale it down. final float ratio = (float) sourceWidth / sourceHeight; if (sourceWidth > sourceHeight) { height = (int) (width / ratio); } else if (sourceHeight > sourceWidth) { width = (int) (height * ratio); } } else if (sourceWidth < width && sourceHeight < height) { // Don't scale up the icon width = sourceWidth; height = sourceHeight; } } // no intrinsic size --> 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
剛才我們用了大量的篇幅講解了制作一個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 < mScrollZone) { if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > 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 < mScrollZone) || (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) ? mDragInfo.screen : indexOfChild(dropTargetLayout); int spanX = mDragInfo != null ? mDragInfo.spanX : 1; int spanY = mDragInfo != null ? mDragInfo.spanY : 1; // First we find the cell nearest to point at which the item is // dropped, without any consideration to whether there is an item there. mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); // If the item being dropped is a shortcut and the nearest drop // cell also contains a shortcut, then create a folder with the two shortcuts. if (!mInScrollArea && createUserFolderIfNecessary(cell, container, dropTargetLayout, mTargetCell, false, d.dragView, null)) { return; } if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, d, false)) { return; } // Aside from the special case where we're dropping a shortcut onto a shortcut, // we need to find the nearest cell location that is vacant mTargetCell = findNearestVacantArea((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], mDragInfo.spanX, mDragInfo.spanY, cell, dropTargetLayout, mTargetCell); if (mCurrentPage != screen && !hasMovedIntoHotseat) { snapScreen = screen; snapToPage(screen); } if (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; ArrayListfolderItem = 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 < 0 || 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 <= mCountX - 1 && lp.cellY >= 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內容,如果有需以後再講解吧。
還是那句話,給大師們茶余飯後取樂,給後來者拋磚引玉,不要在背後罵我就謝天謝地了。
先說明一下,項目代碼已上傳至github,不想看長篇大論的也可以先去下代碼,對照代碼,哪裡不懂點哪裡。代碼在這https://github.com/zgzczzw/ZHF
馬上到雙十一,紅包來襲,時間又是充裕,搶紅包的時候意外發現了百度的福袋界面還不錯,想想還要專門寫一篇博文來完成其界面。當然啦,這其實就是解鎖界面的進化版本。不過其包含的知
這篇文章篇幅較長,可以使用版權聲明下面的目錄,找到感興趣的進行閱讀目錄 3.1 Android Studio使用初探 Project面板 Stucture面板 Andro
先上效果圖: Layout為: