这篇文章我们来一起分析下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已经全部加载完毕.
关于代码的更多细节,还是需要自己慢慢地认真的阅读的.由于知识水平有限,本文难免有写的不对的地方,欢迎大家阅读指出.