Launcher3数据加载

这篇文章我们来一起分析下Launcher3数据的加载流程。Launcher3的数据分为桌面数据和应用列表数据,桌面数据有shortcuts,folder,widget,而应用列表里面的就只有shortcuts.本文着重分析workspace数据的流程.

数据加载的起点是通过调用LauncherModel的startLoader方法,Launcher3的onCreate()中有如下代码:

packages\apps\Launcher3\src\com\android\launcher3\Launcher.java
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // We only load the page synchronously if the user rotates (or triggers a
        // configuration change) while launcher is in the foreground
        if (!mModel.startLoader(mWorkspace.getRestorePage())) {
            // If we are not binding synchronously, show a fade in animation when
            // the first page bind completes.
            mDragLayer.setAlpha(0);
        } else {
            setWorkspaceLoading(true);
        }
        ...
    }

startLoader中通过HandlerThread开启一个LoaderTask,我们来看下LoaderTask的run()方法

public void run() {
            ...
            // Optimize for end-user experience: if the Launcher is up and // running with the
            // All Apps interface in the foreground, load All Apps first. Otherwise, load the
            // workspace first (default).
            keep_running: {
                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                loadAndBindWorkspace();

                if (mStopped) {
                    break keep_running;
                }

                waitForIdle();

                // second step
                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                loadAndBindAllApps();

                waitForIdle();

                // third step
                if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
                loadAndBindDeepShortcuts();
            }

            ...
        }

正如上面的注解一样,数据加载分为三步:

1.loadAndBindWorkspace------此方法是用来加载桌面的,具体来说是先加载所有的桌面(不包含具体内容),然后再加载当前页的内容,最后再加载其它页的item.

2.loadAndBindAllApps------此方法是用来加载所有app列表的.

3.loadAndBindDeepShortcuts------此方法不知道是用来干啥的(若有大神告知的话,在此感谢),追踪其流程,发现在Launcher3.java最终会调用bindDeepShortcutMap方法,而此方法在高通mms8937平台7.1的代码上基本上可以说是一个空实现。

在此我们主要分析下桌面图标的加载,应用图标的加载流程类似,还请读者自行分析.

loadAndBindWorkspace()先调用loadWorkspace(),然后再调用bindWorkspace().loadWorkspace()方法实现如下:

packages\apps\Launcher3\src\com\android\launcher3\LauncherModel.java
private void loadWorkspace() {
            ...

            Log.d(TAG, "loadWorkspace: loading default favorites");
            LauncherSettings.Settings.call(contentResolver,
                    LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);

            synchronized (sBgLock) {
                clearSBgDataStructures();
                final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
                        .getInstance(mContext).updateAndGetActiveSessionCache();
                sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));

                final ArrayList<Long> itemsToRemove = new ArrayList<>();
                final ArrayList<Long> restoredRows = new ArrayList<>();
                Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
                final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
                if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
                final Cursor c = contentResolver.query(contentUri, null, null, null, null);

                ...

                    while (!mStopped && c.moveToNext()) {
                        try {
                            int itemType = c.getInt(itemTypeIndex);
                            boolean restored = 0 != c.getInt(restoredIndex);
                            boolean allowMissingTarget = false;
                            container = c.getInt(containerIndex);

                            switch (itemType) {
                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                                ...

                                    switch (container) {
                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
                                        sBgWorkspaceItems.add(info);
                                        break;
                                    default:
                                        // Item is in a user folder
                                        FolderInfo folderInfo =
                                                findOrMakeFolder(sBgFolders, container);
                                        folderInfo.add(info, false);
                                        break;
                                    }
                                    sBgItemsIdMap.put(info.id, info);
                                } else {
                                    throw new RuntimeException("Unexpected null ShortcutInfo");
                                }
                                break;

                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                               ...
                                switch (container) {
                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
                                        sBgWorkspaceItems.add(folderInfo);
                                        break;
                                }

                                if (restored) {
                                    // no special handling required for restored folders
                                    restoredRows.add(id);
                                }

                                sBgItemsIdMap.put(folderInfo.id, folderInfo);
                                sBgFolders.put(folderInfo.id, folderInfo);
                                break;

                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
                                 ...
                                    sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
                                    sBgAppWidgets.add(appWidgetInfo);
                                }
                                break;
                            }
                        } catch (Exception e) {
                            Log.e(TAG, "Desktop items loading interrupted", e);
                        }
                    }
                } finally {
                    Utilities.closeSilently(c);
                }

               ...
                // Remove any empty screens
                ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
                for (ItemInfo item: sBgItemsIdMap) {
                    long screenId = item.screenId;
                    if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                            unusedScreens.contains(screenId)) {
                        unusedScreens.remove(screenId);
                    }
                }

                // If there are any empty screens remove them, and update.
                if (unusedScreens.size() != 0) {
                    sBgWorkspaceScreens.removeAll(unusedScreens);
                    updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
                }

                ...
        }

loadWorkspace()的主要作用就是填充sBgItemsIdMap,sBgWorkspaceItems,sBgAppWidgets,sBgFolders,sBgWorkspaceScreens等数据.之后再传递给bindWorkspace()来创建workspace和每一页的item,关于其含义在类的开头定义的时候就有具体的解释说明:

static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();

// sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
//       created by LauncherModel that are directly on the home screen (however, no widgets or
//       shortcuts within folders).
static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();

// sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
    new ArrayList<LauncherAppWidgetInfo>();

// sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();

// sBgWorkspaceScreens is the ordered set of workspace screens.
static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();

值得注意的是下面这行代码,

LauncherSettings.Settings.call(contentResolver, LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);

追踪代码执行流程发现,其会调用LauncherProvider的loadDefaultFavoritesIfNecessary()方法,此方法比较简单,有很详细的注解,主要功能就是读取客制化主界面的配置文件,保存到数据库.在此贴一下其具体实现.

packages\apps\Launcher3\src\com\android\launcher3\LauncherProvider.java
   /**
     * Loads the default workspace based on the following priority scheme:
     *   1) From the app restrictions
     *   2) From a package provided by play store
     *   3) From a partner configuration APK, already in the system image
     *   4) The default configuration for the particular device
     */
    synchronized private void loadDefaultFavoritesIfNecessary() {
        SharedPreferences sp = Utilities.getPrefs(getContext());

        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
            Log.d(TAG, "loading default workspace");

            AppWidgetHost widgetHost = new AppWidgetHost(getContext(), Launcher.APPWIDGET_HOST_ID);
            AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);
            if (loader == null) {
                loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
            }
            if (loader == null) {
                final Partner partner = Partner.get(getContext().getPackageManager());
                if (partner != null && partner.hasDefaultLayout()) {
                    final Resources partnerRes = partner.getResources();
                    int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
                            "xml", partner.getPackageName());
                    if (workspaceResId != 0) {
                        loader = new DefaultLayoutParser(getContext(), widgetHost,
                                mOpenHelper, partnerRes, workspaceResId);
                    }
                }
            }

            final boolean usingExternallyProvidedLayout = loader != null;
            if (loader == null) {
                loader = getDefaultLayoutParser(widgetHost);
            }

            // There might be some partially restored DB items, due to buggy restore logic in
            // previous versions of launcher.
            createEmptyDB();
            // Populate favorites table with initial favorites
            if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
                    && usingExternallyProvidedLayout) {
                // Unable to load external layout. Cleanup and load the internal layout.
                createEmptyDB();
                mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
                        getDefaultLayoutParser(widgetHost));
            }
            clearFlagEmptyDbCreated();
        }
    }

所以当我们有客制化主界面的需求时,我们就应该修改partner_default_layout.xml(这个是谷歌GmsSampleLayout.apk里的文件)或者default_workspace_*x*.xml文件.

loadworkspace就讲解到这里,方法比较长,需要自己慢慢阅读.有了数据了我们就来看看它是怎么显示出来的,bindworkspace()实现如下:

packages\apps\Launcher3\src\com\android\launcher3\LauncherModel.java
        /**
         * Binds all loaded data to actual views on the main thread.
         */
        private void bindWorkspace(int synchronizeBindPage) {
            ...

            // Save a copy of all the bg-thread collections
            ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
            ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
            ArrayList<Long> orderedScreenIds = new ArrayList<>();

            synchronized (sBgLock) {
                workspaceItems.addAll(sBgWorkspaceItems);
                appWidgets.addAll(sBgAppWidgets);
                orderedScreenIds.addAll(sBgWorkspaceScreens);
            }

            final int currentScreen;
            {
                int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE
                        ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen();
                if (currScreen >= orderedScreenIds.size()) {
                    // There may be no workspace screens (just hotseat items and an empty page).
                    currScreen = PagedView.INVALID_RESTORE_PAGE;
                }
                currentScreen = currScreen;
            }
            final boolean validFirstPage = currentScreen >= 0;
            final long currentScreenId =
                    validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
            // Separate the items that are on the current screen, and all the other remaining items
            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
            ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
            ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();

            filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
                    otherWorkspaceItems);
            filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
                    otherAppWidgets);
            sortWorkspaceItemsSpatially(currentWorkspaceItems);
            sortWorkspaceItemsSpatially(otherWorkspaceItems);

            // Tell the workspace that we're about to start binding items
            r = new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.clearPendingBinds();
                        callbacks.startBinding();
                    }
                }
            };
            runOnMainThread(r);

            bindWorkspaceScreens(oldCallbacks, orderedScreenIds);

            Executor mainExecutor = new DeferredMainThreadExecutor();
            // Load items on the current page.
            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);

            ...

            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
        ...
        }

方法也是比较好理解,一开始拷贝一份数据,把当前页和其它页的数据进行分离.再调用bindWorkspaceScreens创建所有页面,然后分两次调用bindWorkspaceItems()先显示当前页的数据,再加载其它页的数据.

bindWorkspaceScreens通过回调Launcher3的bindScreens()在主线循环创建所有页面,其最终会调用Workspace的insertNewWorkspaceScreen()方法,我们来看一下它的具体实现.

packages\apps\SnapdragonLauncher\src\com\android\launcher3\Workspace.java
    public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
        if (mWorkspaceScreens.containsKey(screenId)) {
            throw new RuntimeException("Screen id " + screenId + " already exists!");
        }

        // Inflate the cell layout, but do not add it automatically so that we can get the newly
        // created CellLayout.
        CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate(
                        R.layout.workspace_screen, this, false /* attachToRoot */);

        newScreen.setOnLongClickListener(mLongClickListener);
        newScreen.setOnClickListener(mLauncher);
        newScreen.setSoundEffectsEnabled(false);
        mWorkspaceScreens.put(screenId, newScreen);
        mScreenOrder.add(insertIndex, screenId);
        addView(newScreen, insertIndex);

        LauncherAccessibilityDelegate delegate =
                LauncherAppState.getInstance().getAccessibilityDelegate();
        if (delegate != null && delegate.isInAccessibleDrag()) {
            newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
        }
        return screenId;
    }

方法中新建一个CellLayout对像,然后通过调用addView方法显示界面,Workspace继承于PagedView,所以其具体滑动的特性.至此页面就创建好了,继续调用bindWorkspaceItems来填充应用和文件夹图标,bindWorkspaceItems会回调用Launcher.java的bindItems()和bindAppWidget()方法来显示页面的数据.我们分别来看下bindItems()具体的实现,bindAppWidget()还请读者自行分析.

packages\apps\Launcher3\src\com\android\launcher3\Launcher.java
    /**
     * Bind the items start-end from the list.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    @Override
    public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
                          final boolean forceAnimateIcons) {
        Runnable r = new Runnable() {
            public void run() {
                bindItems(shortcuts, start, end, forceAnimateIcons);
            }
        };
        if (waitUntilResume(r)) {
            return;
        }
        ...

            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;
                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                    view = FolderIcon.fromXml(R.layout.folder_icon, this,
                            (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                            (FolderInfo) item, mIconCache);
                    break;
                default:
                    throw new RuntimeException("Invalid Item Type");
            }

            ...
            workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
                    item.cellY, 1, 1);
            ...
        }

        //加载图标的动画,读者可以自己去分析
        ...
        workspace.requestLayout();
    }
方法的一开始就判断是否在resume状态,如果不是就等待.之后根据不同类型创建了一个view,并调用Workspace的addInScreenFromBind()方法来把此view加入到CellLayout里面.方法最终会调用Workspace的addInScreen()方法:
packages\apps\Launcher3\src\com\android\launcher3\Workspace.java
    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
            boolean insert, boolean computeXYFromRank) {
        ...
        final CellLayout layout;
            // Show folder title if not in the hotseat
        if (child instanceof FolderIcon) {
            ((FolderIcon) child).setTextVisible(true);
        }
        layout = getScreenWithId(screenId);

        ViewGroup.LayoutParams genericLp = child.getLayoutParams();
        CellLayout.LayoutParams lp;
        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
        } else {
            lp = (CellLayout.LayoutParams) genericLp;
            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
        ItemInfo info = (ItemInfo) child.getTag();
        int childId = mLauncher.getViewIdForItem(info);

        boolean markCellsAsOccupied = !(child instanceof Folder);
        if ((layout != null) && !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?
            Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout", true);
        }
        ...
    }

方法一开始根据screenId获取某一页具体的CellLayout,之后准备好CellLayout.LayoutParams,最后调用CellLayout的addViewToCelllayout添加到父容器里面.完成之后会调用Workspace的requestLayout方法刷新UI.至此Workspace的数据加载流程就分析完了,但是请问Hotseat的图标和Folder里面的应用图标是怎么加载的呢?在bindItems加入log打印出itemInfo的信息,重新编译,会有类似以下log打印:

2019-04-12 06:53:08.990 23239-23239/com.android.launcher3 I/Tim_L: iteminfo=ShortcutInfo(id=1 type=0 container=-101 screen=0 cellX=0 cellY=0 spanX=1 spanY=1 minSpanX=1 minSpanY=1 rank=0 user=UserHandle{0} title=电话)
2019-04-12 06:53:08.995 23239-23239/com.android.launcher3 I/Tim_L: iteminfo=ShortcutInfo(id=2 type=0 container=-101 screen=1 cellX=1 cellY=0 spanX=1 spanY=1 minSpanX=1 minSpanY=1 rank=0 user=UserHandle{0} title=短信)
2019-04-12 06:53:08.997 23239-23239/com.android.launcher3 I/Tim_L: iteminfo=ShortcutInfo(id=3 type=0 container=-101 screen=3 cellX=3 cellY=0 spanX=1 spanY=1 minSpanX=1 minSpanY=1 rank=0 user=UserHandle{0} title=Chrome)
2019-04-12 06:53:08.998 23239-23239/com.android.launcher3 I/Tim_L: iteminfo=ShortcutInfo(id=4 type=0 container=-101 screen=4 cellX=4 cellY=0 spanX=1 spanY=1 minSpanX=1 minSpanY=1 rank=0 user=UserHandle{0} title=相机)
2019-04-12 06:53:09.000 23239-23239/com.android.launcher3 I/Tim_L: iteminfo=ShortcutInfo(id=5 type=0 container=-100 screen=0 cellX=0 cellY=3 spanX=1 spanY=1 minSpanX=1 minSpanY=1 rank=0 user=UserHandle{0} title=电子邮件)
2019-04-12 06:53:09.003 23239-23239/com.android.launcher3 I/Tim_L: iteminfo=ShortcutInfo(id=6 type=0 container=-100 screen=0 cellX=1 cellY=3 spanX=1 spanY=1 minSpanX=1 minSpanY=1 rank=0 user=UserHandle{0} title=图库)
2019-04-12 06:53:09.355 23239-23239/com.android.launcher3 I/Tim_L: iteminfo=FolderInfo(id=10 type=2 container=-100 screen=1 cellX=2 cellY=2 spanX=1 spanY=1 minSpanX=1 minSpanY=1 rank=0 user=UserHandle{0} title=)
2019-04-12 06:53:09.373 23239-23239/com.android.launcher3 I/Tim_L: iteminfo=FolderInfo(id=12 type=2 container=-100 screen=2 cellX=2 cellY=2 spanX=1 spanY=1 minSpanX=1 minSpanY=1 rank=0 user=UserHandle{0} title=)
2019-04-12 06:53:09.380 23239-23239/com.android.launcher3 I/Tim_L: iteminfo=ShortcutInfo(id=9 type=0 container=-100 screen=3 cellX=3 cellY=2 spanX=1 spanY=1 minSpanX=1 minSpanY=1 rank=0 user=UserHandle{0} title=设置)

其中container=-101的iteminfo表示的就是hotseat上面的shortcut.所以hotseat上面的图标也是走的上面的流程.接下来咱们再来看看Folder里面的图标是怎么加载的.

在loadWorkspace()方法中有如下代码:

packages\apps\Launcher3\src\com\android\launcher3\LauncherModel.java---loadWorkspace()
switch (container) {
	case LauncherSettings.Favorites.CONTAINER_DESKTOP:
	case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
		sBgWorkspaceItems.add(info);
		break;
	default:
		// Item is in a user folder
		FolderInfo folderInfo =
				findOrMakeFolder(sBgFolders, container);
		folderInfo.add(info, false);
		break;
}

当shortcut的container不是CONTAINER_DESKTOP和CONTAINER_HOTSEAT时(具体的值是一个表示文件夹id的值),那它就表示在某一个文件夹里面.那么就把它加入到folderInfo里面,继而传递给Launcher.java的bindItems()方法处理.此方法对于文件夹则调用FolderIcon的fromXml进行处理.来看下代码:

packages\apps\Launcher3\src\com\android\launcher3\folder\FolderIcon.java
 public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
            FolderInfo folderInfo, IconCache iconCache) {
       ...
        DeviceProfile grid = launcher.getDeviceProfile();
        FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);

        // For performance and compatibility reasons we render the preview using a software layer.
        // In particular, hardware path clipping has spotty ecosystem support and bad performance.
        // Software rendering also allows us to use shadow layers.
        icon.setLayerType(LAYER_TYPE_SOFTWARE, new Paint(Paint.FILTER_BITMAP_FLAG));

        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.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
        Folder folder = Folder.fromXml(launcher);
        folder.setDragController(launcher.getDragController());
        folder.setFolderIcon(icon);
        folder.bind(folderInfo);//绑定folder里面的shortcuts
        icon.setFolder(folder); //里面有绘制folder图标的逻辑
        icon.setAccessibilityDelegate(launcher.getAccessibilityDelegate());

        folderInfo.addListener(icon);

        icon.setOnFocusChangeListener(launcher.mFocusHandler);
        return icon;
    }

代码先创建一个FolderIcon和Folder,然后调用folder的bind()方法来加载数据,数据加载完后再更新下图标的绘制,图标的绘制以后再分析,在此我们只关心数据的加载过程,bind()的最终调用FolderPagedView的bindItems()方法

packages\apps\Launcher3\src\com\android\launcher3\folder\FolderPagedView.java
    /**
     * Binds items to the layout.
     * @return list of items that could not be bound, probably because we hit the max size limit.
     */
    public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
        ArrayList<View> icons = new ArrayList<View>();
        ArrayList<ShortcutInfo> extra = new ArrayList<ShortcutInfo>();

        for (ShortcutInfo item : items) {
            if (!ALLOW_FOLDER_SCROLL && icons.size() >= mMaxItemsPerPage) {
                extra.add(item);
            } else {
                icons.add(createNewView(item));
            }
        }
        arrangeChildren(icons, icons.size(), false);
        return extra;
    }

方法中先调用createNewView()创建所有图标(BubbleTextView)再调用arrangeChildren()把这些图标进行布局.


    @SuppressLint("RtlHardcoded")
    private void arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges) {
        ...
        for (int i = 0; i < itemCount; i++) {
            View v = list.size() > i ? list.get(i) : null;
            if (currentPage == null || position >= mMaxItemsPerPage) {
                // Next page
                if (pageItr.hasNext()) {
                    currentPage = pageItr.next();
                } else {
                    currentPage = createAndAddNewPage();
                }
                position = 0;
            }

            if (v != null) {
               ....
                currentPage.addViewToCellLayout(
                        v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);

                if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) {
                    ((BubbleTextView) v).verifyHighRes();
                }
            }
        ...
    }

代码中先创建当前页,然后再把view加入到CellLayout中,到此folder已经全部加载完毕.

关于代码的更多细节,还是需要自己慢慢地认真的阅读的.由于知识水平有限,本文难免有写的不对的地方,欢迎大家阅读指出.

猜你喜欢

转载自blog.csdn.net/lmpt90/article/details/86015611