【源码剖析】Launcher 8.0 源码 (20) --- Launcher 启动流程 第六步之BindWorkspace 第2小步添加图标

Launcher8.0启动流程的第六步startLoader的bindWorkspace将sBgDataModel中的图标放到桌面上。 放置的时候为了提高用户体验,优先放置当前屏幕的图标和widget,然后再放其他屏幕的图标,这样用户能更快的看到图标显示完成。

 

在创建完屏幕后,添加桌面的图标进入bindWorkspaceItems()方法


//绑定图标是回调Launcher的对应方法,而绑定时按照不同item类型进行不同的绘制。

public void bindItems(final ArrayList items, final int start, final int end,
                      final boolean forceAnimateIcons) {

final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
    final Collection bounceAnims = new ArrayList();
    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。

你可能感兴趣的:(源码剖析)