展会信息港展会大全

android Launcher那点事儿(二)
来源:互联网   发布日期:2016-01-06 19:26:08   浏览:3463次  

导读: 应朋友要求,把Launcher应用再详细解说一下。 首先,我们需要去LauncherApplication里面看一下,因为这里没有两个成员变量对我们这一讲非常重要,它们就是...

应朋友要求,把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 (iadded = 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");

}

}

});

if (DEBUG_LOADERS) {

Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in "

+ (SystemClock.uptimeMillis()-t2) + "ms");

}

if (mAllAppsLoadDelay > 0 && i0 ? " (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

去加载我们的应用,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 (widthsourceHeight) {

height = (int) (width / ratio);

} else if (sourceHeight > sourceWidth) {

width = (int) (height * ratio);

}

} else if (sourceWidthuse 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 = iadded = 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 (xslop) {

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 ((dragLayerXmScrollView.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

这个函数比较大,就不一一解释了,大概说一下,分为三种情况,第一如果是从主菜单拖到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

这里做了一些计算,拖动的是什么,放在哪里等等吧,然后就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

也就是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内容,如果有需以后再讲解吧。

还是那句话,给大师们茶余饭后取乐,给后来者抛砖引玉,不要在背后骂我就谢天谢地了。

赞助本站

人工智能实验室

相关热词: android开发 教程

AiLab云推荐
推荐内容
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港