应朋友要求,把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<ApplicationInfo> data =
new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
/** The list of apps that have been added since the last notify() call. */
public ArrayList<ApplicationInfo> added =
new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
/** The list of apps that have been removed since the last notify() call. */
public ArrayList<ApplicationInfo> removed = new ArrayList<ApplicationInfo>();
/** The list of apps that have been modified since the last notify() call. */
public ArrayList<ApplicationInfo> modified = new ArrayList<ApplicationInfo>();
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<ResolveInfo> apps = null;
int N = Integer.MAX_VALUE;
int startIndex;
int i=0;
int batchSize = -1;
while (i < N && !mStopped) {
if (i == 0) {
mAllAppsList.clear();
final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
apps = packageManager.queryIntentActivities(mainIntent, 0);
// for (ResolveInfo resolveInfo : apps) {
// ActivityInfo activityInfo = resolveInfo.activityInfo;
// if(activityInfo == null){
// Log.v("Launcher", "loadAllAppsByBatch resolvePackageName:" + resolveInfo.resolvePackageName);
// }else{
// Log.v("Launcher", "loadAllAppsByBatch activityInfo:" + activityInfo.loadLabel(packageManager).toString());
// }
// }
if (DEBUG_LOADERS) {
Log.d(TAG, "queryIntentActivities took "
+ (SystemClock.uptimeMillis()-qiaTime) + "ms");
}
if (apps == null) {
return;
}
N = apps.size();
if (DEBUG_LOADERS) {
Log.d(TAG, "queryIntentActivities got " + N + " apps");
}
if (N == 0) {
// There are no apps?!?
return;
}
if (mBatchSize == 0) {
batchSize = N;
} else {
batchSize = mBatchSize;
}
final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
Collections.sort(apps,
new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
if (DEBUG_LOADERS) {
Log.d(TAG, "sort took "
+ (SystemClock.uptimeMillis()-sortTime) + "ms");
}
}
final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
startIndex = i;
for (int j=0; i<N && j<batchSize; j++) {
// This builds the icon bitmaps.
mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
mIconCache, mLabelCache));
i++;
}
final boolean first = i <= batchSize;
final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
final ArrayList<ApplicationInfo> added = mAllAppsList.added;
mAllAppsList.added = new ArrayList<ApplicationInfo>();
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 && 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)" : ""));
}
}
首先获取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<ResolveInfo> apps = null;
mAllAppsList.clear();
final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
apps = packageManager.queryIntentActivities(mainIntent, 0);
并且把我们mAllAppsList清空,这个mAllAppsList我们前面说过,这里不再赘述,然后
for (int j=0; i<N && j<batchSize; j++) {
// This builds the icon bitmaps.
mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
mIconCache, mLabelCache));
i++;
}
去加载我们的应用,ApplicationInfo之前我们也说过,但是前面只看了它存储了我们应用的什么信息,这里我们去看看它的构造函数
public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
HashMap<Object, CharSequence> 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<Object, CharSequence> 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<Object, CharSequence> labelCache) {
CacheEntry entry = mCache.get(componentName);
if (entry == null) {
entry = new CacheEntry();
mCache.put(componentName, entry);
ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
if (labelCache != null && labelCache.containsKey(key)) {
entry.title = labelCache.get(key).toString();
} else {
entry.title = info.loadLabel(mPackageManager).toString();
if (labelCache != null) {
labelCache.put(key, entry.title);
}
}
if (entry.title == null) {
entry.title = info.activityInfo.name;
}
entry.icon = Utilities.createIconBitmap(
getFullResIcon(info), mContext);
}
return entry;
}
首先从我们的缓存中获取,如果有就直接返回,如果没有就去获取。获取title也一样,先从缓存中获取,如果有就使用,如果没有就从应用的信息中获取,这里我们可以更改应用在Launcher中显示的名字,这些都容易理解,不做过多解释。接下来就是Icon的获取
entry.icon = Utilities.createIconBitmap(
getFullResIcon(info), mContext);
我们看看getFullResIcon也就是
public Drawable getFullResIcon(ResolveInfo info) {
Resources resources;
try {
resources = mPackageManager.getResourcesForApplication(
info.activityInfo.applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
resources = null;
}
if (resources != null) {
int iconId = info.activityInfo.getIconResource();
if (iconId != 0) {
return getFullResIcon(resources, iconId);
}
}
return getFullResDefaultActivityIcon();
}
首先获取到我们加载应用的资源信息
resources = mPackageManager.getResourcesForApplication(
info.activityInfo.applicationInfo);
如果获取资源不成功就返回getFullResDefaultActivityIcon(),如果获取资源成功
if (resources != null) {
int iconId = info.activityInfo.getIconResource();
if (iconId != 0) {
return getFullResIcon(resources, iconId);
}
}
就得到相应应用图标的ID,然后返回getFullResIcon也就是
public Drawable getFullResIcon(Resources resources, int iconId) {
Drawable d;
try {
d = resources.getDrawableForDensity(iconId, mIconDpi);
} catch (Resources.NotFoundException e) {
d = null;
}
return (d != null) ? d : getFullResDefaultActivityIcon();
}
通过图标的ID获取到图片Drawable返回,如果获取图片不成功同样返回getFullResDefaultActivityIcon(),也就是
public Drawable getFullResDefaultActivityIcon() {
return getFullResIcon(Resources.getSystem(),
com.android.internal.R.mipmap.sym_def_app_icon);
}
这个资源是我们framework下面的一张图片,也就是我们经常见的那个android小人人,可能不同代码这个图片有改动。如果想要把Launcher的图标改成我们想要的就可在这部分动手脚了,例如我们获取到一个我们准备好的图片,然后返回就可以了,这些不再多讲。现在回到cacheLocked中,还看
entry.icon = Utilities.createIconBitmap(
getFullResIcon(info), mContext);
这句话,刚才我们分析了getFullResIcon(info)返回一个Drawable,现在我们看看Utilities.createIconBitmap,Utilities.createIconBitmap是一个重构函数,一个传进去Bitmap,一个传进去Drawable,我们看传进去Drawable的函数
static Bitmap createIconBitmap(Drawable icon, Context context) {
synchronized (sCanvas) { // we share the statics :-(
if (sIconWidth == -1) {
initStatics(context);
}
int width = sIconWidth;
int height = sIconHeight;
if (icon instanceof PaintDrawable) {
PaintDrawable painter = (PaintDrawable) icon;
painter.setIntrinsicWidth(width);
painter.setIntrinsicHeight(height);
} else if (icon instanceof BitmapDrawable) {
// Ensure the bitmap has a density.
BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
Bitmap bitmap = bitmapDrawable.getBitmap();
if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
}
}
int sourceWidth = icon.getIntrinsicWidth();
int sourceHeight = icon.getIntrinsicHeight();
if (sourceWidth > 0 && sourceHeight > 0) {
// There are intrinsic sizes.
if (width < sourceWidth || height < sourceHeight) {
// 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<N && j<batchSize; j++) {
// This builds the icon bitmaps.
mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
mIconCache, mLabelCache));
i++;
}
刚才我们用了大量的篇幅讲解了制作一个ApplicationInfo的过程,希望大家都能明白。接着我们看看mAllAppsList.add也就是
public void add(ApplicationInfo info) {
if (findActivity(data, info.componentName)) {
return;
}
data.add(info);
added.add(info);
}
把我们制作好的ApplicationInfo给了AllAppsList的data和added这两个我们前面说过,这里不再多说。继续看LauncherModel中loadAllAppsByBatch后面的内容
final boolean first = i <= batchSize;
final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
final ArrayList<ApplicationInfo> added = mAllAppsList.added;
mAllAppsList.added = new ArrayList<ApplicationInfo>();
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<ApplicationInfo>也就是added,然后把我们的mAllAppsList.added重新申请,置空。最后又启动了一个线程来加载和显示我们的应用了,也就是
if (first) {
callbacks.bindAllApplications(added);
} else {
callbacks.bindAppsAdded(added);
}
这两个差不多,最终走向一样,我们只看一个bindAllApplications,bindAllApplications是在Launcher中实现的
public void bindAllApplications(final ArrayList<ApplicationInfo> 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<ApplicationInfo> 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;
ArrayList<View> folderItem = folder.mFolder.getItemsInReadingOrder();
if(folderItem.size() == 0){
getParentCellLayoutForView(cell).removeView(cell);
}
}
return;
}
LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX,
lp.cellY);
}
}
final CellLayout parent = (CellLayout) cell.getParent().getParent();
// Prepare it to be animated into its new position
// This must be called after the view has been re-parented
final Runnable disableHardwareLayersRunnable = new Runnable() {
@Override
public void run() {
mAnimatingViewIntoPlace = false;
updateChildrenLayersEnabled();
}
};
mAnimatingViewIntoPlace = true;
if (d.dragView.hasDrawn()) {
int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
setFinalScrollForPageChange(snapScreen);
mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
disableHardwareLayersRunnable);
resetFinalScrollForPageChange(snapScreen);
} else {
cell.setVisibility(VISIBLE);
}
parent.onDropChild(cell);
}
}
这个函数比较大,就不一一解释了,大概说一下,分为三种情况,第一如果是从主菜单拖到workspace待机的走onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d)这里,如果是在workspace拖动的,分两种情况,一种就是没有把该图标拖到另外一页,就更新刷新就完了,如果拖到了下一页就走
addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
mDragInfo.spanX, mDragInfo.spanY);
addInScreen也就是
void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY,
boolean insert) {
if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (screen < 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内容,如果有需以后再讲解吧。
还是那句话,给大师们茶余饭后取乐,给后来者抛砖引玉,不要在背后骂我就谢天谢地了。