在原生launcher中,长按桌面会触发很多种行为。其分类包括:1、空白桌面;2、桌面内容(文件夹、快捷方式、文件夹等);3、桌面既有控件(左右两个屏幕切换按钮,all app list按钮)等;因此我们很容易理解Launcher.java文件中onLongClick函数的行为:
public boolean onLongClick(View v) {
switch (v.getId()) {
case R.id.previous_screen:
if (!isAllAppsVisible()) {
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
showPreviews(v);
}
return true;
case R.id.next_screen:
if (!isAllAppsVisible()) {
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
showPreviews(v);
}
return true;
case R.id.all_apps_button:
if (!isAllAppsVisible()) {
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
showPreviews(v);
}
return true;
}
if (isWorkspaceLocked()) {
return false;
}
if (!(v instanceof CellLayout)) {
v = (View) v.getParent();
}
//获得CellInfo信息
CellLayout.CellInfo cellInfo = (CellLayout.CellInfo) v.getTag();
// This happens when long clicking an item with the dpad/trackball
if (cellInfo == null) {
return true;
}
if (mWorkspace.allowLongPress()) {
if (cellInfo.cell == null) {//如果CellInfo为空且有效,则弹出选择对话框
if (cellInfo.valid) {
// User long pressed on empty space
mWorkspace.setAllowLongPress(false);//接下来的长按桌面操作将失效,直至回复为止
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
showAddDialog(cellInfo);
}
} else {
if (!(cellInfo.cell instanceof Folder)) {//如果CellInfo不为空,同时不是文件夹的话,则此时长按将触发拖动操作
// User long pressed on an item
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
mWorkspace.startDrag(cellInfo);
}
}
}
return true;
}
我们暂时不关注非空内容,先处理长按空白桌面后的操作。此时调用的是showAddDialog(cellInfo)函数:
private void showAddDialog(CellLayout.CellInfo cellInfo) {
mAddItemCellInfo = cellInfo;
mWaitingForResult = true;
showDialog(DIALOG_CREATE_SHORTCUT);
}
此函数只是调用了view的内置函数showDialog,因此我们很容易想到会有一个创建对话框的函数:
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_CREATE_SHORTCUT:
return new CreateShortcut().createDialog();//弹出添加内容对话框
case DIALOG_RENAME_FOLDER:
return new RenameFolder().createDialog();//弹出文件夹编辑对话框
}
return super.onCreateDialog(id);
}
对于添加内容的对话框,我们知道内容如下:

对于这个对话框,涉及到3方面内容:1、对话框显示及操作;2、可以选择的列表;3、点击某个列表项后的操作:
1、对话框:
/**
* Displays the shortcut creation dialog and launches, if necessary, the
* appropriate activity.
*/
private class CreateShortcut implements DialogInterface.OnClickListener,
DialogInterface.OnCancelListener, DialogInterface.OnDismissListener,
DialogInterface.OnShowListener {
private AddAdapter mAdapter;
Dialog createDialog() {
mAdapter = new AddAdapter(Launcher.this);
final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
builder.setTitle(getString(R.string.menu_item_add_item));
builder.setAdapter(mAdapter, this);
builder.setInverseBackgroundForced(true);
AlertDialog dialog = builder.create();
dialog.setOnCancelListener(this);
dialog.setOnDismissListener(this);
dialog.setOnShowListener(this);
return dialog;
}
public void onCancel(DialogInterface dialog) {
mWaitingForResult = false;
cleanup();
}
public void onDismiss(DialogInterface dialog) {
}
private void cleanup() {
try {
dismissDialog(DIALOG_CREATE_SHORTCUT);
} catch (Exception e) {
// An exception is thrown if the dialog is not visible, which is fine
}
}
/**
* Handle the action clicked in the "Add to home" dialog.
*/
public void onClick(DialogInterface dialog, int which) {
Resources res = getResources();
cleanup();
switch (which) {
case AddAdapter.ITEM_SHORTCUT: {
// Insert extra item to handle picking application
pickShortcut();
break;
}
case AddAdapter.ITEM_APPWIDGET: {
int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId();
Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
// start the pick activity
startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
break;
}
case AddAdapter.ITEM_LIVE_FOLDER: {
// Insert extra item to handle inserting folder
Bundle bundle = new Bundle();
ArrayList<String> shortcutNames = new ArrayList<String>();
shortcutNames.add(res.getString(R.string.group_folder));
bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
ArrayList<ShortcutIconResource> shortcutIcons =
new ArrayList<ShortcutIconResource>();
shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
R.drawable.ic_launcher_folder));
bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
pickIntent.putExtra(Intent.EXTRA_INTENT,
new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER));
pickIntent.putExtra(Intent.EXTRA_TITLE,
getText(R.string.title_select_live_folder));
pickIntent.putExtras(bundle);
startActivityForResult(pickIntent, REQUEST_PICK_LIVE_FOLDER);
break;
}
case AddAdapter.ITEM_WALLPAPER: {
startWallpaper();
break;
}
}
}
public void onShow(DialogInterface dialog) {
mWaitingForResult = true;
}
}
2、内容列表AddAdapter:
public AddAdapter(Launcher launcher) {
super();
mInflater = (LayoutInflater) launcher.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// Create default actions
Resources res = launcher.getResources();
mItems.add(new ListItem(res, R.string.group_shortcuts,
R.drawable.ic_launcher_shortcut, ITEM_SHORTCUT));
mItems.add(new ListItem(res, R.string.group_widgets,
R.drawable.ic_launcher_appwidget, ITEM_APPWIDGET));
mItems.add(new ListItem(res, R.string.group_live_folders,
R.drawable.ic_launcher_folder, ITEM_LIVE_FOLDER));
mItems.add(new ListItem(res, R.string.group_wallpapers,
R.drawable.ic_launcher_wallpaper, ITEM_WALLPAPER));
}
这里我们可以明显看到,添加了4项,分别对应上面那张图的四个图标。
3、添加快捷方式:
添加快捷方式,最简洁的方式是调用系统快捷方式列表:
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
pickIntent.putExtra(Intent.EXTRA_INTENT,
new Intent(Intent.ACTION_CREATE_SHORTCUT));
pickIntent.putExtra(Intent.EXTRA_TITLE,
res.getString(R.string.title_select_app));
pickIntent.putExtras(bundle);
startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT);
这个获取到的就是所有可以创建的快捷方式,但是我们需要在这个列表中加入其他项怎么办呢?因为我们知道,我需要在这个列表中加入“应用程序”,这一项,这样当我们点击应用程序的时候,就可以显示应用程序列表。这个时候,我们只需要传入额外的信息就可以了,如下:
Resources res = getResources();
/**
* 用一个Bundle对象,传递两个list信息
* 一个list中存放需要附加到快捷列表中的项的文字
* 一个list中存放每一个对应的图标信息
*/
Bundle bundle = new Bundle();
ArrayList<String> shortcutNames = new ArrayList<String>();
shortcutNames.add(res.getString(R.string.group_application));
shortcutNames.add("其他");
//显示在List第一个的应用快捷方式的名字
bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
//显示图标
ArrayList<ShortcutIconResource> shortcutIcons =
new ArrayList<ShortcutIconResource>();
shortcutIcons.add(ShortcutIconResource.fromContext(UorderLauncher.this,
R.drawable.icon));
shortcutIcons.add(ShortcutIconResource.fromContext(UorderLauncher.this,
R.drawable.icon));
bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
pickIntent.putExtra(Intent.EXTRA_INTENT,
new Intent(Intent.ACTION_CREATE_SHORTCUT));
pickIntent.putExtra(Intent.EXTRA_TITLE,
res.getString(R.string.title_select_app));
//将附加信息加入到pickIntent
pickIntent.putExtras(bundle);
startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT);
此时,点击以后,会弹出一个选择列表:

其中第一项“Applitions”是我们附加上去的,其他的都是已经存在的shortcut。因此我们知道我们点击第一项和点击其他项,需要做的操作时不一样。此时,若点击某一项触发onActivityResult函数中的processShortcut函数:
void processShortcut(Intent intent) {
// Handle case where user selected "Applications"
String applicationName = getResources().getString(R.string.group_applications);
String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
//说明用户选择的是应用程序,进入应用程序列表
if (applicationName != null && applicationName.equals(shortcutName)) {
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application));
startActivityForResultSafely(pickIntent, REQUEST_PICK_APPLICATION);
} else {
//否则,直接创建快捷方式
startActivityForResultSafely(intent, REQUEST_CREATE_SHORTCUT);
}
}
此后,才开始真正的建立shotcut的操作:
/**
* Add a shortcut to the workspace.
*
* @param data The intent describing the shortcut.
* @param cellInfo The position on screen where to create the shortcut.
*/
private void completeAddShortcut(Intent data, CellLayout.CellInfo cellInfo) {
cellInfo.screen = mWorkspace.getCurrentScreen();
if (!findSingleSlot(cellInfo)) return;//寻找空的单个cellInfo
final ShortcutInfo info = mModel.addShortcut(this, data, cellInfo, false);
if (!mRestoring) {
final View view = createShortcut(info);
mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1,
isWorkspaceLocked());
}
}
这里,存在两个问题:1、在桌面寻找空间,只有桌面存在空间才可以添加快捷方式;2、生成快捷方式并添加到桌面;
对于第一步寻找可用空间:
private boolean findSingleSlot(CellLayout.CellInfo cellInfo) {
final int[] xy = new int[2];
if (findSlot(cellInfo, xy, 1, 1)) {
cellInfo.cellX = xy[0];
cellInfo.cellY = xy[1];
return true;
}
return false;
}
private boolean findSlot(CellLayout.CellInfo cellInfo, int[] xy, int spanX, int spanY) {
if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
boolean[] occupied = mSavedState != null ?
mSavedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS) : null;
cellInfo = mWorkspace.findAllVacantCells(occupied);
if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
return false;
}
}
return true;
}
这里实际上对于添加快捷方式来说,是肯定有空间的。如果没有空间,则弹出一个空间不足的提示。(因为你长按的本身就是空白的cellInfo,但是如果在你操作的过程中,有其他程序占用了这个空白空间,那么就会有问题了,因此需要判断是否有可用空间)。
如果有可用空间,则创建一个快捷方式,并将其信息添加到数据库:
ShortcutInfo addShortcut(Context context, Intent data,
CellLayout.CellInfo cellInfo, boolean notify) {
final ShortcutInfo info = infoFromShortcutIntent(context, data);
addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
return info;
}
此时,这个快捷方式只是存在于数据库中,并未真正显示在桌面。因此需要生产一个图标,
/**
* Creates a view representing a shortcut.
*
* @param info The data structure describing the shortcut.
*
* @return A View inflated from R.layout.application.
*/
View createShortcut(ShortcutInfo info) {
return createShortcut(R.layout.application,
(ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info);
}
/**
* Creates a view representing a shortcut inflated from the specified resource.
*
* @param layoutResId The id of the XML layout used to create the shortcut.
* @param parent The group the shortcut belongs to.
* @param info The data structure describing the shortcut.
*
* @return A View inflated from layoutResId.
*/
View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false);
favorite.setCompoundDrawablesWithIntrinsicBounds(null,
new FastBitmapDrawable(info.getIcon(mIconCache)),
null, null);
favorite.setText(info.title);
favorite.setTag(info);
favorite.setOnClickListener(this);
return favorite;
}
最后将这个图标,添加到桌面进行显示:
/**
* 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 screen 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.
*/
void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
if (screen < 0 || screen >= getChildCount()) {
Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
+ " (was " + screen + "); skipping child");
return;
}
clearVacantCache();
final CellLayout group = (CellLayout) getChildAt(screen);
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
if (lp == null) {
lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
} else {
lp.cellX = x;
lp.cellY = y;
lp.cellHSpan = spanX;
lp.cellVSpan = spanY;
}
group.addView(child, insert ? 0 : -1, lp);
if (!(child instanceof Folder)) { //添加该view的长按操作Listener
child.setHapticFeedbackEnabled(false);
child.setOnLongClickListener(mLongClickListener);
}
if (child instanceof DropTarget) {
mDragController.addDropTarget((DropTarget)child);
}
}
以上我们介绍了添加shotcut的流程,对于其他内容包括widget等,流程类似,就不介绍了。
参考资料:
《 说说Android桌面(Launcher应用)背后的故事(二)——应用程序的添加》