Launcher8.0启动流程的第六步startLoader的bindWorkspace将sBgDataModel中的图标放到桌面上。 放置的时候为了提高用户体验,优先放置当前屏幕的图标和widget,然后再放其他屏幕的图标,这样用户能更快的看到图标显示完成。
在创建完屏幕后,添加桌面的图标进入bindWorkspaceItems()方法
//绑定图标是回调Launcher的对应方法,而绑定时按照不同item类型进行不同的绘制。
public void bindItems(final ArrayList
final boolean forceAnimateIcons) {
final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
final Collection
final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
Workspace workspace = mWorkspace;
long newItemsScreenId = -1;
for (int i = start; i < end; i++) {
final ItemInfo item = items.get(i);
//首先进行一个简单判断,如果当前图标是放在快捷栏,而当前手机是没有快捷栏的,则不进行这个图标显示。
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
mHotseat == null) {
continue;
}
// 图标有所细分,单个图标的统一为一类,使用createShortcut来创建
final View view;
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
ShortcutInfo info = (ShortcutInfo) item;
view = createShortcut(info);
break;
}
//createShortcut生成了图标的类型为BubbleTextView,BubbleTextView是放在单个格子中显示的内容
public View createShortcut(ViewGroup parent, ShortcutInfo info) {
BubbleTextView favorite = (BubbleTextView) getLayoutInflater().inflate(R.layout.app_icon,
parent, false);
favorite.applyFromShortcutInfo(info);
favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
favorite.setOnClickListener(this);
favorite.setOnFocusChangeListener(mFocusHandler);
return favorite;
}
//createShortcut里有4个方法:
方法一:applyFromShortcutInfo方法是设置图标的显示信息:
public void applyFromShortcutInfo(ShortcutInfo info, boolean promiseStateChanged) {
applyIconAndLabel(info.iconBitmap, info);
setTag(info);
}
//图标真正显示出来有两个内容,图片和名称。通过seticon和setText完成,而图标和名称源于应用自身。是根据数据库参数里面的packagename等参数从系统中获取的。在load和bind流程都有一个判断,如果没有packagename就终止当前load和bind,因为图标一定要有packagename等
private void applyIconAndLabel(Bitmap icon, ItemInfo info) {
FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(icon, info);
iconDrawable.setIsDisabled(info.isDisabled());
setIcon(iconDrawable);
setText(info.title);
if (info.contentDescription != null) {
setContentDescription(info.isDisabled()
? getContext().getString(R.string.disabled_app_label, info.contentDescription)
: info.contentDescription);
}
}
第二个方法setCompoundDrawablePadding是设置图标的大小,通过设置图标的周围空白大小来控制图标的大小,目的是让桌面上相邻的图标有间距。
setOnClickListener是设置图标点击,LauncherModel回调Launcher的方法,这里传入的this就是Launcher。Launcher实现onclick接口,下面是超级简化后的代码:
public void onClick(View v) {
Object tag = v.getTag();
if (tag instanceof ShortcutInfo) {
onClickAppShortcut(v);
}
}
protected void onClickAppShortcut(final View v) {
if ((v instanceof BubbleTextView) && shortcut.isPromise()) {
String packageName = shortcut.intent.getComponent() != null ?
shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
if (!TextUtils.isEmpty(packageName)) {
onClickPendingAppItem(v, packageName,
shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE));
return;
}
}
startAppShortcutOrInfoActivity(v);
}
//显示在桌面上的图标来自于info对象的icon参数,同样点击图标也可以从icon参数寻找到info对象,然后在从info对象找到intent需要的参数,比如packagename。从而使用intent打开对应的应用
第四个方法:setOnFocusChangeListener是外接键盘选择功能。被focus的图标会有灰色背景显示被选中。此外还有一定动画效果,都在focus类里。
回到binditem方法,首先判断的是图标类型,接下来是folder类型。
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
view = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item);
break;
}
//其中folder图标的生成是一个名叫fromXml的方法,该方法源码如下:
public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
FolderInfo folderInfo) {
DeviceProfile grid = launcher.getDeviceProfile();
FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
//首先传入的第一个参数resid是R.layout.folder_icon,对于res文件里面一个叫做folder_icon.xml的文件,该xml文件里面是一个布局,布局类型是folderIcon,于是最终获取到FolderIcon对象,其中FolderIcon继承至FrameLayout,所以,桌面的图标中,应用图标是textview而文件夹是FrameLayout。而后和应用一样生成名字,大小,click,focus等
icon.setClipToPadding(false);
icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
icon.mFolderName.setText(folderInfo.title);
icon.mFolderName.setCompoundDrawablePadding(0);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
icon.setTag(folderInfo);
icon.setOnClickListener(launcher);
icon.mInfo = folderInfo;
icon.mLauncher = launcher;
icon.mBadgeRenderer = launcher.getDeviceProfile().mBadgeRenderer;
icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
//和应用不同的是文件夹点击后的内容也是Launcher来绘制。Folder是打开后文件夹的类。这里需要记清楚,FolderIcon是文件夹的图标,Folder是打开时的文件夹。
Folder folder = Folder.fromXml(launcher);
folder.setDragController(launcher.getDragController());
folder.setFolderIcon(icon);
folder.bind(folderInfo);
icon.setFolder(folder);
icon.setAccessibilityDelegate(launcher.getAccessibilityDelegate());
folderInfo.addListener(icon);
icon.setOnFocusChangeListener(launcher.mFocusHandler);
return icon;
}
最后是widget类型。从绑定的分类来看,桌面上能显示的内容可以分为三类,应用,文件夹和widget
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: {
LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
if (mIsSafeModeEnabled) {
view = new PendingAppWidgetHostView(this, info, mIconCache, true);
} else {
LauncherAppWidgetProviderInfo providerInfo =
mAppWidgetManager.getLauncherAppWidgetInfo(info.appWidgetId);
if (providerInfo == null) {
deleteWidgetInfo(info);
continue;
}
view = mAppWidgetHost.createView(this, info.appWidgetId, providerInfo);
}
prepareAppWidget((AppWidgetHostView) view, info);
break;
}
//通过createView方法来创造widget的视效。其中LauncherAppWidgetHostView继承至AppWidgetHostView继承至FrameLayout。
public AppWidgetHostView createView(Context context, int appWidgetId,
LauncherAppWidgetProviderInfo appWidget) {
LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(appWidget.initialLayout, lahv);
lahv.setAppWidget(0, appWidget);
lahv.updateLastInflationOrientation();
return lahv;
}
//LauncherApps是android系统类,用以调用底层应用的信息,这里获取了对应信息,完成里面具体的绘制。
public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
updateContentDescription(info);
}
private void updateContentDescription(AppWidgetProviderInfo info) {
if (info != null) {
LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
ApplicationInfo appInfo = null;
appInfo = launcherApps.getApplicationInfo(
info.provider.getPackageName(), 0, info.getProfile());
}
}
以上binditems就是按照分类把每种类型的桌面的view一个一个的创造出来。完成了当前屏幕的绘制,而后进行其他屏幕的view绘制,同一个方法,只是传入的in西为otherWorkspaceItems和otherAppWidgets。下面回归bindWorkspace源码:
bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
//下面这个英文注释是源码中的,告诉桌面我们已经绑定完成,即调用finishBindingItems,和之前的start方法形成照应。
// Tell the workspace that we're done binding items
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.finishBindingItems();
}
mIsLoadingAndBindingWorkspace = false;
// Run all the bind complete runnables after workspace is bound.
if (!mBindCompleteRunnables.isEmpty()) {
synchronized (mBindCompleteRunnables) {
for (final Runnable r : mBindCompleteRunnables) {
runOnWorkerThread(r);
}
mBindCompleteRunnables.clear();
}
}
};
deferredExecutor.execute(r);
}
自此,完成了Launcher启动流程第6步startloader的第2小步bindworkspace。