Launcher3 應用圖示的載入流程
下面從bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
currentFolders, null);開始具體分析應用圖示的載入過程:
LauncherModel.java:
private void bindWorkspaceItems(final Callbacks oldCallbacks,
final ArrayList<ItemInfo> workspaceItems,
final ArrayList<LauncherAppWidgetInfo> appWidgets,
final HashMap<Long, FolderInfo> folders,
ArrayList<Runnable> deferredBindRunnables) {
for (int i = 0; i < N; i += ITEMS_CHUNK) {
final int start = i;
final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
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) {
deferredBindRunnables.add(r);
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
}
}
在上述程式碼中,callbacks.bindItems(workspaceItems, start, start+chunkSize, false);是載入的核心程式碼:
Launcher.java:
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;
}
// 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) {
continue;
}
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)) {
throw new RuntimeException("OCCUPIED");
}
}
workspace.addInScreenFromBind(shortcut, item.container,
item.screenId, item.cellX, item.cellY, 1, 1);
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();
}
上述程式碼中,以下兩行程式碼建立新的應用圖示的快捷方式。
ShortcutInfo info = (ShortcutInfo) item;
View shortcut = createShortcut(info);
以下程式碼是把快捷方式新增到螢幕中對應位置:
workspace.addInScreenFromBind(shortcut, item.container,
item.screenId, item.cellX, item.cellY, 1, 1);
該函式的實現:
WorkSpace.java:
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);
}
被呼叫函式addInScreen()函式的實現:
WorkSpace.java:
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(null);
// 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);
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);
}
}
該程式碼中的核心程式碼是 if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {},該函式中的主要引數有child:新增的圖示物件,lp:封裝了該物件在CellLayout中位置。
addViewToCellLayout()函式的實現:
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) {
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);
return true;
}
return false;
}
該函式實現把Item物件新增到CellLayout中,該函式的核心程式碼是 mShortcutsAndWidgets.addView(child, index, lp)
mShortcutsAndWidgets物件其實是CellLayout中的最終新增shortcut的容器,呼叫addView()函式,最終根據引數lp把shortcut新增到容器中。
在ShortcutAndWidgetContainer.java中,主要是呼叫onMeasure()和onLayout()函式對新增的item進行測量和佈局。
ShortcutAndWidgetContainer.java:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSpecSize, heightSpecSize);
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChild(child);
}
}
}
該函式呼叫measureChild()對子物件進行測量。
measureChild()的實現:
ShortcutAndWidgetContainer.java:
public void measureChild(View child) {
final LauncherAppState app = LauncherAppState.getInstance();
final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
final int cellWidth = mCellWidth;
final int cellHeight = mCellHeight;
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
if (!lp.isFullscreen) {
lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(), mCountX);
if (child instanceof LauncherAppWidgetHostView) {
// Widgets have their own padding, so skip
} else {
// Otherwise, center the icon
int cHeight = getCellContentHeight();
int cellPaddingY = (int) Math.max(0,
((lp.height - cHeight) / 2f));
int cellPaddingX = (int) (grid.edgeMarginPx / 2f);
cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f));
child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0);
}
} else {
lp.x = 0;
lp.y = 0;
lp.width = getMeasuredWidth();
lp.height = getMeasuredHeight();
}
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childheightMeasureSpec);
}
上述程式碼中比較重要的方法有,lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(), mCountX),這個方法用於把計算的結果封裝到LayoutParams中,用於佈局item的時候進行呼叫。
ShortcutAndWidgetContainer.java:
onLayout()函式:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
int childLeft = lp.x;
int childTop = lp.y;
child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
if (lp.dropped) {
lp.dropped = false;
final int[] cellXY = mTmpCellXY;
getLocationOnScreen(cellXY);
mWallpaperManager.sendWallpaperCommand(getWindowToken(),
WallpaperManager.COMMAND_DROP,
cellXY[0] + childLeft + lp.width / 2,
cellXY[1] + childTop + lp.height / 2, 0, null);
}
}
}
}
該函式主要呼叫child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);對子物件進行佈局。