在Launcher的onCreate过程中会加载数据,我们将这个过程单独拎出来,startLoader在LauncherModel中实现,
代码位置:launcher3\src\main\java\com\android\launcher3\LauncherModel.java
public void startLoader(boolean isLaunching, int synchronousBindPage) {
startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE);
}
public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) {
synchronized (mLock) {// 将该代码块锁住,同时只能有一个线程执行它
if (DEBUG_LOADERS) {
Log.d(TAG, "startLoader isLaunching=" + isLaunching);
}
// Clear any deferred bind-runnables from the synchronized load process
// We must do this before any loading/binding is scheduled below.
// 清除延迟执行的绑定app的线程
synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.clear();
}
// Don't bother to start the thread if we know it's not going to do anything
if (mCallbacks != null && mCallbacks.get() != null) {// Callbacks在Launcher中实现
// 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(mApp.getContext(), isLaunching, loadFlags);
if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
&& mAllAppsLoaded && mWorkspaceLoaded) {// launcher不在前台运行 && 所有app已经加载 && workspace已经加载
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
} else {
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
sWorker.post(mLoaderTask);
}
}
}
}
执行到mLoaderTask执行块,直接看到LoaderTask的run方法,
public void run() {
boolean isUpgrade = false;
synchronized (mLock) {
mIsLoaderTaskRunning = true;
}
// 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: {
// 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);
}
// 分两步执行:1.loading workspace 2.loading workspace
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
isUpgrade = loadAndBindWorkspace();
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();// loading all apps之前等待
// second step
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
loadAndBindAllApps();
// Restore the default thread priority after we are done loading items
synchronized (mLock) {
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
}
}
// Update the saved icons if necessary
if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
synchronized (sBgLock) {
for (Object key : sBgDbIconCache.keySet()) {
updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
}
sBgDbIconCache.clear();
}
if (LauncherAppState.isDisableAllApps()) {
// Ensure that all the applications that are in the system are
// represented on the home screen.
if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) {
verifyApplications();
}
}
// Clear out this reference, otherwise we end up holding it until all of the
// callback runnables are done.
mContext = null;
synchronized (mLock) {
// If we are still the last one to be scheduled, remove ourselves.
if (mLoaderTask == this) {
mLoaderTask = null;
}
mIsLoaderTaskRunning = false;
}
}
分两大步执行:
第一步:加载和绑定workspace--loadAndBindWorkspace
/** Returns whether this is an upgrade path */
private boolean loadAndBindWorkspace() {
mIsLoadingAndBindingWorkspace = true;
// Load the workspace
if (DEBUG_LOADERS) {
Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
}
boolean isUpgradePath = false;
if (!mWorkspaceLoaded) {
isUpgradePath = loadWorkspace();
synchronized (LoaderTask.this) {
if (mStopped) {
return isUpgradePath;
}
mWorkspaceLoaded = true;
}
}
// Bind the workspace
bindWorkspace(-1, isUpgradePath);
return isUpgradePath;
}
并没有直接进行加载,只是对一些状态进行了更新和条件判断,loadWorkspace和bindWorkspace才是实际操作。
1.loadWorkspace(加载Workspace上要显示的数据)
loadWorkspace方法非常长,代码就不全部贴出了,但是执行步骤还是非常清晰的。
1)初始化后面要用到的对象实例
final Context context = mContext;
final ContentResolver contentResolver = context.getContentResolver();// 用来保存加载数据后的一些信息
final PackageManager manager = context.getPackageManager();// 初始化PackageManager
final AppWidgetManager widgets = AppWidgetManager.getInstance(context);// 初始化AppWidgetManager
final boolean isSafeMode = manager.isSafeMode();// 是否安全模式启动
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
final boolean isSdCardReady = context.registerReceiver(null,
new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;// SdCard是否就绪
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
int countX = (int) grid.numColumns;// workspace列数
int countY = (int) grid.numRows;// workspace行数
2)加载默认配置
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
// 加载默认的配置,保存到数据库中
synchronized public void loadDefaultFavoritesIfNecessary() {
String spKey = LauncherAppState.getSharedPreferencesKey();
SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
// 判断数据库是否未创建,
if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
Log.d(TAG, "loading default workspace");
AutoInstallsLayout loader = AutoInstallsLayout.get(getContext(),
mOpenHelper.mAppWidgetHost, 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(), mOpenHelper.mAppWidgetHost,
mOpenHelper, partnerRes, workspaceResId);
}
}
}
final boolean usingExternallyProvidedLayout = loader != null;
if (loader == null) {
loader = getDefaultLayoutParser();
}
// 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());
}
clearFlagEmptyDbCreated();
}
}
如果数据库还没有创建,就会加载默认的配置(default_workspace_xxx.xml),并保存到数据库中。
3)读取数据库,获取需要加载的应用快捷方式和AppWidget
整个读取的过程是在一个同步代码块中,在此之前我们先看几个重要的全局变量,
sBgWorkspaceItems--保存ItemInfo
sBgAppWidgets--保存AppWidget
sBgFolders--存放文件夹
sBgItemsIdMap--保存ItemInfo和其Id
sBgDbIconCache--应用图标
sBgWorkspaceScreens--保存Workspace
a)遍历cursor,读取每一个app信息,根据itemType不同类型,分类保存到刚才的几个变量中。分这几种类型:ITEM_TYPE_APPLICATION、ITEM_TYPE_SHORTCUT、ITEM_TYPE_SHORTCUT、ITEM_TYPE_APPWIDGET
b)读取完数据库之后,将需要移除和更新的item进行移除和更新;
c)读取workspace screen数据库信息,如果有未使用过的则将其从数据库中移除。
2.bindWorkspace
应用信息读取完之后,刚才的几个变量中就存储了该信息,然后将其绑定到workspace中去,这个过程也是很复杂的,我们一步一步来看。
1)不直接使用上面提到的几个全局变量,重新定义局部变量来处理
// Save a copy of all the bg-thread collections
// 不直接操作全局变量,将其赋值给局部变量
ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<LauncherAppWidgetInfo>();
HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
synchronized (sBgLock) {
workspaceItems.addAll(sBgWorkspaceItems);
appWidgets.addAll(sBgAppWidgets);
folders.putAll(sBgFolders);
itemsIdMap.putAll(sBgItemsIdMap);
orderedScreenIds.addAll(sBgWorkspaceScreens);
}
final boolean isLoadingSynchronously = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
int currScreen = isLoadingSynchronously ? 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;
}
final int currentScreen = currScreen;// 当前screen
final long currentScreenId = currentScreen < 0 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);// 当前screen id
// Load all the items that are on the current page first (and in the process, unbind
// all the existing workspace items before we call startBinding() below.
unbindWorkspaceItemsOnMainThread();// 先解除绑定
2)根据item中的screenID将items分成当前screen和其他screen,并进行排序
// Separate the items that are on the current screen, and all the other remaining items
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();// 存放当前workspace上的items
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();// 存放除当前workspace之外的items
ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<LauncherAppWidgetInfo>();// 存放当前workspace上的appwidgets
ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<LauncherAppWidgetInfo>();// 存放除当前workspace之外的appwidgets
HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();// 存放当前workspace上的folder
HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();// 存放除当前workspace之外的folder
filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, otherWorkspaceItems);// 过滤items,区分当前screen和其他screen上的items
filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, otherAppWidgets);// 同上
filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders, otherFolders);// 同上
sortWorkspaceItemsSpatially(currentWorkspaceItems);// 对workspace上的items进行排序,按照从上到下和从左到右的顺序
sortWorkspaceItemsSpatially(otherWorkspaceItems);// 同上
3)runnable执行块,告诉workspace要开始绑定items了,startBinding方法在Launcher中实现,做一些清除工作
// 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.startBinding();
}
}
};
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
代码位置:launcher3\src\main\java\com\android\launcher3\Launcher.java
/**
* Refreshes the shortcuts shown on the workspace.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void startBinding() {
setWorkspaceLoading(true);
// If we're starting binding all over again, clear any bind calls we'd postponed in
// the past (see waitUntilResume) -- we don't need them since we're starting binding
// from scratch again
mBindOnResumeCallbacks.clear();
// Clear the workspace because it's going to be rebound
mWorkspace.clearDropTargets();
mWorkspace.removeAllWorkspaceScreens();
mWidgetsToAdvance.clear();
if (mHotseat != null) {
mHotseat.resetLayout();
}
}
4)绑定workspace screen
bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
private void bindWorkspaceScreens(final Callbacks oldCallbacks,
final ArrayList<Long> orderedScreens) {
final Runnable r = new Runnable() {
@Override
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindScreens(orderedScreens);
}
}
};
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
有两个参数,一个是Callback对象,回调方法都在Launcher中实现,另一个是已经排序好的screen id,
代码位置:launcher3\src\main\java\com\android\launcher3\Launcher.java
@Override
public void bindScreens(ArrayList<Long> orderedScreenIds) {
bindAddScreens(orderedScreenIds);
// If there are no screens, we need to have an empty screen
// 如果没有需要添加screen,那我们就添加一个空白的screen
if (orderedScreenIds.size() == 0) {
mWorkspace.addExtraEmptyScreen();
}
// Create the custom content page (this call updates mDefaultScreen which calls
// setCurrentPage() so ensure that all pages are added before calling this).
if (hasCustomContentToLeft()) {
mWorkspace.createCustomContentContainer();
populateCustomContentContainer();
}
}
@Override
public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
// Log to disk
Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
TextUtils.join(", ", orderedScreenIds), true);
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
}
}
通过for循环遍历,分别插入一个新的workspace screen,该方法在Workspace中实现,
代码位置:launcher3\src\main\java\com\android\launcher3\Workspace.java
public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
// Find the index to insert this view into. If the empty screen exists, then
// insert it before that.
int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
if (insertIndex < 0) {
insertIndex = mScreenOrder.size();
}
return insertNewWorkspaceScreen(screenId, insertIndex);
}
// 插入一个新的workspace screen
public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
// Log to disk
Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId + " at index: " + insertIndex, true);
if (mWorkspaceScreens.containsKey(screenId)) {
throw new RuntimeException("Screen id " + screenId + " already exists!");
}
CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
newScreen.setOnLongClickListener(mLongClickListener);
newScreen.setOnClickListener(mLauncher);
newScreen.setSoundEffectsEnabled(false);
mWorkspaceScreens.put(screenId, newScreen);
mScreenOrder.add(insertIndex, screenId);
addView(newScreen, insertIndex);
return screenId;
}
其实就是创建一个CellLayout,然后添加到Workspace中。
5)Workspace绑定完成之后,就是将items、widgets和folders放到上面去
// Load items on the current page
bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, currentFolders, null);
if (isLoadingSynchronously) {
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
callbacks.onPageBoundSynchronously(currentScreen);
}
}
};
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
// Load all the remaining pages (if we are loading synchronously, we want to defer this
// work until after the first render)
synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.clear();
}
bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
(isLoadingSynchronously ? mDeferredBindRunnables : null));
当前页和其它页分别加载,都是调运bindWorkspaceItems来实现的,看一下该方法的实现过程,
a)批量加载itmes
// Bind the workspace items
int N = workspaceItems.size();
for (int i = 0; i < N; i += ITEMS_CHUNK) {
final int start = i;
final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);// 批量绑定,批量大小为ITEMS_CHUNK,如果一共少于ITEMS_CHUNK,那就一次全部绑定
final Runnable r = new Runnable() {
@Override
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindItems(workspaceItems, start, start+chunkSize,
false);
}
}
};
if (postOnMainThread) {
synchronized (deferredBindRunnables) {
deferredBindRunnables.add(r);
}
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
}
回调到Launcher的bindItems方法,
/**
* Bind the items start-end from the list.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
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)) {// 当Launcher处于pause状态时,不进行绑定,待resume时再执行
return;
}
// Get the list of added shortcuts and intersect them with the set of shortcuts here
final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
final Collection<Animator> bounceAnims = new ArrayList<Animator>();
final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
Workspace workspace = mWorkspace;
long newShortcutsScreenId = -1;
for (int i = start; i < end; i++) {
final ItemInfo item = shortcuts.get(i);
// Short circuit if we are loading dock items for a configuration which has no dock
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
mHotseat == null) {// 如果绑定的item要放置在Hotseat,但是又没有配置Hotseat,直接跳过
continue;
}
// 根据item的类型,区分绑定
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:// 快捷图标
ShortcutInfo info = (ShortcutInfo) item;
View shortcut = createShortcut(info);// 创建快捷图标视图
/*
* TODO: FIX collision case
*/
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {// 判断item将要放置的位置是否被占据了
View v = cl.getChildAt(item.cellX, item.cellY);
Object tag = v.getTag();
String desc = "Collision while binding workspace item: " + item + ". Collides with " + tag;
if (LauncherAppState.isDogfoodBuild()) {
throw (new RuntimeException(desc));
} else {
Log.d(TAG, desc);
}
}
}
workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
item.cellY, 1, 1);// 将快捷图标添加到Workspace screen的指定位置,占据一格
if (animateIcons) {
// Animate all the applications up now
shortcut.setAlpha(0f);
shortcut.setScaleX(0f);
shortcut.setScaleY(0f);
bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
newShortcutsScreenId = item.screenId;
}
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:// 文件夹
FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item, mIconCache);// 创建文件夹图标
workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
item.cellY, 1, 1);
break;
default:
throw new RuntimeException("Invalid Item Type");
}
}
if (animateIcons) {
// Animate to the correct page
if (newShortcutsScreenId > -1) {
long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
final Runnable startBounceAnimRunnable = new Runnable() {
public void run() {
anim.playTogether(bounceAnims);
anim.start();
}
};
if (newShortcutsScreenId != currentScreenId) {
// We post the animation slightly delayed to prevent slowdowns
// when we are loading right after we return to launcher.
mWorkspace.postDelayed(new Runnable() {
public void run() {
if (mWorkspace != null) {
mWorkspace.snapToPage(newScreenIndex);
mWorkspace.postDelayed(startBounceAnimRunnable,
NEW_APPS_ANIMATION_DELAY);
}
}
}, NEW_APPS_PAGE_MOVE_DELAY);
} else {
mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
}
}
}
workspace.requestLayout();
}
根据item的类型,分别加载,首先获取item信息,创建快捷图标,然后将快捷图标放置到指定位置addInScreenFromBind,
// At bind time, we use the rank (screenId) to compute x and y for hotseat items.
// See implementation for parameter definition.
void addInScreenFromBind(View child, long container, long screenId, int x, int y,
int spanX, int spanY) {
addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
}
/**
* Adds the specified child in the specified screen. The position and dimension of
* the child are defined by x, y, spanX and spanY.
*
* @param child The child to add in one of the workspace's screens.
* @param screenId The screen in which to add the child.
* @param x The X position of the child in the screen's grid.
* @param y The Y position of the child in the screen's grid.
* @param spanX The number of cells spanned horizontally by the child.
* @param spanY The number of cells spanned vertically by the child.
* @param insert When true, the child is inserted at the beginning of the children list.
* @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
* the x and y position in which to place hotseat items. Otherwise
* we use the x and y position to compute the rank.
*/
void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
boolean insert, boolean computeXYFromRank) {
if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (getScreenWithId(screenId) == null) {
Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
// DEBUGGING - Print out the stack trace to see where we are adding from
new Throwable().printStackTrace();
return;
}
}
if (screenId == EXTRA_EMPTY_SCREEN_ID) {
// This should never happen
throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
}
final CellLayout layout;// 先要获取快捷图标的父视图
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
layout = mLauncher.getHotseat().getLayout();
child.setOnKeyListener(new HotseatIconKeyEventListener());
// Hide folder title in the hotseat
if (child instanceof FolderIcon) {
((FolderIcon) child).setTextVisible(false);
}
if (computeXYFromRank) {
x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
} else {
screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
}
} else {
// Show folder title if not in the hotseat
if (child instanceof FolderIcon) {
((FolderIcon) child).setTextVisible(true);
}
layout = getScreenWithId(screenId);
child.setOnKeyListener(new IconKeyEventListener());
}
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);// 添加快捷图标的时候,是否需要标记所占据的位置,除了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?
Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout", true);
}
if (!(child instanceof Folder)) {
child.setHapticFeedbackEnabled(false);
child.setOnLongClickListener(mLongClickListener);
}
if (child instanceof DropTarget) {
mDragController.addDropTarget((DropTarget) child);// 添加拖拽对象
}
}
先获取快捷图标的父视图,分Hotseat和Desktop;设置布局参数,确定快捷图标放置的位置;父视图将快捷图标添加到指定位置,
代码位置:launcher3\src\main\java\com\android\launcher3\CellLayout.java
public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
boolean markCells) {
final LayoutParams lp = params;
// Hotseat icons - remove text
if (child instanceof BubbleTextView) {// 如果是在Hotseat上的图标,将文字去除
BubbleTextView bubbleChild = (BubbleTextView) child;
bubbleChild.setTextVisibility(!mIsHotseat);
}
child.setScaleX(getChildrenScale());
child.setScaleY(getChildrenScale());
// 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);
mShortcutsAndWidgets.addView(child, index, lp);// 最终放入指定位置
if (markCells) markCellsAsOccupiedForView(child);// 标记所占据的位置,除了folder外,都需要标记
return true;
}
return false;
}
最后设置触摸反馈和长安监听以及拖拽对象的添加。
b)加载folder
// Bind the folders
if (!folders.isEmpty()) {
final Runnable r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindFolders(folders);
}
}
};
if (postOnMainThread) {
synchronized (deferredBindRunnables) {
deferredBindRunnables.add(r);
}
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
}
也是回调到Launcher的bindFolders方法.
/**
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindFolders(final HashMap<Long, FolderInfo> folders) {
Runnable r = new Runnable() {
public void run() {
bindFolders(folders);
}
};
if (waitUntilResume(r)) {
return;
}
sFolders.clear();
sFolders.putAll(folders);
}
这样就结束了,将folders添加到sFolders的HaspMap中。可能有点奇怪,怎么没有像绑定Workspace items那样将其添加到父视图中?因为之前的过程已经添加过了,对于folder而言,它的快捷图标也是保存在workspaceItems中的,这里绑定folders只是获取folders的信息,用于对文件夹的操作,并不需要将其添加到父视图中。
c)加载widget
// Bind the widgets, one at a time
N = appWidgets.size();
for (int i = 0; i < N; i++) {
final LauncherAppWidgetInfo widget = appWidgets.get(i);
final Runnable r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindAppWidget(widget);
}
}
};
if (postOnMainThread) {
deferredBindRunnables.add(r);
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
}
回调到Launcher的bindAppWidget方法中去了,
/**
* Add the views for a widget to the workspace.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAppWidget(final LauncherAppWidgetInfo item) {
Runnable r = new Runnable() {
public void run() {
bindAppWidget(item);
}
};
if (waitUntilResume(r)) {
return;
}
final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
if (DEBUG_WIDGETS) {
Log.d(TAG, "bindAppWidget: " + item);
}
final Workspace workspace = mWorkspace;
AppWidgetProviderInfo appWidgetInfo;
if (!mIsSafeModeEnabled
&& ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
&& ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
appWidgetInfo = mModel.findAppWidgetProviderInfoWithComponent(this, item.providerName);// 获取AppWidgetProviderInfo
if (appWidgetInfo == null) {
if (DEBUG_WIDGETS) {
Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+ " belongs to component " + item.providerName
+ ", as the povider is null");
}
LauncherModel.deleteItemFromDatabase(this, item);// 如果系统中找不到该AppWidget,将其从数据库中删除
return;
}
// Note: This assumes that the id remap broadcast is received before this step.
// If that is not the case, the id remap will be ignored and user may see the
// click to setup view.
PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null, null);// 创建PendingAddWidgetInfo实例
pendingInfo.spanX = item.spanX;
pendingInfo.spanY = item.spanY;
pendingInfo.minSpanX = item.minSpanX;
pendingInfo.minSpanY = item.minSpanY;
Bundle options = AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);// 获取appwidget最大最小宽度、高度
int newWidgetId = mAppWidgetHost.allocateAppWidgetId();// 给AppWidget分配一个id
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId, appWidgetInfo, options);// 用AppWidgetManager来绑定AppWidget
// TODO consider showing a permission dialog when the widget is clicked.
if (!success) {// 如果绑定失败了,删除刚刚分配的id,并将其从数据库中移除,不在继续执行
mAppWidgetHost.deleteAppWidgetId(newWidgetId);
if (DEBUG_WIDGETS) {
Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+ " belongs to component " + item.providerName
+ ", as the launcher is unable to bing a new widget id");
}
LauncherModel.deleteItemFromDatabase(this, item);
return;
}
item.appWidgetId = newWidgetId;
// If the widget has a configure activity, it is still needs to set it up, otherwise
// the widget is ready to go.
item.restoreStatus = (appWidgetInfo.configure == null)
? LauncherAppWidgetInfo.RESTORE_COMPLETED
: LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
LauncherModel.updateItemInDatabase(this, item);// 更新AppWidget在数据库中的信息
}
if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
final int appWidgetId = item.appWidgetId;
appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
if (DEBUG_WIDGETS) {
Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider);
}
item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);// 创建AppWidget视图
} else {
appWidgetInfo = null;
PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
mIsSafeModeEnabled);
view.updateIcon(mIconCache);
item.hostView = view;
item.hostView.updateAppWidget(null);
item.hostView.setOnClickListener(this);
}
item.hostView.setTag(item);
item.onBindAppWidget(this);
workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
item.cellY, item.spanX, item.spanY, false);// 将AppWidget添加到Workspace的指定位置
addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
workspace.requestLayout();
if (DEBUG_WIDGETS) {
Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
+ (SystemClock.uptimeMillis()-start) + "ms");
}
}
其做法跟添加items类似的,就不在赘述了。
最后通知items的绑定完成,
// Tell the workspace that we're done binding items
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.finishBindingItems(isUpgradePath);
}
// If we're profiling, ensure this is the last thing in the queue.
if (DEBUG_LOADERS) {
Log.d(TAG, "bound workspace in "
+ (SystemClock.uptimeMillis()-t) + "ms");
}
mIsLoadingAndBindingWorkspace = false;
}
};
if (isLoadingSynchronously) {
synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.add(r);
}
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
/**
* Callback saying that there aren't any more items to bind.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void finishBindingItems(final boolean upgradePath) {
Runnable r = new Runnable() {
public void run() {
finishBindingItems(upgradePath);
}
};
if (waitUntilResume(r)) {
return;
}
if (mSavedState != null) {
if (!mWorkspace.hasFocus()) {
mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
}
mSavedState = null;
}
mWorkspace.restoreInstanceStateForRemainingPages();
setWorkspaceLoading(false); // workspace已经加载结束了
sendLoadingCompleteBroadcastIfNecessary();// 第一次加载完成时会发送广播
// If we received the result of any pending adds while the loader was running (e.g. the
// widget configuration forced an orientation change), process them now.
if (sPendingAddItem != null) {
final long screenId = completeAdd(sPendingAddItem);
// TODO: this moves the user to the page where the pending item was added. Ideally,
// the screen would be guaranteed to exist after bind, and the page would be set through
// the workspace restore process.
mWorkspace.post(new Runnable() {
@Override
public void run() {
mWorkspace.snapToScreenId(screenId);// // 滑动指定的screenId的screen上
}
});
sPendingAddItem = null;
}
if (upgradePath) {
mWorkspace.getUniqueComponents(true, null);
mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
}
PackageInstallerCompat.getInstance(this).onFinishBind();
if (mLauncherCallbacks != null) {
mLauncherCallbacks.finishBindingItems(upgradePath);
}
}
这样整个loadAndBindWorkspace过程就结束了,接着下一步。
第二步:loadAndBindAllApps
private void loadAndBindAllApps() {
if (DEBUG_LOADERS) {
Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
}
if (!mAllAppsLoaded) {// 如果还没有加载,就加载所有APP,否则是需要绑定就行了
loadAllApps();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
mAllAppsLoaded = true;
}
} else {
onlyBindAllApps();
}
}
分了两种情况,如果所有app已经加载过了,就只需要绑定就行了,否则的话,加载所有app,第一次启动肯定是加载所有的,我们按照这种情况来分析。
private void loadAllApps() {
final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
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 (loadAllApps)");
return;
}
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
// Clear the list of apps
mBgAllAppsList.clear();// 清除所有app列表
SharedPreferences prefs = mContext.getSharedPreferences(
LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
for (UserHandleCompat user : profiles) {
// Query for the set of apps
final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);// 获取需要显示在Launcher上的activity列表
if (DEBUG_LOADERS) {
Log.d(TAG, "getActivityList took "
+ (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
}
// Fail if we don't have any apps
// TODO: Fix this. Only fail for the current user.
if (apps == null || apps.isEmpty()) {// 没有需要显示的,直接返回
return;
}
// Sort the applications by name
final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
Collections.sort(apps, new LauncherModel.ShortcutNameComparator(mLabelCache));// 排序
if (DEBUG_LOADERS) {
Log.d(TAG, "sort took " + (SystemClock.uptimeMillis()-sortTime) + "ms");
}
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfoCompat app = apps.get(i);
// This builds the icon bitmaps.
mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, mLabelCache));// 创建应用图标对象,并添加到所有APP列表中
}
if (ADD_MANAGED_PROFILE_SHORTCUTS && !user.equals(UserHandleCompat.myUserHandle())) {
// Add shortcuts for packages which were installed while launcher was dead.
String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX + mUserManager.getSerialNumberForUser(user);
Set<String> packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET);
HashSet<String> newPackageSet = new HashSet<String>();
for (LauncherActivityInfoCompat info : apps) {
String packageName = info.getComponentName().getPackageName();
if (!packagesAdded.contains(packageName)
&& !newPackageSet.contains(packageName)) {
InstallShortcutReceiver.queueInstallShortcut(info, mContext);
}
newPackageSet.add(packageName);
}
prefs.edit().putStringSet(shortcutsSetKey, newPackageSet).commit();
}
}
// Huh? Shouldn't this be inside the Runnable below?
final ArrayList<AppInfo> added = mBgAllAppsList.added;// 获取自上次更新(notify()广播)后新增加的应用清单,如果是开机初次启动Launcher,那么added就是mBgAllAppsList
mBgAllAppsList.added = new ArrayList<AppInfo>();// 将AllAppsList的added清空,不影响后续新增的app
// Post callback on main thread
mHandler.post(new Runnable() {
public void run() {
final long bindTime = SystemClock.uptimeMillis();
final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindAllApplications(added);
if (DEBUG_LOADERS) {
Log.d(TAG, "bound " + added.size() + " apps in "
+ (SystemClock.uptimeMillis() - bindTime) + "ms");
}
} else {
Log.i(TAG, "not binding apps: no Launcher activity");
}
}
});
if (DEBUG_LOADERS) {
Log.d(TAG, "Icons processed in "
+ (SystemClock.uptimeMillis() - loadTime) + "ms");
}
}
public void dumpState() {
synchronized (sBgLock) {
Log.d(TAG, "mLoaderTask.mContext=" + mContext);
Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
}
}
}
1.获取需要显示到Launcher中的app列表,创建app图标
2.绑定app--bindAllApplications
/**
* Add the icons for all apps.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAllApplications(final ArrayList<AppInfo> apps) {
if (LauncherAppState.isDisableAllApps()) {// 判断是否禁用所有app,就是所有应用都显示在一级目录
if (mIntentsOnWorkspaceFromUpgradePath != null) {
if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
getHotseat().addAllAppsFolder(mIconCache, apps,
mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
}
mIntentsOnWorkspaceFromUpgradePath = null;
}
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.onPackagesUpdated(
LauncherModel.getSortedWidgetsAndShortcuts(this));
}
} else {
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.setApps(apps);
mAppsCustomizeContent.onPackagesUpdated(LauncherModel.getSortedWidgetsAndShortcuts(this));
}
}
if (mLauncherCallbacks != null) {
mLauncherCallbacks.bindAllApplications(apps);
}
}
首先判断是否禁用所有app,就是所有应用都显示在桌面上,我们这里没有禁用所有app页面,直接到else代码块,
// 设置需要显示的app,并排序,更新数据
public void setApps(ArrayList<AppInfo> list) {
if (!LauncherAppState.isDisableAllApps()) {
mApps = list;
Collections.sort(mApps, LauncherModel.getAppNameComparator());
updatePageCountsAndInvalidateData();
}
}
我们跟一下updatePageCountsAndInvalidateData方法,看看到底怎么个过程,
private void updatePageCountsAndInvalidateData() {
if (mInBulkBind) {
mNeedToUpdatePageCountsAndInvalidateData = true;
} else {
updatePageCounts();
invalidateOnDataChange();
mNeedToUpdatePageCountsAndInvalidateData = false;
}
}
updatePageCounts是计算并更新需要的page数量,直接看invalidateOnDataChange方法,
/**
* We should call thise method whenever the core data changes (mApps, mWidgets) so that we can
* appropriately determine when to invalidate the PagedView page data. In cases where the data
* has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the
* next onMeasure() pass, which will trigger an invalidatePageData() itself.
*/
private void invalidateOnDataChange() {
if (!isDataReady()) {
// The next layout pass will trigger data-ready if both widgets and apps are set, so
// request a layout to trigger the page data when ready.
requestLayout();
} else {
cancelAllTasks();
invalidatePageData();
}
}
protected void invalidatePageData() {
invalidatePageData(-1, false);
}
protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
if (!mIsDataReady) {
return;
}
if (mContentIsRefreshable) {
// Force all scrolling-related behavior to end
forceFinishScroller();
// Update all the pages
syncPages();
// We must force a measure after we've loaded the pages to update the content width and
// to determine the full scroll width
measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
// Set a new page as the current page if necessary
// 设置当前显示页
if (currentPage > -1) {
setCurrentPage(Math.min(getPageCount() - 1, currentPage));
}
// Mark each of the pages as dirty
final int count = getChildCount();
mDirtyPageContent.clear();
for (int i = 0; i < count; ++i) {
mDirtyPageContent.add(true);
}
// Load any pages that are necessary for the current window of views
loadAssociatedPages(mCurrentPage, immediateAndOnly);
requestLayout();
}
if (isPageMoving()) {
// If the page is moving, then snap it to the final position to ensure we don't get
// stuck between pages
snapToDestination();
}
}
这个方法是重点,也分了几步来执行,我们分步来看:
1)syncPages
是一个抽象方法,在AppsCustomizePagedView中实现,
代码位置:launcher3\src\main\java\com\android\launcher3\AppsCustomizePagedView.java
@Override
public void syncPages() {
disablePagedViewAnimations();
// 移除所有视图和任务
removeAllViews();
cancelAllTasks();
Context context = getContext();
if (mContentType == ContentType.Applications) {
for (int i = 0; i < mNumAppsPages; ++i) {
AppsCustomizeCellLayout layout = new AppsCustomizeCellLayout(context);
setupPage(layout);
addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
}
} else if (mContentType == ContentType.Widgets) {
for (int j = 0; j < mNumWidgetPages; ++j) {
PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
mWidgetCountY);
setupPage(layout);
addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
}
} else {
throw new RuntimeException("Invalid ContentType");
}
enablePagedViewAnimations();
}
app和widget来分别添加,我们这里只需了解如何添加app页面的,widget完全类似。
a)生成AppsCustomizeCellLayout对象
b)setupPage,设置page
// 设置page的表格、背景色
private void setupPage(AppsCustomizeCellLayout layout) {
layout.setGridSize(mCellCountX, mCellCountY);// 设置页面表格数
// Note: We force a measure here to get around the fact that when we do layout calculations
// immediately after syncing, we don't have a proper width. That said, we already know the
// expected page width, so we can actually optimize by hiding all the TextView-based
// children that are expensive to measure, and let that happen naturally later.
setVisibilityOnChildren(layout, View.GONE);
int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
layout.measure(widthSpec, heightSpec);
// 设置page背景色
Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel);
if (bg != null) {
bg.setAlpha(mPageBackgroundsVisible ? 255: 0);
layout.setBackground(bg);
}
setVisibilityOnChildren(layout, View.VISIBLE);
}
c)addView,将CellLayout添加到page中
2)重新测量,设置当前页等
// We must force a measure after we've loaded the pages to update the content width and
// to determine the full scroll width
measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
// Set a new page as the current page if necessary
// 设置当前显示页
if (currentPage > -1) {
setCurrentPage(Math.min(getPageCount() - 1, currentPage));
}
3)loadAssociatedPages
protected void loadAssociatedPages(int page, boolean immediateAndOnly) {
if (mContentIsRefreshable) {
final int count = getChildCount();
if (page < count) {
int lowerPageBound = getAssociatedLowerPageBound(page);
int upperPageBound = getAssociatedUpperPageBound(page);
if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
+ upperPageBound);
// First, clear any pages that should no longer be loaded
for (int i = 0; i < count; ++i) {
Page layout = (Page) getPageAt(i);
if ((i < lowerPageBound) || (i > upperPageBound)) {
if (layout.getPageChildCount() > 0) {
layout.removeAllViewsOnPage();
}
mDirtyPageContent.set(i, true);
}
}
// Next, load any new pages
for (int i = 0; i < count; ++i) {
if ((i != page) && immediateAndOnly) {
continue;
}
if (lowerPageBound <= i && i <= upperPageBound) {
if (mDirtyPageContent.get(i)) {
syncPageItems(i, (i == page) && immediateAndOnly);
mDirtyPageContent.set(i, false);
}
}
}
}
}
}
先清除不需要加载的pages,然后加载page及items--syncPageItems,也是一个抽象方法在AppsCustomizePagedView中实现,
@Override
public void syncPageItems(int page, boolean immediate) {
if (mContentType == ContentType.Widgets) {
syncWidgetPageItems(page, immediate);
} else {
syncAppsPageItems(page, immediate);
}
}
// 添加items到page上
public void syncAppsPageItems(int page, boolean immediate) {
// ensure that we have the right number of items on the pages
final boolean isRtl = isLayoutRtl();// 是否从右向左排列,一般都是从左向右
int numCells = mCellCountX * mCellCountY;// 每页的格数,及可加载的app数量
int startIndex = page * numCells;// 开始位置
int endIndex = Math.min(startIndex + numCells, mApps.size());// 结束位置,如果不满页,就是app数量的总数
AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page);// 根据page号获取AppsCustomizeCellLayout
layout.removeAllViewsOnPage();
ArrayList<Object> items = new ArrayList<Object>();
ArrayList<Bitmap> images = new ArrayList<Bitmap>();
for (int i = startIndex; i < endIndex; ++i) {// 循环添加items
AppInfo info = mApps.get(i);
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(R.layout.apps_customize_application, layout, false);
icon.applyFromApplicationInfo(info);
icon.setOnClickListener(mLauncher);
icon.setOnLongClickListener(this);
icon.setOnTouchListener(this);
icon.setOnKeyListener(this);
icon.setOnFocusChangeListener(layout.mFocusHandlerView);
// icon.setTextColor(Color.WHITE); // modify text color
int index = i - startIndex;
// 就算item所在表格位置
int x = index % mCellCountX;
int y = index / mCellCountX;
if (isRtl) {
x = mCellCountX - x - 1;
}
layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false);
items.add(info);
images.add(info.iconBitmap);
}
enableHwLayersOnVisiblePages();
}
代码中都有注释,比较好理解,先计算需要放置的位置,然后创建BubbleTextView实例,最后将其添加到page中。整个setApps的过程还是非常长的,最终目的就是将app显示到所有app列表中。
接着会执行
onPackagesUpdated,package的更新操作,就不再展开了。
Widget也是类似的。
这样加载allapp的过程就结束了。